pytest-dev / pytest-dev/pyfakefs

problem with `os.stat` within `eyed3.load` when running multiple tests using `fs` based fixture

Open
#1,105 23 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

limitation
Dominant language
Python
Stars
750
Forks
99
Avg merge
19h 6m
Merged PRs (30d)
6

Description

Describe the bug
Hello together, I have hard times finding out what causes the following error: I am using the fs (not fs_module etc. -> fs) inside a fixture. There is one test, that, when ran in isolation, succeeds. But when it runs together (sequentially) with any other test that uses the fs based fixture it fails.

TLDR
Of course the first thought is, are there any other filesystem actions colliding with the test but I think i ruled those out:

1: fs is used on a per test basis (as far as I can read from the docs only fs_module etc. are scoped broader than function, right?)

2: I added some debugging lines that verify the existence of the file which a code line later causes a fail. Also, before the failing line os.stat is called within eyed3.load method, eyed3 performs some internal checks like if not path.is_file() that are passed. So the pure existance of the file should be given I think. more can be read here: https://github.com/nicfit/eyeD3/blob/2cdef0d7542e65cc2756d63c2afffbdf48061eea/eyed3/mp3/__init__.py#L93

How To Reproduce
This is the test setup and test code up to the point where it raises:

# conftest.py
@pytest.fixture(scope="function")
def test_fs(fs):
    yield fs

# testmethod
    @e2e
    def test_copy_audio_with_fs(
        self,
        sut: AudioFileManager,
        test_assets_dir: str,
        test_fs,
    ):
        # GIVEN
        test_fs.add_real_directory(str(test_assets_dir))
        source_file_path = os.path.join(test_assets_dir, "0001.mp3")
        target_file_path = os.path.join(
            test_assets_dir,
            "target_repo",
            "9999.mp3",
        )
        os.makedirs(
            os.path.join(test_assets_dir, "target_repo"),
        )
        # WHEN
        sut.copy_audio( # <-- this line calls, besides others, the function _read_audio() that raises
            source_file_path,
            target_file_path,
        )

The following code is where it leaves my codebase and raises the error within eyed3:

    def _read_audio(self, file_path: str) -> eyed3.AudioFile:
        # some debugging lines ...
        print(f"file_path: {file_path}")
        print(f"files in parent dir: {os.listdir(os.path.dirname(file_path))}")
        print(f"is parent a dir: {os.path.isdir(os.path.dirname(file_path))}")
        print(f"is file_path a file: {os.path.isfile(file_path)}")
        print(f"size_bytes: {os.stat(file_path)[stat.ST_SIZE]}") # <-- this is the line used in eyed3.load internally that throws, see down below the stack trace
        audio = eyed3.load(file_path) # <-- this line throws when reaching the code line equal to the above one, see down below the stack trace
        # more stuff here that is irrelevant, because the line before fails
        return audio

The resulting debugging output is this:

file_path: [...]\tests\test_assets\target_repo\9999.mp3
files in parent dir: ['9999.mp3']
is parent a dir: True
is file_path a file: True
size_bytes: 3650

The error thrown/stacktrace is:

    sut.copy_audio(
[...]\src\mp3\audio_file_manager.py:52: in copy_audio
    audio = self._read_audio(target_path)
[...]\src\mp3\audio_file_manager.py:81: in _read_audio
    audio = eyed3.load(file_path)
[...]\.venv\Lib\site-packages\eyed3\core.py:452: in load
    return mp3.Mp3AudioFile(path, tag_version)
[...]\.venv\Lib\site-packages\eyed3\mp3\__init__.py:146: in __init__
    super().__init__(path)
[...]\.venv\Lib\site-packages\eyed3\core.py:239: in __init__
    self._read()
[...]\.venv\Lib\site-packages\eyed3\mp3\__init__.py:164: in _read
    self._info = Mp3AudioInfo(file_obj, mp3_offset, self._tag)
[...]\.venv\Lib\site-packages\eyed3\mp3\__init__.py:93: in __init__
    size_bytes = os.stat(file_obj.name)[stat.ST_SIZE]
[...]\.venv\Lib\site-packages\pyfakefs\fake_os.py:1426: in wrapped
    return f(*args, **kwargs)
[...]\.venv\Lib\site-packages\pyfakefs\fake_os.py:693: in stat
    return self.filesystem.stat(path, follow_symlinks)
[...]\.venv\Lib\site-packages\pyfakefs\fake_filesystem.py:673: in stat
    file_object = self.resolve(
[...]\.venv\Lib\site-packages\pyfakefs\fake_filesystem.py:1760: in resolve
    return self.get_object_from_normpath(
[...]\.venv\Lib\site-packages\pyfakefs\fake_filesystem.py:1689: in get_object_from_normpath
    self.raise_os_error(errno.ENOENT, path)
[...]\.venv\Lib\site-packages\pyfakefs\fake_filesystem.py:430: in raise_os_error
    raise OSError(err_no, message, filename)
E   FileNotFoundError: [Errno 2] No such file or directory in the fake filesystem: '[...]\\tests\\test_assets\\target_repo\\9999.mp3'

Your environment
Windows-11-10.0.22000-SP0
Python 3.12.3 (tags/v3.12.3:f6650f9, Apr 9 2024, 14:05:25) [MSC v.1938 64 bit (AMD64)]
pyfakefs 5.6.0
pytest 8.2.1

does such a scenario ring a bell of someone? What can I do to further track down the issue?

Kind regards!

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the sequential fixture case from conftest.py and test_copy_audio_with_fs, then trace the failing path through pyfakefs/fake_os.py and fake_filesystem.py alongside eyed3.load and eyed3/mp3/init.py. Done means the interaction has a confirmed root cause and an agreed regression test or fix.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.