././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1741721344.9685268 pyfakefs-5.8.0/0000755000175100001660000000000014764107401012743 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/CHANGES.md0000644000175100001660000015434214764107375014360 0ustar00runnerdocker# pyfakefs Release Notes The released versions correspond to PyPI releases. ## Policy for Python version support * support for new versions is usually added preliminarily during the Python release beta phase, official support after the final release * support for EOL versions is removed as soon as the CI (GitHub actions) does no longer provide these versions (usually several months after the official EOL) ## Planned changes for next major release (6.0.0) * support for patching legacy modules `scandir` and `pathlib2` will be removed * the default for `FakeFilesystem.shuffle_listdir_results` will change to `True` to reflect the real filesystem behavior ## [Version 5.8.0](https://pypi.python.org/pypi/pyfakefs/5.8.0) (2025-03-11) Adds preliminary support for Python 3.14. ### Changes * added some preliminary support for Python 3.14 * change behavior of `FakeFilesystem.get_object()` to ignore permissions as it has been before version 5.4.0 (see [#1122](../../issues/1122)) ### Fixes * fixed a problem with flushing if writing over the buffer end (see [#1120](../../issues/1120)) * fixed a regression that could break tests under Posix in Python 3.12 (see [#1126](../../issues/1126)) * fixed behavior for `os.access` for symlinks under Windows * fixed permission problem on querying file properties (see [#1122](../../issues/1122)) * fixed patching in pytest setup phase for module and session-scoped fs fixtures (see [#1126](../../issues/1126)) ### Documentation * use a theme for documentation supporting dark mode ## [Version 5.7.4](https://pypi.python.org/pypi/pyfakefs/5.7.4) (2025-01-14) Minor bugfix release. ### Fixes * fixed a problem with module and session scoped fixtures in Python 3.13 (see [#1101](../../issues/1101)) * fixed handling of `cwd` if set to a `pathlib.Path` (see [#1108](../../issues/1108)) * fixed documentation for cleanup handlers, added convenience handler `reload_cleanup_handler` (see [#1105](../../issues/1105)) ## [Version 5.7.3](https://pypi.python.org/pypi/pyfakefs/5.7.3) (2024-12-15) Fixes a regression in version 5.7.3. ### Fixes * fixed a regression in version 5.7.2 that `tempfile` was not patched after pause/resume (POSIX only, see [#1098](../../issues/1098)) * added workaround for a recursion occurring if using pytest under Windows and Python >= 3.12 (see [#1096](../../issues/1096)) ### Infrastructure * run pytest-specific tests for all supported Python versions * pytest is only supported for versions >= 6.2.5, earlier version do not work in Python >= 3.10 due to a pytest issue - adapted tests and documentation ## [Version 5.7.2](https://pypi.python.org/pypi/pyfakefs/5.7.2) (2024-12-01) Fixes some problems with patching. ### Fixes * added some support for loading fake modules in `AUTO` patch mode using `importlib.import_module` (see [#1079](../../issues/1079)) * added some support to avoid patching debugger related modules (see [#1083](../../issues/1083)) ### Performance * avoid reloading `tempfile` in Posix systems ### Infrastructure * use trusted publisher for release (see https://docs.pypi.org/trusted-publishers/) ## [Version 5.7.1](https://pypi.python.org/pypi/pyfakefs/5.7.1) (2024-08-13) Fixes a regression in version 5.7.0 that broke patching fcntl. ### Fixes * fixes a regression that caused unfaked `fcntl` calls to fail (see [#1074](../../issues/1074)) ## [Version 5.7.0](https://pypi.python.org/pypi/pyfakefs/5.7.0) (2024-08-10) Adds official Python 3.13 support, improves OS emulation behavior. ### Changes * officially support Python 3.13 ### Enhancements * the `additional_skip_names` parameter now works with more modules (see [#1023](../../issues/1023)) * added support for `os.fchmod`, allow file descriptor argument for `os.chmod` only for POSIX for Python < 3.13 ### Performance * avoid reloading `glob` in Python 3.13 ### Fixes * removing files while iterating over `scandir` results is now possible (see [#1051](../../issues/1051)) * fake `pathlib.PosixPath` and `pathlib.WindowsPath` now behave more like in the real filesystem (see [#1053](../../issues/1053)) * `PurePosixPath` reported Windows reserved names as reserved in Python >= 3.12 (see [#1067](../../issues/1067)) * `PurePosixPath.joinpath()` incorrectly handled paths with drives under Windows in Python >= 3.12 (see [#1070](../../issues/1070)) ## [Version 5.6.0](https://pypi.python.org/pypi/pyfakefs/5.6.0) (2024-07-12) Adds preliminary Python 3.13 support. ### Enhancements * added preliminary support for Python 3.13 (tested with beta2) (see [#1017](../../issues/1017)) * added `apply_umask` argument to `FakeFilesystem.create_dir` to allow ignoring the umask (see [#1038](../../issues/1038)) ### Fixes * use real open calls for remaining `pathlib` functions so that it works nice with skippedmodules (see [#1012](../../issues/1012)) ### Infrastructure * Add pyupgrade as a pre-commit hook. ## [Version 5.5.0](https://pypi.python.org/pypi/pyfakefs/5.5.0) (2024-05-12) Deprecates the usage of `pathlib2` and `scandir`. ### Changes * The usage of the `pathlib2` and `scandir` modules in pyfakefs is now deprecated. They will now cause deprecation warnings if still used. Support for patching these modules will be removed in pyfakefs 6.0. * `PureWindowsPath` and `PurePosixPath` now use filesystem-independent path separators, and their path-parsing behaviors are now consistent regardless of runtime platform and/or faked filesystem customization (see [#1006](../../issues/1006)). ### Fixes * fixed handling of Windows `pathlib` paths under POSIX and vice verse (see [#1006](../../issues/1006)) * correctly use real open calls in pathlib for skipped modules (see [#1012](../../issues/1012)) ## [Version 5.4.1](https://pypi.python.org/pypi/pyfakefs/5.4.0) (2024-04-11) Fixes a regression. ### Fixes * fixed a regression from version 5.4.0 that incorrectly handled files opened twice via file descriptor (see [#997](../../issues/997)) ## [Version 5.4.0](https://pypi.python.org/pypi/pyfakefs/5.4.0) (2024-04-07) Improves permission handling. ### Changes * the handling of file permissions under Posix should now mostly match the behavior of the real filesystem, which may change the behavior of some tests * removed the argument `module_cleanup_mode`, that was introduced as a temporary workaround in the previous version - related problems shall be handled using a cleanup handler ### Enhancements * added support for `O_NOFOLLOW` and `O_DIRECTORY` flags in `os.open` (see [#972](../../issues/972) and [#974](../../issues/974)) * added support for fake `os.dup`, `os.dup2` and `os.lseek` (see [#970](../../issues/970)) ### Fixes * fixed a specific problem on reloading a pandas-related module (see [#947](../../issues/947)), added possibility for unload hooks for specific modules * use this also to reload django views (see [#932](../../issues/932)) * fixed `EncodingWarning` for Python >= 3.11 (see [#957](../../issues/957)) * consider directory ownership while adding or removing directory entries (see [#959](../../issues/959)) * fixed handling of directory enumeration and search permissions under Posix systems (see [#960](../../issues/960)) * fixed creation of the temp directory in the fake file system after a filesystem reset (see [#965](../../issues/965)) * fixed handling of `dirfd` in `os.symlink` (see [#968](../../issues/968)) * add missing `follow_symlink` argument to `os.link` (see [#973](../../issues/973)) * fixed handling of missing attribute in `os.getxattr` (see [#971](../../issues/971)) * fixed permission problem with `shutil.rmtree` if emulating Windows under POSIX (see [#979](../../issues/979)) * fixed handling of errors on opening files via file descriptor (see [#967](../../issues/967)) * fixed handling of `umask` - it is now applied by default * fixed behavior of `os.makedirs` (see [#987](../../issues/987)) ### Infrastructure * replace `undefined` by own minimal implementation to avoid importing it (see [#981](../../discussions/981)) ## [Version 5.3.5](https://pypi.python.org/pypi/pyfakefs/5.3.5) (2024-01-30) Fixes a regression. ### Fixes * Fixed a regression due to the changed behavior of the dynamic patcher cleanup (see [#939](../../issues/939)). The change is now by default only made if the `django` module is loaded, and the behavior can be changed using the new argument `module_cleanup_mode`. ### Packaging * included `tox.ini` and a few more files into the source distribution (see [#937](../../issues/937)) ## [Version 5.3.4](https://pypi.python.org/pypi/pyfakefs/5.3.4) (2024-01-19) Bugfix release. ### Fixes * fixed handling of unhashable modules which cannot be cached (see [#923](../../issues/923)) * reload modules loaded by the dynamic patcher instead of removing them - sometimes they may not be reloaded automatically (see [#932](../../issues/932)) * added back argument `use_dynamic_patch` as a fallback for similar problems ## [Version 5.3.2](https://pypi.python.org/pypi/pyfakefs/5.3.2) (2023-11-30) Bugfix release. ### Fixes * fixed a problem with patching `_io` under Python 3.12 (see [#910](../../issues/910)) * fixed a problem with accessing the temp path if emulating Linux under Windows (see [#912](../../issues/912)) * fixed result of `os.walk` with a path-like top directory (see [#915](../../issues/915)) * properly fixed the problem that filesystem patching was still active in the pytest logreport phase (see [#904](../../issues/904)), the previous fix was incomplete ## [Version 5.3.1](https://pypi.python.org/pypi/pyfakefs/5.3.1) (2023-11-15) Mostly a bugfix release. ### Changes * changed behavior of `add_real_directory` to be able to map a real directory to an existing directory in the fake filesystem (see [#901](../../issues/901)) ### Fixes * fixed the problem that filesystem patching was still active in the pytest logreport phase (see [#904](../../issues/904)) * restored compatibility with PyTorch 2.0 and above, as well as with other classes that have custom __setattr__ methods (see [#905](../../pull/905)) ## [Version 5.3.0](https://pypi.python.org/pypi/pyfakefs/5.3.0) (2023-10-11) Adds official support for Python 3.12. ### Changes * added official support for Python 3.12 ### Fixes * removed a leftover debug print statement (see [#869](../../issues/869)) * make sure tests work without HOME environment set (see [#870](../../issues/870)) * automount drive or UNC path under Windows if needed for `pathlib.Path.mkdir()` (see [#890](../../issues/890)) * adapted patching `io.open` and `io.open_code` to work with Python 3.12 (see [#836](../../issues/836) and [#892](../../issues/892)) ## [Version 5.2.4](https://pypi.python.org/pypi/pyfakefs/5.2.4) (2023-08-18) Fixes a rare problem on pytest shutdown. ### Fixes * Clear the patched module cache on session shutdown (pytest only) (see [#866](../../issues/866)). Added a class method `Patcher.clear_fs_cache` for clearing the patched module cache. ## [Version 5.2.3](https://pypi.python.org/pypi/pyfakefs/5.2.3) (2023-07-10) Adds compatibility with PyPy 3.10 and Python 3.12. ### Fixes * Re-create temp directory if it had been created before on resetting file system (see [#814](../../issues/814)). * Excluded pytest `pathlib` modules from patching to avoid mixup of patched/unpatched code (see [#814](../../issues/814)). * Adapted to changes in Python 3.12 beta1 (only working partially, see [#830](../../issues/830) and [#831](../../issues/831)). * Adapted to changes in `shutil` in Python 3.12 beta2 (see [#814](../../issues/814)). * Fixed support for newer PyPi versions (see [#859](../../issues/859)). ### Documentation * Added a note regarding the incompatibility of the built-in `sqlite3` module with `pyfakefs` (see [#850](../../issues/850)) ### Infrastructure * Added pytype check for non-test modules in CI (see [#599](../../issues/599)). * Added tests for different pypy3 versions. * Added codespell hook to pre-commit ## [Version 5.2.2](https://pypi.python.org/pypi/pyfakefs/5.2.2) (2023-04-13) Fixes a regression in 5.2.0 ### Changes * Made the user and group IDs accessible via dedicated ``get_uid`` and ``get_gid`` functions (for symmetry to ``set_uid`` / ``set_gid``) ### Fixes * The test fixture is now included in the source distribution and installed with the package. * Some public constants in `fake_filesystem` that had been moved to `helpers` are made accessible from there again (see [#809](../../issues/809)). * Add missing fake implementations for `os.getuid` and `os.getgid` (Posix only) * Make sure a `/tmp` path exists under linux (`TMPDIR` may point elsewhere) (see [#810](../../issues/810)) ## [Version 5.2.1](https://pypi.python.org/pypi/pyfakefs/5.2.1) (2023-04-11) Support for latest Python 3.12 version. ### Changes * Adapted fake pathlib to changes in Python 3.12a7 (last alpha version) ### Fixes * Properties defining the capabilities of some `os` functions like `os.supports_follow_symlinks` are now properly faked to contain the fake functions if the real functions are faked (see [#799](../../issues/799)) ## [Version 5.2.0](https://pypi.python.org/pypi/pyfakefs/5.2.0) (2023-03-31) Supports current Python 3.12 version (alpha 6). We plan to make patch releases in case of breaking changes in alpha or beta versions. ### Changes * Fake module classes previously defined in `fake_filesystem` have now moved to their own modules: `fake_os.FakeOsModule`, `fake_path.FakePathModule`, `fake_io.FakeIoModule` and `fake_open.FakeFileOpen`. Additionally, all fake file classes have been moved to `fake_file`. While most of the changes shall be upwards compatible, we cannot exclude that we missed some problems. * Under macOS, at test start a symlink `/tmp` to the actual temporary directory is now created in the fake filesystem. * Patching of parsers for pandas >= 1.2 is removed since pandas now uses Python fs functions internally even when the engine selected is "c". ### Features * added possibility to set a path inaccessible under Windows by using `chown()` with the `force_unix_mode` flag (see [#720](../../issues/720)) * added support for current Python 3.12 version (alpha 6) * added support for `os.path.splitroot` (new in Python 3.12) ## [Version 5.1.0](https://pypi.python.org/pypi/pyfakefs/5.1.0) (2023-01-12) New version before Debian freeze ### Features * added class level setup method `setUpClassPyfakefs` for unittest and class-scoped fixture `fs_class` for pytest (see [#752](../../issues/752)) * added experimental support for Python 3.12: added fake APIs for Windows junction support. These are not implemented and always return `False`. ### Infrastructure * replaced end-of-life CentOS with RedHat UBI9 docker image * added tests for pytest 7.2.0 * added black to pre-commit checks, which caused some changes to the coding style (max line length is now 88, always use double quotes) * added Python 3.12 to the test suite. * migrated to [setuptools declarative syntax](https://setuptools.pypa.io/en/latest/userguide/declarative_config.html). * fixed docker tests when running on branches containing forward slashes ## [Version 5.0.0](https://pypi.python.org/pypi/pyfakefs/5.0.0) (2022-10-09) New version after the transfer to `pytest-dev`. ### Changes * the old-style API deprecated since version 3.4 has now been removed * the method `copyRealFile` deprecated since version 3.2 has been removed - use `add_real_file` instead ### Infrastructure * transferred the repository to the `pytest-dev` organization * renamed the `master` branch to `main` * added automatic PyPI release workflow * move documentation from GitHub Pages to Read the Docs ### New Features * added some support for `st_blocks` in stat result (see [#722](../../issues/722)) ### Fixes * fixed handling of `O_TMPFILE` in `os.open` (caused handling of `O_DIRECTORY` as `O_TMPFILE`) (see [#723](../../issues/723)) * fixed handling of read permissions (see [#719](../../issues/719)) ## [Version 4.7.0](https://pypi.python.org/pypi/pyfakefs/4.7.0) (2022-09-18) Changed handling of nested fixtures and bug fixes. ### Changes * `fs` fixtures cannot be nested; any nested `fs` fixture (for example inside an `fs_session` or `fs_module` fixture) will just reference the outer fixture (the behavior had been unexpected before) ### Fixes * reverted a performance optimization introduced in version 3.3.0 that caused hanging tests with installed torch (see [#693](../../issues/693)) * do not use the built-in opener in `pathlib` as it may cause problems (see [#697](../../issues/697)) * add support for path-like objects in `shutil.disk_usage` (see [#699](../../issues/699)) * do not advertise support for Python 3.6 in `setup.py` (see [#707](../../issues/707)) * return the expected type from `fcntl.ioctl` and `fcntl.fcntl` calls if `arg` is of type `byte`; the call itself does nothing as before * do not skip filesystem modules by name to allow using own modules with the same name (see [#707](../../issues/707)) * add missing support for `os.renames` (see [#714](../../issues/714)) ## [Version 4.6.3](https://pypi.python.org/pypi/pyfakefs/4.6.3) (2022-07-20) Another patch release that fixes a regression in version 4.6. ### Changes * automatically reset filesystem on changing `is_windows_fs` or `is_macos` (see [#692](../../issues/692)) - ensures better upwards compatibility in most cases :warning: Make sure you write to the filesystem _after_ you change `is_windows_fs` or `is_macos`, otherwise the changes will be lost. ### Fixes * fixed regression: `os.path.exists` returned `True` for any root drive path under Windows ## [Version 4.6.2](https://pypi.python.org/pypi/pyfakefs/4.6.2) (2022-07-14) Patch release that fixes an error in the previous patch. ### Fixes * fixed support for `opener` introduced in previous patch release (see [#689](../../issues/689)) ## [Version 4.6.1](https://pypi.python.org/pypi/pyfakefs/4.6.1) (2022-07-13) Fixes incompatibility with Python 3.11 beta 4. _Note_: Python 3.11 is only supported in the current beta 4 version, problems with later beta or rc versions are still possible. We will try to fix such problems in short order should they appear. ### Fixes * added support for `opener` argument in `open`, which is used in `tempfile` in Python 3.11 since beta 4 (see [#686](../../issues/686)) ### Infrastructure * make sure tests run without `pyfakefs` installed as a package (see [#687](../../issues/687)) ## [Version 4.6.0](https://pypi.python.org/pypi/pyfakefs/4.6.0) (2022-07-12) Adds support for Python 3.11, removes support for Python 3.6, changes root path behavior under Windows. ### Changes * Python 3.6 has reached its end of life on 2021/12/23 and is no longer officially supported by pyfakefs * `os.stat_float_times` has been removed in Python 3.7 and is therefore no longer supported * under Windows, the root path is now effectively `C:\` instead of `\`; a path starting with `\` points to the current drive as in the real file system (see [#673](../../issues/673)) * fake `pathlib.Path.owner()` and `pathlib.Path.group()` now behave like the real methods - they look up the real user/group name for the user/group id that is associated with the fake file (see [#678](../../issues/678)) ### New Features * added some support for the upcoming Python version 3.11 (see [#677](../../issues/677)) * added convenience fixtures for module- and session based `fs` fixtures (`fs_module` and `fs_session`) ### Fixes * fixed an incompatibility of `tmpdir` (and probably other fixtures) with the module-scoped version of `fs`; had been introduced in pyfakefs 4.5.5 by the fix for [#666](../../issues/666) (see [#684](../../issues/684)) ## [Version 4.5.6](https://pypi.python.org/pypi/pyfakefs/4.5.6) (2022-03-17) Fixes a regression which broke tests with older pytest versions (< 3.9). ### Changes * minimum supported pytest version is now 3.0 (older versions do not work properly with current Python versions) ### Fixes * only skip `_pytest.pathlib` in pytest versions where it is actually present (see [#669](../../issues/669)) ### Infrastructure * add tests with different pytest versions, starting with 3.0 ## [Version 4.5.5](https://pypi.python.org/pypi/pyfakefs/4.5.5) (2022-02-14) Bugfix release, needed for compatibility with pytest 7.0. ### Fixes * correctly handle file system space for files opened in write mode (see [#660](../../issues/660)) * correctly handle reading/writing pipes via file (see [#661](../../issues/661)) * disallow `encoding` argument on binary `open()` (see [#664](../../issues/664)) * fixed compatibility issue with pytest 7.0.0 (see [#666](../../issues/666)) ## [Version 4.5.4](https://pypi.python.org/pypi/pyfakefs/4.5.4) (2022-01-12) Minor bugfix release. ### Fixes * added missing mocked functions for fake pipe (see [#650](../../issues/650)) * fixed some bytes warnings (see [#651](../../issues/651)) ## [Version 4.5.3](https://pypi.python.org/pypi/pyfakefs/4.5.3) (2021-11-08) Reverts a change in the previous release that could cause a regression. ### Changes * `os.listdir`, `os.scandir` and `pathlib.Path.listdir` now return the directory list in a random order only if explicitly configured in the file system (use `fs.shuffle_listdir_results = True` with `fs` being the file system). In a future version, the default may be changed to better reflect the real filesystem behavior (see [#647](../../issues/647)) ## [Version 4.5.2](https://pypi.python.org/pypi/pyfakefs/4.5.2) (2021-11-07) This is a bugfix release. ### Changes * `os.listdir`, `os.scandir` and `pathlib.Path.listdir` now return the directory list in a random order (see [#638](../../issues/638)) * the `fcntl` module under Unix is now mocked, e.g. all functions have no effect (this may be changed in the future if needed, see [#645](../../issues/645)) ### Fixes * fixed handling of alternative path separator in `os.path.split`, `os.path.splitdrive` and `glob.glob` (see [#632](../../issues/632)) * fixed handling of failed rename due to permission error (see [#643](../../issues/643)) ## [Version 4.5.1](https://pypi.python.org/pypi/pyfakefs/4.5.1) (2021-08-29) This is a bugfix release. ### Fixes * added handling of path-like where missing * improved handling of `str`/`bytes` paths * suppress all warnings while inspecting loaded modules (see [#614](../../issues/614)) * do not import pandas and related modules if it is not patched (see [#627](../../issues/627)) * handle `pathlib.Path.owner()` and `pathlib.Path.group` by returning the current user/group name (see [#629](../../issues/629)) * fixed handling of `use_known_patches=False` (could cause an exception) * removed Python 3.5 from metadata to disable installation for that version (see [#615](../../issues/615)) ### Infrastructure * added test dependency check (see [#608](../../issues/608)) * skip tests failing with ASCII locale (see [#623](../../issues/623)) ## Version 4.5.0 (2021-06-04) Adds some support for Python 3.10 and basic type checking. _Note_: This version has been yanked from PyPI as it erroneously allowed installation under Python 3.5. ### New Features * added support for some Python 3.10 features: * new method `pathlib.Path.hardlink_to` * new `newline` argument in `pathlib.Path.write_text` * new `follow_symlinks` argument in `pathlib.Path.stat` and `pathlib.Path.chmod` * new 'strict' argument in `os.path.realpath` ### Changes * Python 3.5 has reached its end of life in September 2020 and is no longer supported * `pathlib2` is still supported, but considered to have the same functionality as `pathlib` and is no longer tested separately; the previous behavior broke newer `pathlib` features if `pathlib2` was installed (see [#592](../../issues/592)) ### Fixes * correctly handle byte paths in `os.path.exists` (see [#595](../../issues/595)) * Update `fake_pathlib` to support changes coming in Python 3.10 ([see](https://github.com/python/cpython/pull/19342) * correctly handle UNC paths in `os.path.split` and in directory path evaluation (see [#606](../../issues/606)) ### Infrastructure * added mypy checks in CI (see [#599](../../issues/599)) ## [Version 4.4.0](https://pypi.python.org/pypi/pyfakefs/4.4.0) (2021-02-24) Adds better support for Python 3.8 / 3.9. ### New Features * added support for `pathlib.Path.link_to` (new in Python 3.8) (see [#580](../../issues/580)) * added support for `pathlib.Path.readlink` (new in Python 3.9) (see [#584](../../issues/584)) * added `FakeFilesystem.create_link` convenience method which creates intermittent directories (see [#580](../../issues/580)) ### Fixes * fixed handling of pipe descriptors in the fake filesystem (see [#581](../../issues/581)) * added non-functional argument `effective_ids` to `os.access` (see [#585](../../issues/585)) * correctly handle `os.file` for unreadable files (see [#588](../../issues/588)) ### Infrastructure * added automatic documentation build and check-in ## [Version 4.3.3](https://pypi.python.org/pypi/pyfakefs/4.3.3) (2020-12-20) Another bugfix release. ### Fixes * Reverted one Windows-specific optimization that can break tests under some conditions (see [#573](../../issues/573)) * Setting `os` did not reset `os.sep` and related variables, fixed null device name, added `os.pathsep` and missing `os.path` variables (see [#572](../../issues/572)) ## [Version 4.3.2](https://pypi.python.org/pypi/pyfakefs/4.3.2) (2020-11-26) This is a bugfix release that fixes a regression introduced in version 4.2.0. ### Fixes * `open` calls had not been patched for modules with a name ending with "io" (see [#569](../../issues/569)) ## [Version 4.3.1](https://pypi.python.org/pypi/pyfakefs/4.3.1) (2020-11-23) This is an update to the performance release, with more setup caching and the possibility to disable it. ### Changes * Added caching of patched modules to avoid lookup overhead * Added `use_cache` option and `clear_cache` method to be able to deal with unwanted side effects of the newly introduced caching ### Infrastructure * Moved CI builds to GitHub Actions for performance reasons ## [Version 4.3.0](https://pypi.python.org/pypi/pyfakefs/4.3.0) (2020-11-19) This is mostly a performance release. The performance of the pyfakefs setup has been decreasing sufficiently, especially with the 4.x releases. This release corrects that by making the most expansive feature optional, and by adding some other performance improvements. This shall decrease the setup time by about a factor of 20, and it shall now be comparable to the performance of the 3.4 release. ### Changes * The `patchfs` decorator now expects a positional argument instead of the keyword arguments `fs`. This avoids confusion with the pytest `fs` fixture and conforms to the behavior of `mock.patch`. You may have to adapt the argument order if you use the `patchfs` and `mock.patch` decorators together (see [#566](../../issues/566)) * Default arguments that are file system functions are now _not_ patched by default to avoid a large performance impact. An additional parameter `patch_default_args` has been added that switches this behavior on (see [#567](../../issues/567)). ### Performance * Added performance improvements in the test setup, including caching the unpatched modules ## [Version 4.2.1](https://pypi.python.org/pypi/pyfakefs/4.2.1) (2020-11-02) This is a bugfix release that fixes a regression issue. ### Fixes * remove dependency of pyfakefs on `pytest` (regression, see [#565](../../issues/565)) ## [Version 4.2.0](https://pydpi.python.org/pypi/pyfakefs/4.2.0) (2020-11-01) #### New Features * add support for the `buffering` parameter in `open` (see [#549](../../issues/549)) * add possibility to patch `io.open_code` using the new argument `patch_open_code` (since Python 3.8) (see [#554](../../issues/554)) * add possibility to set file system OS via `FakeFilesystem.os` #### Fixes * fix check for link in `os.walk` (see [#559](../../issues/559)) * fix handling of real files in combination with `home` if simulating Posix under Windows (see [#558](../../issues/558)) * do not call fake `open` if called from skipped module (see [#552](../../issues/552)) * do not call fake `pathlib.Path` if called from skipped module (see [#553](../../issues/553)) * fixed handling of `additional_skip_names` with several module components * allow to open existing pipe file descriptor (see [#493](../../issues/493)) * do not truncate file on failed flush (see [#548](../../issues/548)) * suppress deprecation warnings while collecting modules (see [#542](../../issues/542)) * add support for `os.truncate` and `os.ftruncate` (see [#545](../../issues/545)) #### Infrastructure * fixed another problem with CI test scripts not always propagating errors * make sure pytest will work without pyfakefs installed (see [#550](../../issues/550)) ## [Version 4.1.0](https://pypi.python.org/pypi/pyfakefs/4.1.0) (2020-07-12) #### New Features * Added some support for pandas (`read_csv`, `read_excel` and more), and for django file locks to work with the fake filesystem (see [#531](../../issues/531)) #### Fixes * `os.expanduser` now works with a bytes path * Do not override global warnings setting in `Deprecator` (see [#526](../../issues/526)) * Make sure filesystem modules in `pathlib` are patched (see [#527](../../issues/527)) * Make sure that alternative path separators are correctly handled under Windows (see [#530](../../issues/530)) #### Infrastructure * Make sure all temporary files from real fs tests are removed ## [Version 4.0.2](https://pypi.python.org/pypi/pyfakefs/4.0.2) (2020-03-04) This as a patch release that only builds for Python 3. Note that versions 4.0.0 and 4.0.1 will be removed from PyPI to disable installing them under Python 2. #### Fixes * Do not build for Python 2 (see [#524](../../issues/524)) ## Version 4.0.1 (2020-03-03) This as a bug fix release for a regression bug. _Note_: This version has been yanked from PyPI as it erroneously allowed installation under Python 2. This has been fixed in version 4.0.2. #### Fixes * Avoid exception if using `flask-restx` (see [#523](../../issues/523)) ## Version 4.0.0 (2020-03-03) pyfakefs 4.0.0 drops support for Python 2.7. If you still need Python 2.7, you can continue to use pyfakefs 3.7.x. _Note_: This version has been yanked from PyPI as it erroneously allowed installation under Python 2. This has been fixed in version 4.0.2. #### Changes * Removed Python 2.7 and 3.4 support (see [#492](../../issues/492)) #### New Features * Added support for handling keyword-only arguments in some `os` functions * Added possibility to pass additional parameters to `fs` pytest fixture * Added automatic patching of default arguments that are file system functions * Added convenience decorator `patchfs` to patch single functions using the fake filesystem #### Fixes * Added missing `st_ino` in `makedir` (see [#515](../../issues/515)) * Fixed handling of relative paths in `lresolve` / `os.lstat` (see [#516](../../issues/516)) * Fixed handling of byte string paths (see [#517](../../issues/517)) * Fixed `os.walk` if path ends with path separator (see [#512](../../issues/512)) * Fixed handling of empty path in `os.makedirs` (see [#510](../../issues/510)) * Fixed handling of `os.TMPFILE` flag under Linux (see [#509](../../issues/509) and [#511](../../issues/511)) * Adapted fake `pathlib` to changes in Python 3.7.6/3.8.1 (see [#508](../../issues/508)) * Fixed behavior of `os.makedirs` in write-protected directory (see [#507](../../issues/507)) ## [Version 3.7.2](https://pypi.python.org/pypi/pyfakefs/3.7.2) (2020-03-02) This version backports some fixes from main. #### Fixes * Fixed handling of relative paths in `lresolve` / `os.lstat` (see [#516](../../issues/516)) * Fixed `os.walk` if path ends with path separator (see [#512](../../issues/512)) * Fixed handling of empty path in `os.makedirs` (see [#510](../../issues/510)) * Fixed handling of `os.TMPFILE` flag under Linux (see [#509](../../issues/509) and [#511](../../issues/511)) * Fixed behavior of `os.makedirs` in write-protected directory (see [#507](../../issues/507)) ## [Version 3.7.1](https://pypi.python.org/pypi/pyfakefs/3.7.1) (2020-02-14) This version adds support for Python 3.7.6 and 3.8.1. #### Fixes * Adapted fake `pathlib` to changes in Python 3.7.6/3.8.1 (see [#508](../../issues/508)) (backported from main) ## [Version 3.7](https://pypi.python.org/pypi/pyfakefs/3.7) (2019-11-23) This version adds support for Python 3.8. _Note:_ This is the last pyfakefs version that will support Python 2.7 and Python 3.4 (possible bug fix releases notwithstanding). #### New Features * added support for Python 3.8 (see [#504](../../issues/504)) * added preliminary support for Windows-specific `os.stat_result` attributes `tst_file_attributes` and `st_reparse_tag` (see [#504](../../issues/504)) * added support for fake `os.sendfile` (Posix only, Python 3 only) (see [#504](../../issues/504)) #### Fixes * support `devnull` in Windows under Python 3.8 (see [#504](../../issues/504)) * fixed side effect of calling `DirEntry.stat()` under Windows (changed st_nlink) (see [#502](../../issues/502)) * fixed problem of fake modules still referenced after a test in modules loaded during the test (see [#501](../../issues/501) and [#427](../../issues/427)) * correctly handle missing read permission for parent directory (see [#496](../../issues/496)) * raise for `os.scandir` with non-existing directory (see [#498](../../issues/498)) #### Infrastructure * fixed CI tests scripts to always propagate errors (see [#500](../../issues/500)) ## [Version 3.6.1](https://pypi.python.org/pypi/pyfakefs/3.6.1) (2019-10-07) #### Fixes * avoid rare side effect during module iteration in test setup (see [#338](../../issues/338)) * make sure real OS tests are not executed by default (see [#495](../../issues/495)) ## [Version 3.6](https://pypi.python.org/pypi/pyfakefs/3.6) (2019-06-30) #### Changes * removed unneeded parameter `use_dynamic_patch` #### New Features * support for `src_dir_fd` and `dst_dir_fd` arguments in `os.rename`, `os.replace` and `os.link` * added possibility to use modules instead of module names for the `additional_skip_names` argument (see [#482](../../issues/482)) * added argument `allow_root_user` to `Patcher` and `UnitTest` to allow forcing non-root access (see [#474](../../issues/474)) * added basic support for `os.pipe` (see [#473](../../issues/473)) * added support for symlinks in `add_real_directory` * added new public method `add_real_symlink` #### Infrastructure * added check for correctly installed Python 3 version in Travis.CI (see [#487](../../issues/487)) #### Fixes * fixed incorrect argument names for some `os` functions * fake `DirEntry` now implements `os.PathLike` in Python >= 3.6 (see [#483](../../issues/483)) * fixed incorrect argument name for `os.makedirs` (see [#481](../../issues/481)) * avoid pytest warning under Python 2.7 (see [#466](../../issues/466)) * add __next__ to FakeFileWrapper (see [#485](../../issues/485)) ## [Version 3.5.8](https://pypi.python.org/pypi/pyfakefs/3.5.8) (2019-06-21) Another bug-fix release that mainly fixes a regression with Python 2 that has been introduced in version 3.5.3. #### Fixes * regression: patching built-in `open` under Python 2 broke unit tests (see [#469](../../issues/469)) * fixed writing to file added with `add_real_file` (see [#470](../../issues/470)) * fixed argument name of `FakeIOModule.open` (see [#471](../../pull/471)) #### Infrastructure * more changes to run tests using `python setup.py test` under Python 2 regardless of `pathlib2` presence ## [Version 3.5.7](https://pypi.python.org/pypi/pyfakefs/3.5.7) (2019-02-08) This is mostly a bug-fix release. #### Fixes * regression: `pathlib` did not get patched in the presence of `pathlib2` (see [#467](../../issues/467)) * fixed errors if running the PyCharm debugger under Python 2 (see [#464](../../issues/464)) #### Infrastructure * do not run real file system tests by default (fixes deployment problem, see [#465](../../issues/465)) * make tests run if running `python setup.py test` under Python 2 ## [Version 3.5.6](https://pypi.python.org/pypi/pyfakefs/3.5.6) (2019-01-13) #### Changes * import external `pathlib2` and `scandir` packages first if present (see [#462](../../issues/462)) ## [Version 3.5.5](https://pypi.python.org/pypi/pyfakefs/3.5.5) (2018-12-20) #### Fixes * removed shebang from test files to avoid packaging warnings (see [#461](../../issues/461)) ## [Version 3.5.4](https://pypi.python.org/pypi/pyfakefs/3.5.4) (2018-12-19) #### New Features * added context manager class `Pause` for pause/resume (see [#448](../../issues/448)) #### Fixes * fixed `AttributeError` shown while displaying `fs` in a failing pytest in Python 2 * fixed permission handling for root user * avoid `AttributeError` triggered by modules without `__module__` attribute (see [#460](../../issues/460)) ## [Version 3.5.3](https://pypi.python.org/pypi/pyfakefs/3.5.3) (2018-11-22) This is a minor release to have a version with passing tests for OpenSUSE packaging. #### New Features * automatically patch file system methods imported as another name like `from os.path import exists as my_exists`, including builtin `open` and `io.open` #### Fixes * make tests for access time less strict to account for file systems that do not change it immediately ([#453](../../issues/453)) ## [Version 3.5.2](https://pypi.python.org/pypi/pyfakefs/3.5.2) (2018-11-11) This is mostly a bug-fix release. #### New Features * added support for pause/resume of patching the file system modules ([#448](../../issues/448)) * allow to set current group ID, set current user ID and group ID as `st_uid` and `st_gid` in new files ([#449](../../issues/449)) #### Fixes * fixed using `modules_to_patch` (regression, see [#450](../../issues/450)) * fixed recursion error on unpickling the fake file system ([#445](../../issues/445)) * allow trailing path in `add_real_directory` ([#446](../../issues/446)) ## [Version 3.5](https://pypi.python.org/pypi/pyfakefs/3.5) (2018-10-22) #### Changes * This version of pyfakefs does not support Python 3.3. Python 3.3 users must keep using pyfakefs 3.4.3, or upgrade to a newer Python version. * The deprecation warnings for the old API are now switched on by default. To switch them off for legacy code, use: ```python from pyfakefs.deprecator import Deprecator Deprecator.show_warnings = False ``` #### New Features * Improved automatic patching: * automatically patch methods of a patched file system module imported like `from os.path import exists` ([#443](../../pull/443)) * a module imported as another name (`import os as _os`) is now correctly patched without the need of additional parameters ([#434](../../pull/434)) * automatically patch `Path` if imported like `from pathlib import Path` ([#440](../../issues/440)) * parameter `patch_path` has been removed from `UnitTest` and `Patcher`, the correct patching of `path` imports is now done automatically ([#429](../../pull/429)) * `UnitTest` /`Patcher` arguments can now also be set in `setUpPyfakefs()` ([#430](../../pull/430)) * added possibility to set user ID ([#431](../../issues/431)) * added side_effect option to fake files ([#433](../../pull/433)) * added some support for extended filesystem attributes under Linux ([#423](../../issues/423)) * handle `contents=None` in `create_file()` as empty contents if size not set ([#424](../../issues/424)) * added `pathlib2` support ([#408](../../issues/408)) ([#422](../../issues/422)) * added support for null device ([#418](../../issues/418)) * improved error message for "Bad file descriptor in fake filesystem" ([#419](../../issues/419)) #### Fixes * fixed pytest when both pyfakefs and future are installed ([#441](../../issues/441)) * file timestamps are now updated more according to the real behavior ([#435](../../issues/435)) * fixed a problem related to patching `shutil` functions using `zipfile` ([#427](../../issues/427)) ## [Version 3.4.3](https://pypi.python.org/pypi/pyfakefs/3.4.3) (2018-06-13) This is mostly a bug fix release, mainly for bugs found by [@agroce](https://github.com/agroce) using [tstl](https://github.com/agroce/tstl). #### New Features * added support for path-like objects as arguments in `create_file()`, `create_dir()`, `create_symlink()`, `add_real_file()` and `add_real_directory()` (Python >= 3.6, see [#409](../../issues/409)) #### Infrastructure * moved tests into package * use README.md in pypi ([#358](../../issues/358)) #### Fixes * `tell` after `seek` gave incorrect result in append mode ([#363](../../issues/363)) * a failing pytest did not display the test function correctly ([#381](../../issues/381)) * flushing file contents after truncate was incorrect under some conditions ([#412](../../issues/412)) * `readline()` did not work correctly in binary mode ([#411](../../issues/411)) * `pathlib.Path.resolve()` behaved incorrectly if the path does not exist ([#401](../../issues/401)) * `closed` attribute was not implemented in fake file ([#380](../../issues/380)) * `add_real_directory` did not behave correctly for nested paths * the following functions did not behave correctly for paths ending with a path separator (found by @agroce using [tstl](https://github.com/agroce/tstl)): * `os.rename` ([#400](../../issues/400)) * `os.link` ([#399](../../issues/399), [#407](../../issues/407)) * `os.rmdir` ([#398](../../issues/398)) * `os.mkdir`, `os.makedirs` ([#396](../../issues/396)) * `os.rename` ([#391](../../issues/391), [#395](../../issues/395), [#396](../../issues/396), [#389](../../issues/389), [#406](../../issues/406)) * `os.symlink` ([#371](../../issues/371), [#390](../../issues/390)) * `os.path.isdir` ([#387](../../issues/387)) * `open` ([#362](../../issues/362), [#369](../../issues/369), [#397](../../issues/397)) * `os.path.lexists`, `os.path.islink` ([#365](../../issues/365), [#373](../../issues/373), [#396](../../issues/396)) * `os.remove` ([#360](../../issues/360), [#377](../../issues/377), [#396](../../issues/396)) * `os.stat` ([#376](../../issues/376)) * `os.path.isfile` ([#374](../../issues/374)) * `os.path.getsize` ([#368](../../issues/368)) * `os.lstat` ([#366](../../issues/366)) * `os.path.exists` ([#364](../../issues/364)) * `os.readlink` ([#359](../../issues/359), [#372](../../issues/372), [#392](../../issues/392)) ## [Version 3.4.1](https://pypi.python.org/pypi/pyfakefs/3.4.1) (2018-03-18) This is a bug fix only release. #### Fixes * Missing cleanup after using dynamic patcher let to incorrect behavior of `tempfile` after test execution (regression, see [#356](../../issues/356)) * `add_real_directory` does not work after `chdir` (see [#355](../../issues/355)) ## [Version 3.4](https://pypi.python.org/pypi/pyfakefs/3.4) (2018-03-08) This version of pyfakefs does not support Python 2.6. Python 2.6 users must use pyfakefs 3.3 or earlier. #### New Features * Added possibility to map real files or directories to another path in the fake file system (see [#347](../../issues/347)) * Configuration of `Patcher` and `TestCase`: * Possibility to reload modules is now also available in `Patcher` * Added possibility to add own fake modules via `modules_to_patch` argument (see [#345](../../issues/345)) * Dynamic loading of modules after setup is now on by default and no more considered experimental (see [#340](../../issues/340)) * Added support for file descriptor path parameter in `os.scandir` (Python >= 3.7, Posix only) (see [#346](../../issues/346)) * Added support to fake out backported `scandir` module ([#332](../../issues/332)) * `IOError`/`OSError` exception messages in the fake file system now always start with the message issued in the real file system in Unix systems (see [#202](../../issues/202)) #### Infrastructure * Changed API to be PEP-8 conform ([#186](../../issues/186)). Note: The old API is still available. * Removed Python 2.6 support ([#293](../../issues/293)) * Added usage documentation to GitHub Pages * Added contributing guide * Added flake8 tests to Travis CI #### Fixes * Links in base path in `os.scandir` shall not be resolved ([#350](../../issues/350)) * Fixed unit tests when run on a computer not having umask set to 0022 * Correctly handle newline parameter in `open()` for Python 3, added support for universal newline mode in Python 2 ([#339](../../issues/339)) * Fixed handling of case-changing rename with symlink under MacOS ([#322](../../issues/322)) * Creating a file with a path ending with path separator did not raise ([#320](../../issues/320)) * Fixed more problems related to `flush` ([#302](../../issues/302), [#300](../../issues/300)) * Correctly handle opening files more than once ([#343](../../issues/343)) * Fake `os.lstat()` crashed with several trailing path separators ([#342](../../issues/342)) * Fixed handling of path components starting with a drive letter([#337](../../issues/337)) * Symlinks to absolute paths were incorrectly resolved under Windows ([#341](../../issues/341)) * Unittest mock didn't work after setUpPyfakefs ([#334](../../issues/334)) * `os.path.split()` and `os.path.dirname()` gave incorrect results under Windows ([#335](../../issues/335)) ## [Version 3.3](https://pypi.python.org/pypi/pyfakefs/3.3) (2017-11-12) This is the last release that supports Python 2.6. #### New Features * The OS specific temp directory is now automatically created in `setUp()` (related to [#191](../../issues/191)). Note that this may break test code that assumes that the fake file system is completely empty at test start. * Added possibility to reload modules and switch on dynamic loading of modules after setup (experimental, see [#248](../../issues/248)) * Added possibility to patch modules that import file system modules under another name, for example `import os as '_os` ([#231](../../issues/231)) * Added support for `dir_fd` argument in several `os` functions ([#206](../../issues/206)) * Added support for open file descriptor as path argument in `os.utime`, `os.chmod`, `os.chdir`, `os.chown`, `os.listdir`, `os.stat` and `os.lstat` (Python >= 3.3) ([#205](../../issues/205)) * Added support for basic modes in fake `os.open()` ([#204](../../issues/204)) * Added fake `os.path.samefile` implementation ([#193](../../issues/193)) * Added support for `ns` argument in `os.utime()` (Python >= 3.3) ([#192](../../issues/192)) * Added nanosecond time members in `os.stat_result` (Python >= 3.3) ([#196](../../issues/196)) #### Infrastructure * Added Travis CI tests for MacOSX (Python 2.7 and 3.6) * Added Appveyor CI tests for Windows (Python 2.7, 3.3 and 3.6) * Added auto-generated documentation for development version on GitHub Pages * Removed most of `fake_filesystem_shutil` implementation, relying on the patched `os` module instead ([#194](../../issues/194)) * Removed `fake_tempfile` and `fake_filesystem_glob`, relying on the patched `os` module instead ([#189](../../issues/189), [#191](../../issues/191)) #### Fixes * Multiple fixes of bugs found using TSTL by @agroce (see about 100 issues with the `TSTL` label) * several problems with buffer handling in high-level IO functions * several problems with multiple handles on the same file * several problems with low-level IO functions * incorrect exception (`IOError` vs `OSError`) raised in several cases * Fake `rename` did not behave like `os.rename` in many cases * Symlinks have not been considered or incorrectly handled in several functions * A nonexistent file that has the same name as the content of the parent object was seen as existing * Incorrect error handling during directory creation * many fixes for OS-specific behavior * Also patch modules that are loaded between `__init__()` and `setUp()` ([#199](../../issues/199)) * Creating files in read-only directory was possible ([#203](../../issues/203)) ## [Version 3.2](https://pypi.python.org/pypi/pyfakefs/3.2) (2017-05-27) #### New Features * The `errors` argument is supported for `io.open()` and `os.open()` * New methods `add_real_file()`, `add_real_directory()` and `add_real_paths()` make real files and directories appear within the fake file system. File contents are read from the real file system only as needed ([#170](../../issues/170)). See `example_test.py` for a usage example. * Deprecated `TestCase.copyRealFile()` in favor of `add_real_file()`. `copyRealFile()` remains only for backward compatibility. Also, some less-popular argument combinations have been disallowed. * Added this file you are reading, `CHANGES.md`, to the release manifest #### Infrastructure * The `mox3` package is no longer a prerequisite--the portion required by pyfakefs has been integrated into pyfakefs ([#182](../../issues/182)) #### Fixes * Corrected the handling of byte/unicode paths in several functions ([#187](../../issues/187)) * `FakeShutilModule.rmtree()` failed for directories ending with path separator ([#177](../../issues/177)) * Case was incorrectly handled for added Windows drives * `pathlib.glob()` incorrectly handled case under MacOS ([#167](../../issues/167)) * tox support was broken ([#163](../../issues/163)) * On Windows it was not possible to rename a file when only the case of the file name changed ([#160](../../issues/160)) ## [Version 3.1](https://pypi.python.org/pypi/pyfakefs/3.1) (2017-02-11) #### New Features * Added helper method `TestCase.copyRealFile()` to copy a file from the real file system to the fake file system. This makes it easy to use template, data and configuration files in your tests. * A pytest plugin is now installed with pyfakefs that exports the fake filesystem as pytest fixture `fs`. #### Fixes * Incorrect disk usage calculation if too large file created ([#155](../../issues/155)) ## [Version 3.0](https://pypi.python.org/pypi/pyfakefs/3.0) (2017-01-18) #### New Features * Support for path-like objects as arguments in fake `os` and `os.path` modules (Python >= 3.6) * Some changes to make pyfakefs work with Python 3.6 * Added fake `pathlib` module (Python >= 3.4) ([#29](../../issues/29)) * Support for `os.replace` (Python >= 3.3) * `os.access`, `os.chmod`, `os.chown`, `os.stat`, `os.utime`: support for `follow_symlinks` argument (Python >= 3.3) * Support for `os.scandir` (Python >= 3.5) ([#119](../../issues/119)) * Option to not fake modules named `path` ([#53](../../issues/53)) * `glob.glob`, `glob.iglob`: support for `recursive` argument (Python >= 3.5) ([#116](../../issues/116)) * Support for `glob.iglob` ([#59](../../issues/59)) #### Infrastructure * Added [auto-generated documentation](http://pytest-dev.github.io/pyfakefs/) #### Fixes * `shutil.move` incorrectly moves directories ([#145](../../issues/145)) * Missing support for 'x' mode in `open` (Python >= 3.3) ([#147](../../issues/147)) * Incorrect exception type in Posix if path ancestor is a file ([#139](../../issues/139)) * Exception handling when using `Patcher` with py.test ([#135](../../issues/135)) * Fake `os.listdir` returned sorted instead of unsorted entries ## [Version 2.9](https://pypi.python.org/pypi/pyfakefs/2.9) (2016-10-02) #### New Features * `io.open`, `os.open`: support for `encoding` argument ([#120](../../issues/120)) * `os.makedirs`: support for `exist_ok` argument (Python >= 3.2) ([#98](../../issues/98)) * Support for fake `io.open()` ([#70](../../issues/70)) * Support for mount points ([#25](../../issues/25)) * Support for hard links ([#75](../../issues/75)) * Support for float times (mtime, ctime) * Windows support: * support for alternative path separator * support for case-insensitive filesystems ([#69](../../issues/69)) * support for drive letters and UNC paths * Support for filesystem size ([#86](../../issues/86)) * `shutil.rmtree`: support for `ignore_errors` and `onerror` arguments ([#72](../../issues/72)) * Support for `os.fsync()` and `os.fdatasync()` ([#73](../../issues/73)) * `os.walk`: Support for `followlinks` argument #### Fixes * `shutil` functions like `make_archive` do not work with pyfakefs ([#104](../../issues/104)) * File permissions on deletion not correctly handled ([#27](../../issues/27)) * `shutil.copy` error with bytes contents ([#105](../../issues/105)) * mtime and ctime not updated on content changes ## [Version 2.7](https://pypi.python.org/pypi/pyfakefs/2.7) #### Infrastructure * Moved repository from GoogleCode to GitHub, merging 3 projects * Added continuous integration testing with Travis CI * Added usage documentation in project wiki * Better support for pypi releases #### New Features * Added direct unit test support in `fake_filesystem_unittest` (transparently patches all calls to faked implementations) * Added support for doctests * Added support for cygwin * Better support for Python 3 #### Fixes * `os.utime` fails to traverse symlinks ([#49](../../issues/49)) * `chown` incorrectly accepts non-integer uid/gid arguments ([#30](../../issues/30)) * Reading from fake block devices doesn't work ([#24](../../issues/24)) * `fake_tempfile` is using `AddOpenFile` incorrectly ([#23](../../issues/23)) * Incorrect behavior of `relpath`, `abspath` and `normpath` on Windows. * Cygwin wasn't treated as Windows ([#37](../../issues/37)) * Python 3 `open` in binary mode not working ([#32](../../issues/32)) * `os.remove` doesn't work with relative paths ([#31](../../issues/31)) * `mkstemp` returns no valid file descriptor ([#19](../../issues/19)) * `open` methods lack `IOError` for prohibited operations ([#18](../../issues/18)) * Incorrectly resolved relative path ([#3](../../issues/3)) * `FakeFileOpen` keyword args do not match the `__builtin__` equivalents ([#5](../../issues/5)) * Relative paths not supported ([#16](../../issues/16), [#17](../../issues/17))) ## Older Versions There are no release notes for releases 2.6 and below. The following versions are still available on PyPI: * [1.1](https://pypi.python.org/pypi/pyfakefs/1.1), [1.2](https://pypi.python.org/pypi/pyfakefs/1.2), [2.0](https://pypi.python.org/pypi/pyfakefs/2.0), [2.1](https://pypi.python.org/pypi/pyfakefs/2.1), [2.2](https://pypi.python.org/pypi/pyfakefs/2.2), [2.3](https://pypi.python.org/pypi/pyfakefs/2.3) and [2.4](https://pypi.python.org/pypi/pyfakefs/2.4) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/CONTRIBUTING.md0000644000175100001660000000651314764107375015213 0ustar00runnerdocker # Contributing to pyfakefs We welcome any contributions that help to improve pyfakefs for the community. Contributions may include bug reports, bug fixes, new features, infrastructure enhancements, or documentation updates. ## How to contribute ### Reporting Bugs If you think you found a bug in pyfakefs, you can [create an issue](https://help.github.com/articles/creating-an-issue/). Before filing the bug, please check, if it still exists in the [main branch](https://github.com/pytest-dev/pyfakefs). If you can reproduce the problem, please provide enough information so that it can be reproduced by other developers. This includes: * The Operating System * The Python version * A minimal example to reproduce the problem (preferably in the form of a failing test) * The stack trace in case of an unexpected exception. For better readability, you may use [markdown code formatting](https://help.github.com/articles/creating-and-highlighting-code-blocks/) for any included code. ### Proposing Enhancements If you need a specific feature that is not implemented, or have an idea for the next exciting gimmick in pyfakefs, you can also create a respective issue. Of course - implementing it yourself is the best chance to get it done! The next item has some information on doing this. ### Contributing Code The preferred workflow for contributing code is to [fork](https://help.github.com/articles/fork-a-repo/) the [repository](https://github.com/pytest-dev/pyfakefs) on GitHub, clone it, develop on a feature branch, and [create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork) when done. There are a few things to consider for contributing code: * We ensure the [PEP-8 coding style](https://www.python.org/dev/peps/pep-0008/) by using [black](https://pypi.org/project/black/) auto-format in a pre-commit hook; you can locally install [pre-commit](https://pypi.org/project/pre-commit/) to run the linter tests on check-in or on demand (`pre-commit run --all-files`) * Use the [Google documentation style](https://google.github.io/styleguide/pyguide.html) to document new public classes or methods * Provide unit tests for bug fixes or new functionality - check the existing tests for examples * Provide meaningful commit messages - it is ok to amend the commits to improve the comments * Check that the automatic GitHub Action CI tests all pass for your pull request * Be ready to adapt your changes after a code review ### Contributing Documentation If you want to improve the existing documentation, you can do this also using a pull request. You can contribute to: * the source code documentation using [Google documentation style](https://google.github.io/styleguide/pyguide.html) * the [README](https://github.com/pytest-dev/pyfakefs/blob/main/README.md) using [markdown syntax](https://help.github.com/articles/basic-writing-and-formatting-syntax/) * the documentation published on [Read the Docs](https://pytest-pyfakefs.readthedocs.io/en/latest/), located in the `docs` directory (call `make html` from that directory). For building the documentation, you will need [sphinx](http://sphinx.pocoo.org/). * [this file](https://github.com/pytest-dev/pyfakefs/blob/main/CONTRIBUTING.md) if you want to enhance the contributing guide itself Thanks for taking the time to contribute to pyfakefs! ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/COPYING0000644000175100001660000002363614764107375014022 0ustar00runnerdocker Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/MANIFEST.in0000755000175100001660000000007114764107375014514 0ustar00runnerdockerinclude COPYING include *.md include *.ini include *.txt ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1741721344.9685268 pyfakefs-5.8.0/PKG-INFO0000644000175100001660000001713114764107401014043 0ustar00runnerdockerMetadata-Version: 2.2 Name: pyfakefs Version: 5.8.0 Summary: pyfakefs implements a fake file system that mocks the Python file system modules. Home-page: https://github.com/pytest-dev/pyfakefs Author: Google Author-email: google-pyfakefs@google.com Maintainer: John McGehee Maintainer-email: pyfakefs@johnnado.com License: http://www.apache.org/licenses/LICENSE-2.0 Keywords: testing,test,file,os,shutil,pathlib,mocking,unittest,pytest,fakes,filesystem Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Operating System :: POSIX Classifier: Operating System :: MacOS Classifier: Operating System :: Microsoft :: Windows Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Testing Classifier: Topic :: System :: Filesystems Classifier: Framework :: Pytest Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: COPYING # pyfakefs [![PyPI version](https://badge.fury.io/py/pyfakefs.svg)](https://badge.fury.io/py/pyfakefs) [![Python version](https://img.shields.io/pypi/pyversions/pyfakefs.svg)](https://img.shields.io/pypi/pyversions/pyfakefs.svg) ![Testsuite](https://github.com/pytest-dev/pyfakefs/workflows/Testsuite/badge.svg) [![Documentation Status](https://readthedocs.org/projects/pytest-pyfakefs/badge/?version=latest)](https://pytest-pyfakefs.readthedocs.io/en/latest/?badge=latest) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/pytest-dev/pyfakefs/main.svg)](https://results.pre-commit.ci/latest/github/pytest-dev/pyfakefs/main) pyfakefs implements a fake file system that mocks the Python file system modules. Using pyfakefs, your tests operate on a fake file system in memory without touching the real disk. The software under test requires no modification to work with pyfakefs. Pyfakefs creates a new empty in-memory file system at each test start, which replaces the real filesystem during the test. Think of pyfakefs as making a per-test temporary directory, except for an entire file system. There are several means to achieve this: by using the `fs` fixture if running pytest, by using `fake_filesystem_unittest.TestCase` as a base class if using unittest, by using a `fake_filesystem_unittest.Patcher` instance as a context manager, or by using the `patchfs` decorator. pyfakefs works with current versions of Linux, Windows and macOS. ## Documentation This document provides a general overview for pyfakefs. There is more: * The documentation at **Read the Docs**: * The [Release documentation](https://pytest-pyfakefs.readthedocs.io/en/stable) contains usage documentation for pyfakefs and a description of the most relevant classes, methods and functions for the last version released on PyPI * The [Development documentation](https://pytest-pyfakefs.readthedocs.io/en/latest) contains the same documentation for the current main branch * The [Release 3.7 documentation](https://pytest-pyfakefs.readthedocs.io/en/v3.7.2/) contains usage documentation for the last version of pyfakefs supporting Python 2.7 * The [Release Notes](https://github.com/pytest-dev/pyfakefs/blob/main/CHANGES.md) show a list of changes in the latest versions ## Usage The simplest method to use pyfakefs is using the `fs` fixture with `pytest`. Refer to the [usage documentation](https://pytest-pyfakefs.readthedocs.io/en/latest/usage.html) for information on other test scenarios, test customization and using convenience functions. ## Features Apart from automatically mocking most file-system functions, pyfakefs provides some additional features: - mapping files and directories from the real file system into the fake filesystem - configuration and tracking of the file system size - pause and resume of patching to be able to use the real file system inside a test step - (limited) emulation of other OSes (Linux, macOS or Windows) - configuration to behave as if running as a non-root user while running under root ## Compatibility pyfakefs works with CPython 3.7 and above, on Linux, Windows and macOS, and with PyPy3. pyfakefs works with [pytest](http://doc.pytest.org) version 6.2.5 or above, though a current version is recommended. pyfakefs will not work with Python libraries that use C libraries to access the file system. This is because pyfakefs cannot patch the underlying C libraries' file access functions--the C libraries will always access the real file system. Refer to the [documentation](https://pytest-pyfakefs.readthedocs.io/en/latest/intro.html#limitations) for more information about the limitations of pyfakefs. ## Development ### Continuous integration pyfakefs is currently automatically tested on Linux, macOS and Windows, with Python 3.7 to 3.13, and with PyPy3 on Linux, using [GitHub Actions](https://github.com/pytest-dev/pyfakefs/actions). ### Running pyfakefs unit tests #### On the command line pyfakefs unit tests can be run using `pytest` (all tests) or `unittest` (all tests except `pytest`-specific ones): ```bash $ cd pyfakefs/ $ export PYTHONPATH=$PWD $ python -m pytest pyfakefs $ python -m pyfakefs.tests.all_tests ``` Similar scripts are called by `tox` and Github Actions. `tox` can be used to run tests locally against supported python versions: ```bash $ tox ``` #### In a Docker container The `Dockerfile` at the repository root will run the tests on the latest Ubuntu version. Build the container: ```bash cd pyfakefs/ docker build -t pyfakefs . ``` Run the unit tests in the container: ```bash docker run -t pyfakefs ``` ### Contributing to pyfakefs We always welcome contributions to the library. Check out the [Contributing Guide](https://github.com/pytest-dev/pyfakefs/blob/main/CONTRIBUTING.md) for more information. ## History pyfakefs.py was initially developed at Google by Mike Bland as a modest fake implementation of core Python modules. It was introduced to all of Google in September 2006. Since then, it has been enhanced to extend its functionality and usefulness. At last count, pyfakefs was used in over 20,000 Python tests at Google. Google released pyfakefs to the public in 2011 as Google Code project [pyfakefs](http://code.google.com/p/pyfakefs/): * Fork [jmcgeheeiv-pyfakefs](http://code.google.com/p/jmcgeheeiv-pyfakefs/) added [direct support for unittest and doctest](../../wiki/Automatically-find-and-patch-file-functions-and-modules) * Fork [shiffdane-jmcgeheeiv-pyfakefs](http://code.google.com/p/shiffdane-jmcgeheeiv-pyfakefs/) added further corrections After the [shutdown of Google Code](http://google-opensource.blogspot.com/2015/03/farewell-to-google-code.html) was announced, [John McGehee](https://github.com/jmcgeheeiv) merged all three Google Code projects together [here on GitHub](https://github.com/pytest-dev/pyfakefs) where an enthusiastic community actively supports, maintains and extends pyfakefs. In 2022, the repository has been transferred to [pytest-dev](https://github.com/pytest-dev) to ensure continuous maintenance. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/README.md0000644000175100001660000001373014764107375014240 0ustar00runnerdocker# pyfakefs [![PyPI version](https://badge.fury.io/py/pyfakefs.svg)](https://badge.fury.io/py/pyfakefs) [![Python version](https://img.shields.io/pypi/pyversions/pyfakefs.svg)](https://img.shields.io/pypi/pyversions/pyfakefs.svg) ![Testsuite](https://github.com/pytest-dev/pyfakefs/workflows/Testsuite/badge.svg) [![Documentation Status](https://readthedocs.org/projects/pytest-pyfakefs/badge/?version=latest)](https://pytest-pyfakefs.readthedocs.io/en/latest/?badge=latest) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/pytest-dev/pyfakefs/main.svg)](https://results.pre-commit.ci/latest/github/pytest-dev/pyfakefs/main) pyfakefs implements a fake file system that mocks the Python file system modules. Using pyfakefs, your tests operate on a fake file system in memory without touching the real disk. The software under test requires no modification to work with pyfakefs. Pyfakefs creates a new empty in-memory file system at each test start, which replaces the real filesystem during the test. Think of pyfakefs as making a per-test temporary directory, except for an entire file system. There are several means to achieve this: by using the `fs` fixture if running pytest, by using `fake_filesystem_unittest.TestCase` as a base class if using unittest, by using a `fake_filesystem_unittest.Patcher` instance as a context manager, or by using the `patchfs` decorator. pyfakefs works with current versions of Linux, Windows and macOS. ## Documentation This document provides a general overview for pyfakefs. There is more: * The documentation at **Read the Docs**: * The [Release documentation](https://pytest-pyfakefs.readthedocs.io/en/stable) contains usage documentation for pyfakefs and a description of the most relevant classes, methods and functions for the last version released on PyPI * The [Development documentation](https://pytest-pyfakefs.readthedocs.io/en/latest) contains the same documentation for the current main branch * The [Release 3.7 documentation](https://pytest-pyfakefs.readthedocs.io/en/v3.7.2/) contains usage documentation for the last version of pyfakefs supporting Python 2.7 * The [Release Notes](https://github.com/pytest-dev/pyfakefs/blob/main/CHANGES.md) show a list of changes in the latest versions ## Usage The simplest method to use pyfakefs is using the `fs` fixture with `pytest`. Refer to the [usage documentation](https://pytest-pyfakefs.readthedocs.io/en/latest/usage.html) for information on other test scenarios, test customization and using convenience functions. ## Features Apart from automatically mocking most file-system functions, pyfakefs provides some additional features: - mapping files and directories from the real file system into the fake filesystem - configuration and tracking of the file system size - pause and resume of patching to be able to use the real file system inside a test step - (limited) emulation of other OSes (Linux, macOS or Windows) - configuration to behave as if running as a non-root user while running under root ## Compatibility pyfakefs works with CPython 3.7 and above, on Linux, Windows and macOS, and with PyPy3. pyfakefs works with [pytest](http://doc.pytest.org) version 6.2.5 or above, though a current version is recommended. pyfakefs will not work with Python libraries that use C libraries to access the file system. This is because pyfakefs cannot patch the underlying C libraries' file access functions--the C libraries will always access the real file system. Refer to the [documentation](https://pytest-pyfakefs.readthedocs.io/en/latest/intro.html#limitations) for more information about the limitations of pyfakefs. ## Development ### Continuous integration pyfakefs is currently automatically tested on Linux, macOS and Windows, with Python 3.7 to 3.13, and with PyPy3 on Linux, using [GitHub Actions](https://github.com/pytest-dev/pyfakefs/actions). ### Running pyfakefs unit tests #### On the command line pyfakefs unit tests can be run using `pytest` (all tests) or `unittest` (all tests except `pytest`-specific ones): ```bash $ cd pyfakefs/ $ export PYTHONPATH=$PWD $ python -m pytest pyfakefs $ python -m pyfakefs.tests.all_tests ``` Similar scripts are called by `tox` and Github Actions. `tox` can be used to run tests locally against supported python versions: ```bash $ tox ``` #### In a Docker container The `Dockerfile` at the repository root will run the tests on the latest Ubuntu version. Build the container: ```bash cd pyfakefs/ docker build -t pyfakefs . ``` Run the unit tests in the container: ```bash docker run -t pyfakefs ``` ### Contributing to pyfakefs We always welcome contributions to the library. Check out the [Contributing Guide](https://github.com/pytest-dev/pyfakefs/blob/main/CONTRIBUTING.md) for more information. ## History pyfakefs.py was initially developed at Google by Mike Bland as a modest fake implementation of core Python modules. It was introduced to all of Google in September 2006. Since then, it has been enhanced to extend its functionality and usefulness. At last count, pyfakefs was used in over 20,000 Python tests at Google. Google released pyfakefs to the public in 2011 as Google Code project [pyfakefs](http://code.google.com/p/pyfakefs/): * Fork [jmcgeheeiv-pyfakefs](http://code.google.com/p/jmcgeheeiv-pyfakefs/) added [direct support for unittest and doctest](../../wiki/Automatically-find-and-patch-file-functions-and-modules) * Fork [shiffdane-jmcgeheeiv-pyfakefs](http://code.google.com/p/shiffdane-jmcgeheeiv-pyfakefs/) added further corrections After the [shutdown of Google Code](http://google-opensource.blogspot.com/2015/03/farewell-to-google-code.html) was announced, [John McGehee](https://github.com/jmcgeheeiv) merged all three Google Code projects together [here on GitHub](https://github.com/pytest-dev/pyfakefs) where an enthusiastic community actively supports, maintains and extends pyfakefs. In 2022, the repository has been transferred to [pytest-dev](https://github.com/pytest-dev) to ensure continuous maintenance. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/extra_requirements.txt0000644000175100001660000000063114764107375017444 0ustar00runnerdocker# these are used to test pandas-specific patches to allow # pyfakefs to work with pandas # we use the latest version to see any problems with new versions pandas==1.3.5; python_version == '3.7' # pyup: ignore pandas==2.0.3; python_version == '3.8' # pyup: ignore pandas==2.2.3; python_version > '3.8' xlrd==2.0.1 openpyxl==3.1.3; python_version == '3.7' # pyup: ignore openpyxl==3.1.5; python_version > '3.7' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/legacy_requirements.txt0000644000175100001660000000066414764107375017573 0ustar00runnerdocker# "pathlib2" and "scandir" are backports of new standard modules, pyfakefs will # patch them if available when running on older Python versions. # # The modules are no longer for all required Python version, and only used for CI tests. # Note that the usage of these modules is deprecated, and their support # will be removed in pyfakefs 6.0 pathlib2>=2.3.2 scandir>=1.8; python_version < '3.13' # not (yet) available for Python 3.13 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/mypy.ini0000644000175100001660000000100614764107375014451 0ustar00runnerdocker[mypy] show_error_codes = True warn_unused_configs = True exclude=(docs|pyfakefs/tests) [mypy-django.*] ignore_missing_imports = True [mypy-hotshot.*] ignore_missing_imports = True [mypy-openpyxl.*] ignore_missing_imports = True [mypy-pandas.*] ignore_missing_imports = True [mypy-pathlib2.*] ignore_missing_imports = True [mypy-psyco.*] ignore_missing_imports = True [mypy-scandir.*] ignore_missing_imports = True [mypy-setuptools.*] ignore_missing_imports = True [mypy-xlrd.*] ignore_missing_imports = True ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1741721344.9585268 pyfakefs-5.8.0/pyfakefs/0000755000175100001660000000000014764107401014553 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/__init__.py0000755000175100001660000000007014764107375016676 0ustar00runnerdockerfrom pyfakefs._version import __version__ # noqa: F401 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/_version.py0000644000175100001660000000002614764107375016761 0ustar00runnerdocker__version__ = "5.8.0" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/fake_file.py0000644000175100001660000014210714764107375017051 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Fake implementations for different file objects.""" import errno import io import os import sys import traceback from stat import ( S_IFREG, S_IFDIR, ) from types import TracebackType from typing import ( List, Optional, Callable, Union, Any, Dict, cast, AnyStr, NoReturn, Iterator, TextIO, Type, TYPE_CHECKING, ) from pyfakefs import helpers from pyfakefs.helpers import ( FakeStatResult, BinaryBufferIO, TextBufferIO, is_int_type, is_unicode_string, to_string, matching_string, real_encoding, AnyPath, AnyString, get_locale_encoding, _OpenModes, is_root, ) if TYPE_CHECKING: from pyfakefs.fake_filesystem import FakeFilesystem # Work around pyupgrade auto-rewriting `io.open()` to `open()`. io_open = io.open AnyFileWrapper = Union[ "FakeFileWrapper", "FakeDirWrapper", "StandardStreamWrapper", "FakePipeWrapper", ] AnyFile = Union["FakeFile", "FakeDirectory"] class FakeLargeFileIoException(Exception): """Exception thrown on unsupported operations for fake large files. Fake large files have a size with no real content. """ def __init__(self, file_path: str) -> None: super().__init__( "Read and write operations not supported for " "fake large file: %s" % file_path ) class FakeFile: """Provides the appearance of a real file. Attributes currently faked out: * `st_mode`: user-specified, otherwise S_IFREG * `st_ctime`: the time.time() timestamp of the file change time (updated each time a file's attributes is modified). * `st_atime`: the time.time() timestamp when the file was last accessed. * `st_mtime`: the time.time() timestamp when the file was last modified. * `st_size`: the size of the file * `st_nlink`: the number of hard links to the file * `st_ino`: the inode number - a unique number identifying the file * `st_dev`: a unique number identifying the (fake) file system device the file belongs to * `st_uid`: always set to USER_ID, which can be changed globally using `set_uid` * `st_gid`: always set to GROUP_ID, which can be changed globally using `set_gid` .. note:: The resolution for `st_ctime`, `st_mtime` and `st_atime` in the real file system depends on the used file system (for example it is only 1s for HFS+ and older Linux file systems, but much higher for ext4 and NTFS). This is currently ignored by pyfakefs, which uses the resolution of `time.time()`. Under Windows, `st_atime` is not updated for performance reasons by default. pyfakefs never updates `st_atime` under Windows, assuming the default setting. """ stat_types = ( "st_mode", "st_ino", "st_dev", "st_nlink", "st_uid", "st_gid", "st_size", "st_atime", "st_mtime", "st_ctime", "st_atime_ns", "st_mtime_ns", "st_ctime_ns", ) def __init__( self, name: AnyStr, st_mode: int = S_IFREG | helpers.PERM_DEF_FILE, contents: Optional[AnyStr] = None, filesystem: Optional["FakeFilesystem"] = None, encoding: Optional[str] = None, errors: Optional[str] = None, side_effect: Optional[Callable[["FakeFile"], None]] = None, open_modes: Optional[_OpenModes] = None, ): """ Args: name: Name of the file/directory, without parent path information st_mode: The stat.S_IF* constant representing the file type (i.e. stat.S_IFREG, stat.S_IFDIR), and the file permissions. If no file type is set (e.g. permission flags only), a regular file type is assumed. contents: The contents of the filesystem object; should be a string or byte object for regular files, and a dict of other FakeFile or FakeDirectory objects with the file names as keys for FakeDirectory objects filesystem: The fake filesystem where the file is created. encoding: If contents is a unicode string, the encoding used for serialization. errors: The error mode used for encoding/decoding errors. side_effect: function handle that is executed when file is written, must accept the file object as an argument. open_modes: The modes the file was opened with (e.g. can read, write etc.) """ # to be backwards compatible regarding argument order, we raise on None if filesystem is None: raise ValueError("filesystem shall not be None") self.filesystem: "FakeFilesystem" = filesystem self._side_effect: Optional[Callable] = side_effect self.name: AnyStr = name # type: ignore[assignment] self.stat_result = FakeStatResult( filesystem.is_windows_fs, helpers.get_uid(), helpers.get_gid(), helpers.now(), ) if st_mode >> 12 == 0: st_mode |= S_IFREG self.stat_result.st_mode = st_mode self.st_size: int = 0 self.encoding: Optional[str] = real_encoding(encoding) self.errors: str = errors or "strict" self._byte_contents: Optional[bytes] = self._encode_contents(contents) self.stat_result.st_size = ( len(self._byte_contents) if self._byte_contents is not None else 0 ) self.epoch: int = 0 self.parent_dir: Optional[FakeDirectory] = None # Linux specific: extended file system attributes self.xattr: Dict = {} self.opened_as: AnyString = "" self.open_modes = open_modes @property def byte_contents(self) -> Optional[bytes]: """Return the contents as raw byte array.""" return self._byte_contents @property def contents(self) -> Optional[str]: """Return the contents as string with the original encoding.""" if isinstance(self.byte_contents, bytes): return self.byte_contents.decode( self.encoding or get_locale_encoding(), errors=self.errors, ) return None @property def st_ctime(self) -> float: """Return the creation time of the fake file.""" return self.stat_result.st_ctime @st_ctime.setter def st_ctime(self, val: float) -> None: """Set the creation time of the fake file.""" self.stat_result.st_ctime = val @property def st_atime(self) -> float: """Return the access time of the fake file.""" return self.stat_result.st_atime @st_atime.setter def st_atime(self, val: float) -> None: """Set the access time of the fake file.""" self.stat_result.st_atime = val @property def st_mtime(self) -> float: """Return the modification time of the fake file.""" return self.stat_result.st_mtime @st_mtime.setter def st_mtime(self, val: float) -> None: """Set the modification time of the fake file.""" self.stat_result.st_mtime = val def set_large_file_size(self, st_size: int) -> None: """Sets the self.st_size attribute and replaces self.content with None. Provided specifically to simulate very large files without regards to their content (which wouldn't fit in memory). Note that read/write operations with such a file raise :py:class:`FakeLargeFileIoException`. Args: st_size: (int) The desired file size Raises: OSError: if the st_size is not a non-negative integer, or if st_size exceeds the available file system space """ self._check_positive_int(st_size) if self.st_size: self.size = 0 if self.filesystem: self.filesystem.change_disk_usage(st_size, self.name, self.st_dev) self.st_size = st_size self._byte_contents = None def _check_positive_int(self, size: int) -> None: # the size should be an positive integer value if not is_int_type(size) or size < 0: self.filesystem.raise_os_error(errno.ENOSPC, self.name) def is_large_file(self) -> bool: """Return `True` if this file was initialized with size but no contents. """ return self._byte_contents is None def _encode_contents(self, contents: Union[str, bytes, None]) -> Optional[bytes]: if is_unicode_string(contents): contents = bytes( cast(str, contents), self.encoding or get_locale_encoding(), self.errors, ) return cast(bytes, contents) def set_initial_contents(self, contents: AnyStr) -> bool: """Sets the file contents and size. Called internally after initial file creation. Args: contents: string, new content of file. Returns: True if the contents have been changed. Raises: OSError: if the st_size is not a non-negative integer, or if st_size exceeds the available file system space """ byte_contents = self._encode_contents(contents) changed = self._byte_contents != byte_contents st_size = len(byte_contents) if byte_contents else 0 current_size = self.st_size or 0 self.filesystem.change_disk_usage( st_size - current_size, self.name, self.st_dev ) self._byte_contents = byte_contents self.st_size = st_size self.epoch += 1 return changed def set_contents(self, contents: AnyStr, encoding: Optional[str] = None) -> bool: """Sets the file contents and size and increases the modification time. Also executes the side_effects if available. Args: contents: (str, bytes) new content of file. encoding: (str) the encoding to be used for writing the contents if they are a unicode string. If not given, the locale preferred encoding is used. Returns: True if the contents have been changed. Raises: OSError: if `st_size` is not a non-negative integer, or if it exceeds the available file system space. """ self.encoding = real_encoding(encoding) changed = self.set_initial_contents(contents) if self._side_effect is not None: self._side_effect(self) return changed @property def size(self) -> int: """Return the size in bytes of the file contents.""" return self.st_size @size.setter def size(self, st_size: int) -> None: """Resizes file content, padding with nulls if new size exceeds the old size. Args: st_size: The desired size for the file. Raises: OSError: if the st_size arg is not a non-negative integer or if st_size exceeds the available file system space """ self._check_positive_int(st_size) current_size = self.st_size or 0 self.filesystem.change_disk_usage( st_size - current_size, self.name, self.st_dev ) if self._byte_contents: if st_size < current_size: self._byte_contents = self._byte_contents[:st_size] else: self._byte_contents += b"\0" * (st_size - current_size) self.st_size = st_size self.epoch += 1 @property def path(self) -> AnyStr: # type: ignore[type-var] """Return the full path of the current object.""" names: List[AnyStr] = [] # pytype: disable=invalid-annotation obj: Optional[FakeFile] = self while obj: names.insert(0, matching_string(self.name, obj.name)) # type: ignore obj = obj.parent_dir sep = self.filesystem.get_path_separator(names[0]) if names[0] == sep: names.pop(0) dir_path = sep.join(names) drive = self.filesystem.splitdrive(dir_path)[0] # if a Windows path already starts with a drive or UNC path, # no extra separator is needed if not drive: dir_path = sep + dir_path else: dir_path = sep.join(names) return self.filesystem.absnormpath(dir_path) if sys.version_info >= (3, 12): @property def is_junction(self) -> bool: return self.filesystem.isjunction(self.path) def __getattr__(self, item: str) -> Any: """Forward some properties to stat_result.""" if item in self.stat_types: return getattr(self.stat_result, item) return super().__getattribute__(item) def __setattr__(self, key: str, value: Any) -> None: """Forward some properties to stat_result.""" if key in self.stat_types: return setattr(self.stat_result, key, value) return super().__setattr__(key, value) def __str__(self) -> str: return f"{self.name!r}({self.st_mode:o})" def has_permission(self, permission_bits: int) -> bool: """Checks if the given permissions are set in the fake file. Args: permission_bits: The permission bits as set for the user. Returns: True if the permissions are set in the correct class (user/group/other). """ if helpers.get_uid() == self.stat_result.st_uid: return self.st_mode & permission_bits == permission_bits if helpers.get_gid() == self.stat_result.st_gid: return self.st_mode & (permission_bits >> 3) == permission_bits >> 3 return self.st_mode & (permission_bits >> 6) == permission_bits >> 6 class FakeNullFile(FakeFile): def __init__(self, filesystem: "FakeFilesystem") -> None: super().__init__(filesystem.devnull, filesystem=filesystem, contents="") @property def byte_contents(self) -> bytes: return b"" def set_initial_contents(self, contents: AnyStr) -> bool: return False class FakeFileFromRealFile(FakeFile): """Represents a fake file copied from the real file system. The contents of the file are read on demand only. """ def __init__( self, file_path: str, filesystem: "FakeFilesystem", side_effect: Optional[Callable] = None, ) -> None: """ Args: file_path: Path to the existing file. filesystem: The fake filesystem where the file is created. Raises: OSError: if the file does not exist in the real file system. OSError: if the file already exists in the fake file system. """ super().__init__( name=os.path.basename(file_path), filesystem=filesystem, side_effect=side_effect, ) self.contents_read = False @property def byte_contents(self) -> Optional[bytes]: if not self.contents_read: self.contents_read = True with io_open(self.file_path, "rb") as f: self._byte_contents = f.read() # On MacOS and BSD, the above io.open() updates atime on the real file self.st_atime = os.stat(self.file_path).st_atime return self._byte_contents def set_contents(self, contents, encoding=None): self.contents_read = True super().set_contents(contents, encoding) def is_large_file(self): """The contents are never faked.""" return False class FakeDirectory(FakeFile): """Provides the appearance of a real directory.""" def __init__( self, name: str, perm_bits: int = helpers.PERM_DEF, filesystem: Optional["FakeFilesystem"] = None, ): """ Args: name: name of the file/directory, without parent path information perm_bits: permission bits. defaults to 0o777. filesystem: if set, the fake filesystem where the directory is created """ FakeFile.__init__(self, name, S_IFDIR | perm_bits, "", filesystem=filesystem) # directories have the link count of contained entries, # including '.' and '..' self.st_nlink += 1 self._entries: Dict[str, AnyFile] = {} def set_contents(self, contents: AnyStr, encoding: Optional[str] = None) -> bool: raise self.filesystem.raise_os_error(errno.EISDIR, self.path) @property def entries(self) -> Dict[str, FakeFile]: """Return the list of contained directory entries.""" return self._entries @property def ordered_dirs(self) -> List[str]: """Return the list of contained directory entry names ordered by creation order. """ return [ item[0] for item in sorted(self._entries.items(), key=lambda entry: entry[1].st_ino) ] def add_entry(self, path_object: FakeFile) -> None: """Adds a child FakeFile to this directory. Args: path_object: FakeFile instance to add as a child of this directory. Raises: OSError: if the directory has no write permission (Posix only) OSError: if the file or directory to be added already exists """ if ( not helpers.is_root() and not self.filesystem.is_windows_fs and not self.has_permission(helpers.PERM_WRITE) ): raise OSError(errno.EACCES, "Permission Denied", self.path) path_object_name: str = to_string(path_object.name) if path_object_name in self.entries: self.filesystem.raise_os_error(errno.EEXIST, self.path) self._entries[path_object_name] = path_object path_object.parent_dir = self if path_object.st_ino is None: self.filesystem.last_ino += 1 path_object.st_ino = self.filesystem.last_ino self.st_nlink += 1 path_object.st_nlink += 1 path_object.st_dev = self.st_dev if path_object.st_nlink == 1: self.filesystem.change_disk_usage( path_object.size, path_object.name, self.st_dev ) def get_entry(self, pathname_name: str) -> AnyFile: """Retrieves the specified child file or directory entry. Args: pathname_name: The basename of the child object to retrieve. Returns: The fake file or directory object. Raises: KeyError: if no child exists by the specified name. """ pathname_name = self._normalized_entryname(pathname_name) return self.entries[to_string(pathname_name)] def _normalized_entryname(self, pathname_name: str) -> str: if not self.filesystem.is_case_sensitive: matching_names = [ name for name in self.entries if name.lower() == pathname_name.lower() ] if matching_names: pathname_name = matching_names[0] return pathname_name def remove_entry(self, pathname_name: str, recursive: bool = True) -> None: """Removes the specified child file or directory. Args: pathname_name: Basename of the child object to remove. recursive: If True (default), the entries in contained directories are deleted first. Used to propagate removal errors (e.g. permission problems) from contained entries. Raises: KeyError: if no child exists by the specified name. OSError: if user lacks permission to delete the file, or (Windows only) the file is open. """ pathname_name = self._normalized_entryname(pathname_name) entry = self.get_entry(pathname_name) if self.filesystem.is_windows_fs: if not is_root() and entry.st_mode & helpers.PERM_WRITE == 0: self.filesystem.raise_os_error(errno.EACCES, pathname_name) if self.filesystem.has_open_file(entry): raise_error = True if os.name == "posix" and not hasattr(os, "O_TMPFILE"): # special handling for emulating Windows under macOS and PyPi # tempfile uses unlink based on the real OS while deleting # a temporary file, so we ignore that error in this specific case st = traceback.extract_stack(limit=6) if sys.version_info < (3, 10): if ( st[0].name == "TemporaryFile" and st[0].line == "_os.unlink(name)" ): raise_error = False else: # TemporaryFile implementation has changed in Python 3.10 if st[0].name == "opener" and st[0].line == "_os.unlink(name)": raise_error = False if raise_error: self.filesystem.raise_os_error(errno.EACCES, pathname_name) else: if not helpers.is_root() and not self.has_permission( helpers.PERM_WRITE | helpers.PERM_EXE ): self.filesystem.raise_os_error(errno.EACCES, pathname_name) if recursive and isinstance(entry, FakeDirectory): while entry.entries: entry.remove_entry(list(entry.entries)[0]) elif entry.st_nlink == 1: self.filesystem.change_disk_usage(-entry.size, pathname_name, entry.st_dev) self.st_nlink -= 1 entry.st_nlink -= 1 assert entry.st_nlink >= 0 del self.entries[to_string(pathname_name)] @property def size(self) -> int: """Return the total size of all files contained in this directory tree. """ return sum([item[1].size for item in self.entries.items()]) @size.setter def size(self, st_size: int) -> None: """Setting the size is an error for a directory.""" raise self.filesystem.raise_os_error(errno.EISDIR, self.path) def has_parent_object(self, dir_object: "FakeDirectory") -> bool: """Return `True` if dir_object is a direct or indirect parent directory, or if both are the same object.""" obj: Optional[FakeDirectory] = self while obj: if obj == dir_object: return True obj = obj.parent_dir return False def __str__(self) -> str: description = super().__str__() + ":\n" for item in self.entries: item_desc = self.entries[item].__str__() for line in item_desc.split("\n"): if line: description = description + " " + line + "\n" return description class FakeDirectoryFromRealDirectory(FakeDirectory): """Represents a fake directory copied from the real file system. The contents of the directory are read on demand only. """ def __init__( self, source_path: AnyPath, filesystem: "FakeFilesystem", read_only: bool, target_path: Optional[AnyPath] = None, ): """ Args: source_path: Full directory path. filesystem: The fake filesystem where the directory is created. read_only: If set, all files under the directory are treated as read-only, e.g. a write access raises an exception; otherwise, writing to the files changes the fake files only as usually. target_path: If given, the target path of the directory, otherwise the target is the same as `source_path`. Raises: OSError: if the directory does not exist in the real file system """ target_path = target_path or source_path real_stat = os.stat(source_path) super().__init__( name=to_string(os.path.split(target_path)[1]), perm_bits=real_stat.st_mode, filesystem=filesystem, ) self.st_ctime = real_stat.st_ctime self.st_atime = real_stat.st_atime self.st_mtime = real_stat.st_mtime self.st_gid = real_stat.st_gid self.st_uid = real_stat.st_uid self.source_path = source_path # type: ignore self.read_only = read_only self.contents_read = False @property def entries(self) -> Dict[str, FakeFile]: """Return the list of contained directory entries, loading them if not already loaded.""" if not self.contents_read: self.contents_read = True base = self.path for entry in os.listdir(self.source_path): source_path = os.path.join(self.source_path, entry) target_path = os.path.join(base, entry) # type: ignore if os.path.islink(source_path): self.filesystem.add_real_symlink(source_path, target_path) elif os.path.isdir(source_path): self.filesystem.add_real_directory( source_path, self.read_only, target_path=target_path ) else: self.filesystem.add_real_file( source_path, self.read_only, target_path=target_path ) return self._entries @property def size(self) -> int: # we cannot get the size until the contents are loaded if not self.contents_read: return 0 return super().size @size.setter def size(self, st_size: int) -> None: raise self.filesystem.raise_os_error(errno.EISDIR, self.path) class FakeFileWrapper: """Wrapper for a stream object for use by a FakeFile object. If the wrapper has any data written to it, it will propagate to the FakeFile object on close() or flush(). """ def __init__( self, file_object: FakeFile, file_path: AnyStr, update: bool, read: bool, append: bool, delete_on_close: bool, filesystem: "FakeFilesystem", newline: Optional[str], binary: bool, closefd: bool, encoding: Optional[str], errors: Optional[str], buffering: int, raw_io: bool, opened_as_fd: bool, is_stream: bool = False, ): self.file_object = file_object self.file_path = file_path # type: ignore[var-annotated] self._append = append self._read = read self.allow_update = update self._closefd = closefd self._file_epoch = file_object.epoch self.raw_io = raw_io self._binary = binary self.opened_as_fd = opened_as_fd self.is_stream = is_stream self._changed = False self._buffer_size = buffering if self._buffer_size == 0 and not binary: raise ValueError("can't have unbuffered text I/O") # buffer_size is ignored in text mode elif self._buffer_size == -1 or not binary: self._buffer_size = io.DEFAULT_BUFFER_SIZE self._use_line_buffer = not binary and buffering == 1 contents = file_object.byte_contents self._encoding = encoding or get_locale_encoding() errors = errors or "strict" self._io: Union[BinaryBufferIO, TextBufferIO] = ( BinaryBufferIO(contents) if binary else TextBufferIO( contents, encoding=encoding, newline=newline, errors=errors ) ) self._read_whence = 0 self._read_seek = 0 self._flush_pos = 0 if contents: self._flush_pos = len(contents) if update: if not append: self._io.seek(0) else: self._io.seek(self._flush_pos) self._read_seek = self._io.tell() if delete_on_close: assert filesystem, "delete_on_close=True requires filesystem" self._filesystem = filesystem self.delete_on_close = delete_on_close # override, don't modify FakeFile.name, as FakeFilesystem expects # it to be the file name only, no directories. self.name = file_object.opened_as self.filedes: Optional[int] = None def __enter__(self) -> "FakeFileWrapper": """To support usage of this fake file with the 'with' statement.""" return self def __exit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: """To support usage of this fake file with the 'with' statement.""" self.close() def _raise(self, message: str) -> NoReturn: if self.raw_io: self._filesystem.raise_os_error(errno.EBADF, self.file_path) raise io.UnsupportedOperation(message) def get_object(self) -> FakeFile: """Return the FakeFile object that is wrapped by the current instance. """ return self.file_object def fileno(self) -> int: """Return the file descriptor of the file object.""" if self.filedes is not None: return self.filedes raise OSError(errno.EBADF, "Invalid file descriptor") def close(self) -> None: """Close the file.""" self.close_fd(self.filedes) def close_fd(self, fd: Optional[int]) -> None: """Close the file for the given file descriptor.""" # ignore closing a closed file if not self._is_open(): return # for raw io, all writes are flushed immediately if not self.raw_io: try: self.flush() except OSError as e: if e.errno == errno.EBADF: # if we get here, we have an open file descriptor # without write permission, which has to be closed assert self.filedes self._filesystem.close_open_file(self.filedes) raise if self._filesystem.is_windows_fs and self._changed: self.file_object.st_mtime = helpers.now() assert fd is not None if self._closefd: self._filesystem.close_open_file(fd) else: open_files = self._filesystem.open_files[fd] assert open_files is not None open_files.remove(self) if self.delete_on_close: self._filesystem.remove_object( self.get_object().path # type: ignore[arg-type] ) @property def closed(self) -> bool: """Simulate the `closed` attribute on file.""" return not self._is_open() def _try_flush(self, old_pos: int) -> None: """Try to flush and reset the position if it fails.""" flush_pos = self._flush_pos try: self.flush() except OSError: # write failed - reset to previous position self._io.seek(old_pos) self._io.truncate() self._flush_pos = flush_pos raise def flush(self) -> None: """Flush file contents to 'disk'.""" if self.is_stream: return self._check_open_file() if self.allow_update: if self._append: contents = self._io.getvalue() self._sync_io() old_contents = self.file_object.byte_contents assert old_contents is not None contents = old_contents + contents[self._flush_pos :] self._set_stream_contents(contents) else: self._io.flush() contents = self._io.getvalue() changed = self.file_object.set_contents(contents, self._encoding) self.update_flush_pos() if changed: if self._filesystem.is_windows_fs: self._changed = True else: current_time = helpers.now() self.file_object.st_ctime = current_time self.file_object.st_mtime = current_time self._file_epoch = self.file_object.epoch self._flush_related_files() else: buf_length = len(self._io.getvalue()) content_length = 0 if self.file_object.byte_contents is not None: content_length = len(self.file_object.byte_contents) # an error is only raised if there is something to flush if content_length != buf_length: self._filesystem.raise_os_error(errno.EBADF) def update_flush_pos(self) -> None: self._flush_pos = self._io.tell() def _flush_related_files(self) -> None: for open_files in self._filesystem.open_files[3:]: if open_files is not None: for open_file in open_files: if ( open_file is not self and isinstance(open_file, FakeFileWrapper) and self.file_object == open_file.file_object and not open_file._append ): open_file._sync_io() def seek(self, offset: int, whence: int = 0) -> None: """Move read/write pointer in 'file'.""" self._check_open_file() if not self._append: self._io.seek(offset, whence) else: self._read_seek = offset self._read_whence = whence if not self.is_stream: self.flush() def tell(self) -> int: """Return the file's current position. Returns: int, file's current position in bytes. """ self._check_open_file() if not self.is_stream: self.flush() if not self._append: return self._io.tell() if self._read_whence: write_seek = self._io.tell() self._io.seek(self._read_seek, self._read_whence) self._read_seek = self._io.tell() self._read_whence = 0 self._io.seek(write_seek) return self._read_seek def _sync_io(self) -> None: """Update the stream with changes to the file object contents.""" if self._file_epoch == self.file_object.epoch: return contents = self.file_object.byte_contents assert contents is not None self._set_stream_contents(contents) self._file_epoch = self.file_object.epoch def _set_stream_contents(self, contents: bytes) -> None: whence = self._io.tell() self._io.seek(0) self._io.truncate() self._io.putvalue(contents) if not self._append: self._io.seek(whence) def _read_wrappers(self, name: str) -> Callable: """Wrap a stream attribute in a read wrapper. Returns a read_wrapper which tracks our own read pointer since the stream object has no concept of a different read and write pointer. Args: name: The name of the attribute to wrap. Should be a read call. Returns: The read_wrapper function. """ io_attr = getattr(self._io, name) def read_wrapper(*args, **kwargs): """Wrap all read calls to the stream object. We do this to track the read pointer separate from the write pointer. Anything that wants to read from the stream object while we're in append mode goes through this. Args: *args: pass through args **kwargs: pass through kwargs Returns: Wrapped stream object method """ self._io.seek(self._read_seek, self._read_whence) ret_value = io_attr(*args, **kwargs) self._read_seek = self._io.tell() self._read_whence = 0 self._io.seek(0, 2) return ret_value return read_wrapper def _other_wrapper(self, name: str) -> Callable: """Wrap a stream attribute in an other_wrapper. Args: name: the name of the stream attribute to wrap. Returns: other_wrapper which is described below. """ io_attr = getattr(self._io, name) def other_wrapper(*args, **kwargs): """Wrap all other calls to the stream Object. We do this to track changes to the write pointer. Anything that moves the write pointer in a file open for appending should move the read pointer as well. Args: *args: Pass through args. **kwargs: Pass through kwargs. Returns: Wrapped stream object method. """ write_seek = self._io.tell() ret_value = io_attr(*args, **kwargs) if write_seek != self._io.tell(): self._read_seek = self._io.tell() self._read_whence = 0 return ret_value return other_wrapper def _write_wrapper(self, name: str) -> Callable: """Wrap a stream attribute in a write_wrapper. Args: name: the name of the stream attribute to wrap. Returns: write_wrapper which is described below. """ io_attr = getattr(self._io, name) def write_wrapper(*args, **kwargs): """Wrap all other calls to the stream Object. We do this to track changes to the write pointer. Anything that moves the write pointer in a file open for appending should move the read pointer as well. Args: *args: Pass through args. **kwargs: Pass through kwargs. Returns: Wrapped stream object method. """ old_pos = self._io.tell() ret_value = io_attr(*args, **kwargs) new_pos = self._io.tell() # if the buffer size is exceeded, we flush use_line_buf = self._use_line_buffer and "\n" in args[0] if new_pos - self._flush_pos > self._buffer_size or use_line_buf: flush_all = new_pos - old_pos > self._buffer_size or use_line_buf # if the current write does not exceed the buffer size, # we revert to the previous position and flush that, # otherwise we flush all if not flush_all: self._io.seek(old_pos) self._io.truncate() self._try_flush(old_pos) if not flush_all: ret_value = io_attr(*args, **kwargs) if self._append: self._read_seek = self._io.tell() self._read_whence = 0 return ret_value return write_wrapper def _adapt_size_for_related_files(self, size: int) -> None: for open_files in self._filesystem.open_files[3:]: if open_files is not None: for open_file in open_files: if ( open_file is not self and isinstance(open_file, FakeFileWrapper) and self.file_object == open_file.file_object and cast(FakeFileWrapper, open_file)._append ): open_file._read_seek += size def _truncate_wrapper(self) -> Callable: """Wrap truncate() to allow flush after truncate. Returns: Wrapper which is described below. """ io_attr = self._io.truncate def truncate_wrapper(*args, **kwargs): """Wrap truncate call to call flush after truncate.""" if self._append: self._io.seek(self._read_seek, self._read_whence) size = io_attr(*args, **kwargs) self.flush() if not self.is_stream: self.file_object.size = size buffer_size = len(self._io.getvalue()) if buffer_size < size: self._io.seek(buffer_size) self._io.putvalue(b"\0" * (size - buffer_size)) self.file_object.set_contents(self._io.getvalue(), self._encoding) self._flush_pos = size self._adapt_size_for_related_files(size - buffer_size) self.flush() return size return truncate_wrapper def size(self) -> int: """Return the content size in bytes of the wrapped file.""" return self.file_object.st_size def __getattr__(self, name: str) -> Any: if self.file_object.is_large_file(): raise FakeLargeFileIoException(self.file_path) reading = name.startswith("read") or name == "next" truncate = name == "truncate" writing = name.startswith("write") or truncate if reading or writing: self._check_open_file() if not self._read and reading: return self._read_error() if not self.opened_as_fd and not self.allow_update and writing: return self._write_error() if reading: self._sync_io() if not self.is_stream: self.flush() if not self._filesystem.is_windows_fs: self.file_object.st_atime = helpers.now() if truncate: return self._truncate_wrapper() if self._append: if reading: return self._read_wrappers(name) elif not writing: return self._other_wrapper(name) if writing: return self._write_wrapper(name) return getattr(self._io, name) def _read_error(self) -> Callable: def read_error(*args, **kwargs): """Throw an error unless the argument is zero.""" if args and args[0] == 0: if self._filesystem.is_windows_fs and self.raw_io: return b"" if self._binary else "" self._raise("File is not open for reading.") return read_error def _write_error(self) -> Callable: def write_error(*args, **kwargs): """Throw an error.""" if self.raw_io: if self._filesystem.is_windows_fs and args and len(args[0]) == 0: return 0 self._raise("File is not open for writing.") return write_error def _is_open(self) -> bool: if self.filedes is not None and self.filedes < len(self._filesystem.open_files): open_files = self._filesystem.open_files[self.filedes] if open_files is not None and self in open_files: return True return False def _check_open_file(self) -> None: if not self.is_stream and not self._is_open(): raise ValueError("I/O operation on closed file") def __iter__(self) -> Union[Iterator[str], Iterator[bytes]]: if not self._read: self._raise("File is not open for reading") return self._io.__iter__() def __next__(self): if not self._read: self._raise("File is not open for reading") return next(self._io) class StandardStreamWrapper: """Wrapper for a system standard stream to be used in open files list.""" def __init__(self, stream_object: TextIO): self._stream_object = stream_object self.filedes: Optional[int] = None def get_object(self) -> TextIO: return self._stream_object def fileno(self) -> int: """Return the file descriptor of the wrapped standard stream.""" if self.filedes is not None: return self.filedes raise OSError(errno.EBADF, "Invalid file descriptor") def read(self, n: int = -1) -> bytes: return cast(bytes, self._stream_object.read()) def write(self, contents: bytes) -> int: self._stream_object.write(cast(str, contents)) return len(contents) def close(self) -> None: """We do not support closing standard streams.""" def close_fd(self, fd: Optional[int]) -> None: """We do not support closing standard streams.""" def is_stream(self) -> bool: return True def __enter__(self) -> "StandardStreamWrapper": """To support usage of this standard stream with the 'with' statement.""" return self def __exit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: """To support usage of this standard stream with the 'with' statement.""" self.close() class FakeDirWrapper: """Wrapper for a FakeDirectory object to be used in open files list.""" def __init__( self, file_object: FakeDirectory, file_path: AnyString, filesystem: "FakeFilesystem", ): self.file_object = file_object self.file_path = file_path self._filesystem = filesystem self.filedes: Optional[int] = None def get_object(self) -> FakeDirectory: """Return the FakeFile object that is wrapped by the current instance.""" return self.file_object def fileno(self) -> int: """Return the file descriptor of the file object.""" if self.filedes is not None: return self.filedes raise OSError(errno.EBADF, "Invalid file descriptor") def close(self) -> None: """Close the directory.""" self.close_fd(self.filedes) def close_fd(self, fd: Optional[int]) -> None: """Close the directory.""" assert fd is not None self._filesystem.close_open_file(fd) def read(self, numBytes: int = -1) -> bytes: """Read from the directory.""" return self.file_object.read(numBytes) def write(self, contents: bytes) -> int: """Write to the directory.""" self.file_object.write(contents) return len(contents) def __enter__(self) -> "FakeDirWrapper": """To support usage of this fake directory with the 'with' statement.""" return self def __exit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: """To support usage of this fake directory with the 'with' statement.""" self.close() class FakePipeWrapper: """Wrapper for a read or write descriptor of a real pipe object to be used in open files list. """ def __init__( self, filesystem: "FakeFilesystem", fd: int, can_write: bool, mode: str = "", ): self._filesystem = filesystem self.fd = fd # the real file descriptor self.can_write = can_write self.file_object = None self.filedes: Optional[int] = None self.real_file = None if mode: self.real_file = open(fd, mode) def __enter__(self) -> "FakePipeWrapper": """To support usage of this fake pipe with the 'with' statement.""" return self def __exit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: """To support usage of this fake pipe with the 'with' statement.""" self.close() def get_object(self) -> None: return self.file_object def fileno(self) -> int: """Return the fake file descriptor of the pipe object.""" if self.filedes is not None: return self.filedes raise OSError(errno.EBADF, "Invalid file descriptor") def read(self, numBytes: int = -1) -> bytes: """Read from the real pipe.""" if self.real_file: return self.real_file.read(numBytes) # pytype: disable=bad-return-type return os.read(self.fd, numBytes) def flush(self) -> None: """Flush the real pipe?""" def write(self, contents: bytes) -> int: """Write to the real pipe.""" if self.real_file: return self.real_file.write(contents) return os.write(self.fd, contents) def close(self) -> None: """Close the pipe descriptor.""" self.close_fd(self.filedes) def close_fd(self, fd: Optional[int]) -> None: """Close the pipe descriptor with the given file descriptor.""" assert fd is not None open_files = self._filesystem.open_files[fd] assert open_files is not None open_files.remove(self) if self.real_file: self.real_file.close() else: os.close(self.fd) def readable(self) -> bool: """The pipe end can either be readable or writable.""" return not self.can_write def writable(self) -> bool: """The pipe end can either be readable or writable.""" return self.can_write def seekable(self) -> bool: """A pipe is not seekable.""" return False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/fake_filesystem.py0000644000175100001660000037364414764107375020332 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """A fake filesystem implementation for unit testing. :Usage: >>> from pyfakefs import fake_filesystem, fake_os >>> filesystem = fake_filesystem.FakeFilesystem() >>> os_module = fake_os.FakeOsModule(filesystem) >>> pathname = '/a/new/dir/new-file' Create a new file object, creating parent directory objects as needed: >>> os_module.path.exists(pathname) False >>> new_file = filesystem.create_file(pathname) File objects can't be overwritten: >>> os_module.path.exists(pathname) True >>> try: ... filesystem.create_file(pathname) ... except OSError as e: ... assert e.errno == errno.EEXIST, 'unexpected errno: %d' % e.errno ... assert e.strerror == 'File exists in the fake filesystem' Remove a file object: >>> filesystem.remove_object(pathname) >>> os_module.path.exists(pathname) False Create a new file object at the previous path: >>> beatles_file = filesystem.create_file(pathname, ... contents='Dear Prudence\\nWon\\'t you come out to play?\\n') >>> os_module.path.exists(pathname) True Use the FakeFileOpen class to read fake file objects: >>> file_module = fake_filesystem.FakeFileOpen(filesystem) >>> for line in file_module(pathname): ... print(line.rstrip()) ... Dear Prudence Won't you come out to play? File objects cannot be treated like directory objects: >>> try: ... os_module.listdir(pathname) ... except OSError as e: ... assert e.errno == errno.ENOTDIR, 'unexpected errno: %d' % e.errno ... assert e.strerror == 'Not a directory in the fake filesystem' The FakeOsModule can list fake directory objects: >>> os_module.listdir(os_module.path.dirname(pathname)) ['new-file'] The FakeOsModule also supports stat operations: >>> import stat >>> stat.S_ISREG(os_module.stat(pathname).st_mode) True >>> stat.S_ISDIR(os_module.stat(os_module.path.dirname(pathname)).st_mode) True """ import contextlib import dataclasses import errno import heapq import os import random import sys import tempfile from collections import namedtuple, OrderedDict from doctest import TestResults from enum import Enum from stat import ( S_IFREG, S_IFDIR, S_ISLNK, S_IFMT, S_ISDIR, S_IFLNK, S_ISREG, ) from typing import ( List, Callable, Union, Any, Dict, Tuple, cast, AnyStr, overload, NoReturn, Optional, ) from pyfakefs import fake_file, fake_path, fake_io, fake_os, helpers, fake_open from pyfakefs.fake_file import AnyFileWrapper, AnyFile from pyfakefs.helpers import ( is_int_type, make_string_path, to_string, matching_string, AnyPath, AnyString, WINDOWS_PROPERTIES, POSIX_PROPERTIES, FSType, ) if sys.platform.startswith("linux"): # on newer Linux system, the default maximum recursion depth is 40 # we ignore older systems here _MAX_LINK_DEPTH = 40 else: # on MacOS and Windows, the maximum recursion depth is 32 _MAX_LINK_DEPTH = 32 class OSType(Enum): """Defines the real or simulated OS of the underlying file system.""" LINUX = "linux" MACOS = "macos" WINDOWS = "windows" # definitions for backwards compatibility FakeFile = fake_file.FakeFile FakeNullFile = fake_file.FakeNullFile FakeFileFromRealFile = fake_file.FakeFileFromRealFile FakeDirectory = fake_file.FakeDirectory FakeDirectoryFromRealDirectory = fake_file.FakeDirectoryFromRealDirectory FakeFileWrapper = fake_file.FakeFileWrapper StandardStreamWrapper = fake_file.StandardStreamWrapper FakeDirWrapper = fake_file.FakeDirWrapper FakePipeWrapper = fake_file.FakePipeWrapper FakePathModule = fake_path.FakePathModule FakeOsModule = fake_os.FakeOsModule FakeFileOpen = fake_open.FakeFileOpen FakeIoModule = fake_io.FakeIoModule if sys.platform != "win32": FakeFcntlModule = fake_io.FakeFcntlModule PatchMode = fake_io.PatchMode is_root = helpers.is_root get_uid = helpers.get_uid set_uid = helpers.set_uid get_gid = helpers.get_gid set_gid = helpers.set_gid reset_ids = helpers.reset_ids PERM_READ = helpers.PERM_READ PERM_WRITE = helpers.PERM_WRITE PERM_EXE = helpers.PERM_EXE PERM_DEF = helpers.PERM_DEF PERM_DEF_FILE = helpers.PERM_DEF_FILE PERM_ALL = helpers.PERM_ALL class FakeFilesystem: """Provides the appearance of a real directory tree for unit testing. Attributes: is_case_sensitive: `True` if a case-sensitive file system is assumed. root: The root :py:class:`FakeDirectory` entry of the file system. umask: The umask used for newly created files, see `os.umask`. patcher: Holds the Patcher object if created from it. Allows access to the patcher object if using the pytest fs fixture. patch_open_code: Defines how `io.open_code` will be patched; patching can be on, off, or in automatic mode. shuffle_listdir_results: If `True`, `os.listdir` will not sort the results to match the real file system behavior. """ def __init__( self, path_separator: str = os.path.sep, total_size: Optional[int] = None, patcher: Any = None, create_temp_dir: bool = False, ) -> None: """ Args: path_separator: optional substitute for os.path.sep total_size: if not None, the total size in bytes of the root filesystem. patcher: the Patcher instance if created from the Patcher create_temp_dir: If True, a temp directory is created on initialization. Under Posix, if the temp directory is not `/tmp`, a link to the temp path is additionally created at `/tmp`. Example usage to use the same path separator under all systems: >>> filesystem = FakeFilesystem(path_separator='/') """ self.patcher = patcher self.create_temp_dir = create_temp_dir # is_windows_fs can be used to test the behavior of pyfakefs under # Windows fs on non-Windows systems and vice verse; # is it used to support drive letters, UNC paths and some other # Windows-specific features self._is_windows_fs = sys.platform == "win32" # can be used to test some MacOS-specific behavior under other systems self._is_macos = sys.platform == "darwin" # is_case_sensitive can be used to test pyfakefs for case-sensitive # file systems on non-case-sensitive systems and vice verse self.is_case_sensitive: bool = not (self._is_windows_fs or self._is_macos) # by default, we use the configured filesystem self.fs_type = FSType.DEFAULT base_properties = ( WINDOWS_PROPERTIES if self._is_windows_fs else POSIX_PROPERTIES ) self.fs_properties = [ dataclasses.replace(base_properties), POSIX_PROPERTIES, WINDOWS_PROPERTIES, ] self.path_separator = path_separator self.root: FakeDirectory self._cwd = "" # We can't query the current value without changing it: self.umask: int = os.umask(0o22) os.umask(self.umask) # A list of open file objects. Their position in the list is their # file descriptor number self.open_files: List[Optional[List[AnyFileWrapper]]] = [] # A heap containing all free positions in self.open_files list self._free_fd_heap: List[int] = [] # last used numbers for inodes (st_ino) and devices (st_dev) self.last_ino: int = 0 self.last_dev: int = 0 self.mount_points: Dict[AnyString, Dict] = OrderedDict() self.dev_null: Any = None self.reset(total_size=total_size, init_pathlib=False) # set from outside if needed self.patch_open_code = PatchMode.OFF self.shuffle_listdir_results = False @property def is_linux(self) -> bool: """Returns `True` in a real or faked Linux file system.""" return not self.is_windows_fs and not self.is_macos @property def is_windows_fs(self) -> bool: """Returns `True` in a real or faked Windows file system.""" return self.fs_type == FSType.WINDOWS or ( self.fs_type == FSType.DEFAULT and self._is_windows_fs ) @is_windows_fs.setter def is_windows_fs(self, value: bool) -> None: if self._is_windows_fs != value: self._is_windows_fs = value if value: self._is_macos = False self.reset() FakePathModule.reset(self) @property def is_macos(self) -> bool: """Returns `True` in a real or faked macOS file system.""" return self._is_macos @is_macos.setter def is_macos(self, value: bool) -> None: if self._is_macos != value: self._is_macos = value if value: self._is_windows_fs = False self.reset() FakePathModule.reset(self) @property def path_separator(self) -> str: """Returns the path separator, corresponds to `os.path.sep`.""" return self.fs_properties[self.fs_type.value].sep @path_separator.setter def path_separator(self, value: str) -> None: self.fs_properties[0].sep = value if value != os.sep: self.alternative_path_separator = None @property def alternative_path_separator(self) -> Optional[str]: """Returns the alternative path separator, corresponds to `os.path.altsep`.""" return self.fs_properties[self.fs_type.value].altsep @alternative_path_separator.setter def alternative_path_separator(self, value: Optional[str]) -> None: self.fs_properties[0].altsep = value @property def devnull(self) -> str: return self.fs_properties[self.fs_type.value].devnull @property def pathsep(self) -> str: return self.fs_properties[self.fs_type.value].pathsep @property def line_separator(self) -> str: return self.fs_properties[self.fs_type.value].linesep @property def cwd(self) -> str: """Return the current working directory of the fake filesystem.""" return self._cwd @cwd.setter def cwd(self, value: str) -> None: """Set the current working directory of the fake filesystem. Make sure a new drive or share is auto-mounted under Windows. """ _cwd = make_string_path(value) self._cwd = _cwd.replace( matching_string(_cwd, os.sep), matching_string(_cwd, self.path_separator) ) self._auto_mount_drive_if_needed(value) @property def root_dir(self) -> FakeDirectory: """Return the root directory, which represents "/" under POSIX, and the current drive under Windows.""" if self.is_windows_fs: return self._mount_point_dir_for_cwd() return self.root @property def root_dir_name(self) -> str: """Return the root directory name, which is "/" under POSIX, and the root path of the current drive under Windows.""" root_dir = to_string(self.root_dir.name) if not root_dir.endswith(self.path_separator): return root_dir + self.path_separator return root_dir @property def os(self) -> OSType: """Return the real or simulated type of operating system.""" return ( OSType.WINDOWS if self.is_windows_fs else OSType.MACOS if self.is_macos else OSType.LINUX ) @os.setter def os(self, value: OSType) -> None: """Set the simulated type of operating system underlying the fake file system.""" self._is_windows_fs = value == OSType.WINDOWS self._is_macos = value == OSType.MACOS self.is_case_sensitive = value == OSType.LINUX self.fs_type = FSType.DEFAULT base_properties = ( WINDOWS_PROPERTIES if self._is_windows_fs else POSIX_PROPERTIES ) self.fs_properties[0] = base_properties self.reset() FakePathModule.reset(self) def reset(self, total_size: Optional[int] = None, init_pathlib: bool = True): """Remove all file system contents and reset the root.""" self.root = FakeDirectory(self.path_separator, filesystem=self) self.dev_null = FakeNullFile(self) self.open_files.clear() self._free_fd_heap.clear() self.last_ino = 0 self.last_dev = 0 self.mount_points.clear() self._add_root_mount_point(total_size) self._add_standard_streams() if self.create_temp_dir: self._create_temp_dir() if init_pathlib: from pyfakefs import fake_pathlib fake_pathlib.init_module(self) @contextlib.contextmanager def use_fs_type(self, fs_type: FSType): old_fs_type = self.fs_type try: self.fs_type = fs_type yield finally: self.fs_type = old_fs_type def _add_root_mount_point(self, total_size): mount_point = "C:" if self.is_windows_fs else self.path_separator self._cwd = mount_point if not self.cwd.endswith(self.path_separator): self._cwd += self.path_separator self.add_mount_point(mount_point, total_size) def pause(self) -> None: """Pause the patching of the file system modules until :py:meth:`resume` is called. After that call, all file system calls are executed in the real file system. Calling `pause()` twice is silently ignored. Only allowed if the file system object was created by a `Patcher` object. This is also the case for the pytest `fs` fixture. Raises: RuntimeError: if the file system was not created by a `Patcher`. """ if self.patcher is None: raise RuntimeError( "pause() can only be called from a fake file " "system object created by a Patcher object" ) self.patcher.pause() def resume(self) -> None: """Resume the patching of the file system modules if :py:meth:`pause` has been called before. After that call, all file system calls are executed in the fake file system. Does nothing if patching is not paused. Raises: RuntimeError: if the file system has not been created by `Patcher`. """ if self.patcher is None: raise RuntimeError( "resume() can only be called from a fake file " "system object created by a Patcher object" ) self.patcher.resume() def clear_cache(self) -> None: """Clear the cache of non-patched modules.""" if self.patcher: self.patcher.clear_cache() def raise_os_error( self, err_no: int, filename: Optional[AnyString] = None, winerror: Optional[int] = None, ) -> NoReturn: """Raises OSError. The error message is constructed from the given error code and shall start with the error string issued in the real system. Note: this is not true under Windows if winerror is given - in this case a localized message specific to winerror will be shown in the real file system. Args: err_no: A numeric error code from the C variable errno. filename: The name of the affected file, if any. winerror: Windows only - the specific Windows error code. """ message = os.strerror(err_no) + " in the fake filesystem" if winerror is not None and sys.platform == "win32" and self.is_windows_fs: raise OSError(err_no, message, filename, winerror) raise OSError(err_no, message, filename) def get_path_separator(self, path: AnyStr) -> AnyStr: """Return the path separator as the same type as path""" return matching_string(path, self.path_separator) def _alternative_path_separator(self, path: AnyStr) -> Optional[AnyStr]: """Return the alternative path separator as the same type as path""" return matching_string(path, self.alternative_path_separator) def starts_with_sep(self, path: AnyStr) -> bool: """Return True if path starts with a path separator.""" sep = self.get_path_separator(path) altsep = self._alternative_path_separator(path) return path.startswith(sep) or altsep is not None and path.startswith(altsep) def add_mount_point( self, path: AnyStr, total_size: Optional[int] = None, can_exist: bool = False, ) -> Dict: """Add a new mount point for a filesystem device. The mount point gets a new unique device number. Args: path: The root path for the new mount path. total_size: The new total size of the added filesystem device in bytes. Defaults to infinite size. can_exist: If `True`, no error is raised if the mount point already exists. Returns: The newly created mount point dict. Raises: OSError: if trying to mount an existing mount point again, and `can_exist` is False. """ path = self.normpath(self.normcase(path)) for mount_point in self.mount_points: if ( self.is_case_sensitive and path == matching_string(path, mount_point) or not self.is_case_sensitive and path.lower() == matching_string(path, mount_point.lower()) ): if can_exist: return self.mount_points[mount_point] self.raise_os_error(errno.EEXIST, path) self.last_dev += 1 self.mount_points[path] = { "idev": self.last_dev, "total_size": total_size, "used_size": 0, } if path == matching_string(path, self.root.name): # special handling for root path: has been created before root_dir = self.root self.last_ino += 1 root_dir.st_ino = self.last_ino else: root_dir = self._create_mount_point_dir(path) root_dir.st_dev = self.last_dev return self.mount_points[path] def _create_mount_point_dir(self, directory_path: AnyPath) -> FakeDirectory: """A version of `create_dir` for the mount point directory creation, which avoids circular calls and unneeded checks. """ dir_path = self.make_string_path(directory_path) path_components = self._path_components(dir_path) current_dir = self.root new_dirs = [] for component in [to_string(p) for p in path_components]: directory = self._directory_content(current_dir, to_string(component))[1] if not directory: new_dir = FakeDirectory(component, filesystem=self) new_dirs.append(new_dir) current_dir.add_entry(new_dir) current_dir = new_dir else: current_dir = cast(FakeDirectory, directory) for new_dir in new_dirs: new_dir.st_mode = S_IFDIR | helpers.PERM_DEF return current_dir def _auto_mount_drive_if_needed(self, path: AnyStr) -> Optional[Dict]: """Windows only: if `path` is located on an unmounted drive or UNC mount point, the drive/mount point is added to the mount points.""" if self.is_windows_fs: drive = self.splitdrive(path)[0] if drive: return self.add_mount_point(path=drive, can_exist=True) return None def _mount_point_for_path(self, path: AnyStr) -> Dict: path = self.absnormpath(self._original_path(path)) for mount_path in self.mount_points: if path == matching_string(path, mount_path): return self.mount_points[mount_path] mount_path = matching_string(path, "") drive = self.splitdrive(path)[0] for root_path in self.mount_points: root_path = matching_string(path, root_path) if drive and not root_path.startswith(drive): continue if path.startswith(root_path) and len(root_path) > len(mount_path): mount_path = root_path if mount_path: return self.mount_points[to_string(mount_path)] mount_point = self._auto_mount_drive_if_needed(path) assert mount_point return mount_point def _mount_point_dir_for_cwd(self) -> FakeDirectory: """Return the fake directory object of the mount point where the current working directory points to.""" def object_from_path(file_path) -> FakeDirectory: path_components = self._path_components(file_path) target = self.root for component in path_components: target = cast(FakeDirectory, target.get_entry(component)) return target path = to_string(self.cwd) for mount_path in self.mount_points: if path == to_string(mount_path): return object_from_path(mount_path) mount_path = "" drive = to_string(self.splitdrive(path)[0]) for root_path in self.mount_points: str_root_path = to_string(root_path) if drive and not str_root_path.startswith(drive): continue if path.startswith(str_root_path) and len(str_root_path) > len(mount_path): mount_path = root_path return object_from_path(mount_path) def _mount_point_for_device(self, idev: int) -> Optional[Dict]: for mount_point in self.mount_points.values(): if mount_point["idev"] == idev: return mount_point return None def get_disk_usage(self, path: Optional[AnyStr] = None) -> Tuple[int, int, int]: """Return the total, used and free disk space in bytes as named tuple, or placeholder values simulating unlimited space if not set. .. note:: This matches the return value of ``shutil.disk_usage()``. Args: path: The disk space is returned for the file system device where `path` resides. Defaults to the root path (e.g. '/' on Unix systems). """ DiskUsage = namedtuple("DiskUsage", "total, used, free") if path is None: mount_point = next(iter(self.mount_points.values())) else: file_path = make_string_path(path) mount_point = self._mount_point_for_path(file_path) if mount_point and mount_point["total_size"] is not None: return DiskUsage( mount_point["total_size"], mount_point["used_size"], mount_point["total_size"] - mount_point["used_size"], ) return DiskUsage(1024 * 1024 * 1024 * 1024, 0, 1024 * 1024 * 1024 * 1024) def set_disk_usage(self, total_size: int, path: Optional[AnyStr] = None) -> None: """Changes the total size of the file system, preserving the used space. Example usage: set the size of an auto-mounted Windows drive. Args: total_size: The new total size of the filesystem in bytes. path: The disk space is changed for the file system device where `path` resides. Defaults to the root path (e.g. '/' on Unix systems). Raises: OSError: if the new space is smaller than the used size. """ file_path: AnyStr = ( path if path is not None else self.root_dir_name # type: ignore ) mount_point = self._mount_point_for_path(file_path) if ( mount_point["total_size"] is not None and mount_point["used_size"] > total_size ): self.raise_os_error(errno.ENOSPC, path) mount_point["total_size"] = total_size def change_disk_usage( self, usage_change: int, file_path: AnyStr, st_dev: int ) -> None: """Change the used disk space by the given amount. Args: usage_change: Number of bytes added to the used space. If negative, the used space will be decreased. file_path: The path of the object needing the disk space. st_dev: The device ID for the respective file system. Raises: OSError: if `usage_change` exceeds the free file system space """ mount_point = self._mount_point_for_device(st_dev) if mount_point: total_size = mount_point["total_size"] if total_size is not None: if total_size - mount_point["used_size"] < usage_change: self.raise_os_error(errno.ENOSPC, file_path) mount_point["used_size"] += usage_change def stat(self, entry_path: AnyStr, follow_symlinks: bool = True): """Return the os.stat-like tuple for the FakeFile object of entry_path. Args: entry_path: Path to filesystem object to retrieve. follow_symlinks: If False and entry_path points to a symlink, the link itself is inspected instead of the linked object. Returns: The FakeStatResult object corresponding to entry_path. Raises: OSError: if the filesystem object doesn't exist. """ # stat should return the tuple representing return value of os.stat try: file_object = self.resolve( entry_path, follow_symlinks, allow_fd=True, check_read_perm=False, check_exe_perm=False, ) except TypeError: file_object = self.resolve(entry_path) if not is_root(): # make sure stat raises if a parent dir is not readable parent_dir = file_object.parent_dir if parent_dir: self._get_object(parent_dir.path, check_read_perm=False) # type: ignore[arg-type] self.raise_for_filepath_ending_with_separator( entry_path, file_object, follow_symlinks ) return file_object.stat_result.copy() def raise_for_filepath_ending_with_separator( self, entry_path: AnyStr, file_object: FakeFile, follow_symlinks: bool = True, macos_handling: bool = False, ) -> None: if self.ends_with_path_separator(entry_path): if S_ISLNK(file_object.st_mode): try: link_object = self.resolve(entry_path) except OSError as exc: if self.is_macos and exc.errno != errno.ENOENT: return if self.is_windows_fs: self.raise_os_error(errno.EINVAL, entry_path) raise if not follow_symlinks or self.is_windows_fs or self.is_macos: file_object = link_object if self.is_windows_fs: is_error = S_ISREG(file_object.st_mode) elif self.is_macos and macos_handling: is_error = not S_ISLNK(file_object.st_mode) else: is_error = not S_ISDIR(file_object.st_mode) if is_error: error_nr = errno.EINVAL if self.is_windows_fs else errno.ENOTDIR self.raise_os_error(error_nr, entry_path) def chmod( self, path: Union[AnyStr, int], mode: int, follow_symlinks: bool = True, force_unix_mode: bool = False, ) -> None: """Change the permissions of a file as encoded in integer mode. Args: path: (str | int) Path to the file or file descriptor. mode: (int) Permissions. follow_symlinks: If `False` and `path` points to a symlink, the link itself is affected instead of the linked object. force_unix_mode: if True and run under Windows, the mode is not adapted for Windows to allow making dirs unreadable """ allow_fd = not self.is_windows_fs or sys.version_info >= (3, 13) file_object = self.resolve( path, follow_symlinks, allow_fd=allow_fd, check_owner=True ) if self.is_windows_fs and not force_unix_mode: if mode & helpers.PERM_WRITE: file_object.st_mode = file_object.st_mode | 0o222 else: file_object.st_mode = file_object.st_mode & 0o777555 else: file_object.st_mode = (file_object.st_mode & ~helpers.PERM_ALL) | ( mode & helpers.PERM_ALL ) file_object.st_ctime = helpers.now() def utime( self, path: AnyStr, times: Optional[Tuple[Union[int, float], Union[int, float]]] = None, *, ns: Optional[Tuple[int, int]] = None, follow_symlinks: bool = True, ) -> None: """Change the access and modified times of a file. Args: path: (str) Path to the file. times: 2-tuple of int or float numbers, of the form (atime, mtime) which is used to set the access and modified times in seconds. If None, both times are set to the current time. ns: 2-tuple of int numbers, of the form (atime, mtime) which is used to set the access and modified times in nanoseconds. If `None`, both times are set to the current time. follow_symlinks: If `False` and entry_path points to a symlink, the link itself is queried instead of the linked object. Raises: TypeError: If anything other than the expected types is specified in the passed `times` or `ns` tuple, or if the tuple length is not equal to 2. ValueError: If both times and ns are specified. """ self._handle_utime_arg_errors(ns, times) file_object = self.resolve(path, follow_symlinks, allow_fd=True) if times is not None: for file_time in times: if not isinstance(file_time, (int, float)): raise TypeError("atime and mtime must be numbers") file_object.st_atime = times[0] file_object.st_mtime = times[1] elif ns is not None: for file_time in ns: if not isinstance(file_time, int): raise TypeError("atime and mtime must be ints") file_object.st_atime_ns = ns[0] file_object.st_mtime_ns = ns[1] else: current_time = helpers.now() file_object.st_atime = current_time file_object.st_mtime = current_time @staticmethod def _handle_utime_arg_errors( ns: Optional[Tuple[int, int]], times: Optional[Tuple[Union[int, float], Union[int, float]]], ): if times is not None and ns is not None: raise ValueError( "utime: you may specify either 'times' or 'ns' but not both" ) if times is not None and len(times) != 2: raise TypeError("utime: 'times' must be either a tuple of two ints or None") if ns is not None and len(ns) != 2: raise TypeError("utime: 'ns' must be a tuple of two ints") def add_open_file(self, file_obj: AnyFileWrapper, new_fd: int = -1) -> int: """Add file_obj to the list of open files on the filesystem. Used internally to manage open files. The position in the open_files array is the file descriptor number. Args: file_obj: File object to be added to open files list. new_fd: The optional new file descriptor. Returns: File descriptor number for the file object. """ if new_fd >= 0: size = len(self.open_files) if new_fd < size: open_files = self.open_files[new_fd] if open_files: for f in open_files: try: f.close() except OSError: pass if new_fd in self._free_fd_heap: self._free_fd_heap.remove(new_fd) self.open_files[new_fd] = [file_obj] else: for fd in range(size, new_fd): self.open_files.append([]) heapq.heappush(self._free_fd_heap, fd) self.open_files.append([file_obj]) return new_fd if self._free_fd_heap: open_fd = heapq.heappop(self._free_fd_heap) self.open_files[open_fd] = [file_obj] return open_fd self.open_files.append([file_obj]) return len(self.open_files) - 1 def close_open_file(self, file_des: int) -> None: """Remove file object with given descriptor from the list of open files. Sets the entry in open_files to None. Args: file_des: Descriptor of file object to be removed from open files list. """ self.open_files[file_des] = None heapq.heappush(self._free_fd_heap, file_des) def get_open_file(self, file_des: int) -> AnyFileWrapper: """Return an open file. Args: file_des: File descriptor of the open file. Raises: OSError: an invalid file descriptor. TypeError: filedes is not an integer. Returns: Open file object. """ try: return self.get_open_files(file_des)[0] except IndexError: self.raise_os_error(errno.EBADF, str(file_des)) def get_open_files(self, file_des: int) -> List[AnyFileWrapper]: """Return the list of open files for a file descriptor. Args: file_des: File descriptor of the open files. Raises: OSError: an invalid file descriptor. TypeError: filedes is not an integer. Returns: List of open file objects. """ if not is_int_type(file_des): raise TypeError("an integer is required") valid = file_des < len(self.open_files) if valid: return self.open_files[file_des] or [] self.raise_os_error(errno.EBADF, str(file_des)) def has_open_file(self, file_object: FakeFile) -> bool: """Return True if the given file object is in the list of open files. Args: file_object: The FakeFile object to be checked. Returns: `True` if the file is open. """ return file_object in [ wrappers[0].get_object() for wrappers in self.open_files if wrappers ] def _normalize_path_sep(self, path: AnyStr) -> AnyStr: alt_sep = self._alternative_path_separator(path) if alt_sep is not None: return path.replace(alt_sep, self.get_path_separator(path)) return path def normcase(self, path: AnyStr) -> AnyStr: """Replace all appearances of alternative path separator with path separator. Do nothing if no alternative separator is set. Args: path: The path to be normalized. Returns: The normalized path that will be used internally. """ file_path = make_string_path(path) return self._normalize_path_sep(file_path) def normpath(self, path: AnyStr) -> AnyStr: """Mimic os.path.normpath using the specified path_separator. Mimics os.path.normpath using the path_separator that was specified for this FakeFilesystem. Normalizes the path, but unlike the method absnormpath, does not make it absolute. Eliminates dot components (. and ..) and combines repeated path separators (//). Initial .. components are left in place for relative paths. If the result is an empty path, '.' is returned instead. This also replaces alternative path separator with path separator. That is, it behaves like the real os.path.normpath on Windows if initialized with '\\' as path separator and '/' as alternative separator. Args: path: (str) The path to normalize. Returns: (str) A copy of path with empty components and dot components removed. """ path_str = self.normcase(path) drive, path_str = self.splitdrive(path_str) sep = self.get_path_separator(path_str) is_absolute_path = path_str.startswith(sep) path_components: List[AnyStr] = path_str.split( sep ) # pytype: disable=invalid-annotation collapsed_path_components: List[ AnyStr ] = [] # pytype: disable=invalid-annotation dot = matching_string(path_str, ".") dotdot = matching_string(path_str, "..") for component in path_components: if (not component) or (component == dot): continue if component == dotdot: if collapsed_path_components and ( collapsed_path_components[-1] != dotdot ): # Remove an up-reference: directory/.. collapsed_path_components.pop() continue elif is_absolute_path: # Ignore leading .. components if starting from the # root directory. continue collapsed_path_components.append(component) collapsed_path = sep.join(collapsed_path_components) if is_absolute_path: collapsed_path = sep + collapsed_path return drive + collapsed_path or dot def _original_path(self, path: AnyStr) -> AnyStr: """Return a normalized case version of the given path for case-insensitive file systems. For case-sensitive file systems, return path unchanged. Args: path: the file path to be transformed Returns: A version of path matching the case of existing path elements. """ def components_to_path(): if len(path_components) > len(normalized_components): normalized_components.extend( to_string(p) for p in path_components[len(normalized_components) :] ) sep = self.path_separator normalized_path = sep.join(normalized_components) if self.starts_with_sep(path) and not self.starts_with_sep(normalized_path): normalized_path = sep + normalized_path if len(normalized_path) == 2 and self.starts_with_drive_letter( normalized_path ): normalized_path += sep return normalized_path if self.is_case_sensitive or not path: return path path = self.replace_windows_root(path) path_components = self._path_components(path) normalized_components = [] current_dir = self.root for component in path_components: if not isinstance(current_dir, FakeDirectory): return components_to_path() dir_name, directory = self._directory_content( current_dir, to_string(component) ) if directory is None or ( isinstance(directory, FakeDirectory) and directory._byte_contents is None and directory.st_size == 0 ): return components_to_path() current_dir = cast(FakeDirectory, directory) normalized_components.append(dir_name) return components_to_path() def absnormpath(self, path: AnyStr) -> AnyStr: """Absolutize and minimalize the given path. Forces all relative paths to be absolute, and normalizes the path to eliminate dot and empty components. Args: path: Path to normalize. Returns: The normalized path relative to the current working directory, or the root directory if path is empty. """ path = self.normcase(path) cwd = matching_string(path, self.cwd) if not path: path = self.get_path_separator(path) if path == matching_string(path, "."): path = cwd elif not self._starts_with_root_path(path): # Prefix relative paths with cwd, if cwd is not root. root_name = matching_string(path, self.root.name) empty = matching_string(path, "") path = self.get_path_separator(path).join( (cwd != root_name and cwd or empty, path) ) else: path = self.replace_windows_root(path) return self.normpath(path) def splitpath(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]: """Mimic os.path.split using the specified path_separator. Mimics os.path.split using the path_separator that was specified for this FakeFilesystem. Args: path: (str) The path to split. Returns: (str) A duple (pathname, basename) for which pathname does not end with a slash, and basename does not contain a slash. """ path = make_string_path(path) sep = self.get_path_separator(path) alt_sep = self._alternative_path_separator(path) seps = sep if alt_sep is None else sep + alt_sep drive, path = self.splitdrive(path) i = len(path) while i and path[i - 1] not in seps: i -= 1 head, tail = path[:i], path[i:] # now tail has no slashes # remove trailing slashes from head, unless it's all slashes head = head.rstrip(seps) or head return drive + head, tail def splitdrive(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]: """Splits the path into the drive part and the rest of the path. Taken from Windows specific implementation in Python 3.5 and slightly adapted. Args: path: the full path to be splitpath. Returns: A tuple of the drive part and the rest of the path, or of an empty string and the full path if drive letters are not supported or no drive is present. """ path_str = make_string_path(path) if self.is_windows_fs: if len(path_str) >= 2: norm_str = self.normcase(path_str) sep = self.get_path_separator(path_str) # UNC path_str handling if (norm_str[0:2] == sep * 2) and (norm_str[2:3] != sep): # UNC path_str handling - splits off the mount point # instead of the drive sep_index = norm_str.find(sep, 2) if sep_index == -1: return path_str[:0], path_str sep_index2 = norm_str.find(sep, sep_index + 1) if sep_index2 == sep_index + 1: return path_str[:0], path_str if sep_index2 == -1: sep_index2 = len(path_str) return path_str[:sep_index2], path_str[sep_index2:] if path_str[1:2] == matching_string(path_str, ":"): return path_str[:2], path_str[2:] return path_str[:0], path_str def splitroot(self, path: AnyStr): """Split a pathname into drive, root and tail. Implementation taken from ntpath and posixpath. """ p = os.fspath(path) if isinstance(p, bytes): sep = self.path_separator.encode() altsep = None alternative_path_separator = self.alternative_path_separator if alternative_path_separator is not None: altsep = alternative_path_separator.encode() colon = b":" unc_prefix = b"\\\\?\\UNC\\" empty = b"" else: sep = self.path_separator altsep = self.alternative_path_separator colon = ":" unc_prefix = "\\\\?\\UNC\\" empty = "" if self.is_windows_fs: normp = p.replace(altsep, sep) if altsep else p if normp[:1] == sep: if normp[1:2] == sep: # UNC drives, e.g. \\server\share or \\?\UNC\server\share # Device drives, e.g. \\.\device or \\?\device start = 8 if normp[:8].upper() == unc_prefix else 2 index = normp.find(sep, start) if index == -1: return p, empty, empty index2 = normp.find(sep, index + 1) if index2 == -1: return p, empty, empty return p[:index2], p[index2 : index2 + 1], p[index2 + 1 :] else: # Relative path with root, e.g. \Windows return empty, p[:1], p[1:] elif normp[1:2] == colon: if normp[2:3] == sep: # Absolute drive-letter path, e.g. X:\Windows return p[:2], p[2:3], p[3:] else: # Relative path with drive, e.g. X:Windows return p[:2], empty, p[2:] else: # Relative path, e.g. Windows return empty, empty, p else: if p[:1] != sep: # Relative path, e.g.: 'foo' return empty, empty, p elif p[1:2] != sep or p[2:3] == sep: # Absolute path, e.g.: '/foo', '///foo', '////foo', etc. return empty, sep, p[1:] else: return empty, p[:2], p[2:] def _join_paths_with_drive_support(self, *all_paths: AnyStr) -> AnyStr: """Taken from Python 3.5 os.path.join() code in ntpath.py and slightly adapted""" base_path = all_paths[0] paths_to_add = all_paths[1:] sep = self.get_path_separator(base_path) seps = [sep, self._alternative_path_separator(base_path)] result_drive, result_path = self.splitdrive(base_path) for path in paths_to_add: drive_part, path_part = self.splitdrive(path) if path_part and path_part[:1] in seps: # Second path is absolute if drive_part or not result_drive: result_drive = drive_part result_path = path_part continue elif drive_part and drive_part != result_drive: if self.is_case_sensitive or drive_part.lower() != result_drive.lower(): # Different drives => ignore the first path entirely result_drive = drive_part result_path = path_part continue # Same drive in different case result_drive = drive_part # Second path is relative to the first if result_path and result_path[-1:] not in seps: result_path = result_path + sep result_path = result_path + path_part # add separator between UNC and non-absolute path colon = matching_string(base_path, ":") if ( result_path and result_path[:1] not in seps and result_drive and result_drive[-1:] != colon ): return result_drive + sep + result_path return result_drive + result_path def joinpaths(self, *paths: AnyStr) -> AnyStr: """Mimic os.path.join using the specified path_separator. Args: *paths: (str) Zero or more paths to join. Returns: (str) The paths joined by the path separator, starting with the last absolute path in paths. """ file_paths = [os.fspath(path) for path in paths] if len(file_paths) == 1: return paths[0] if self.is_windows_fs: return self._join_paths_with_drive_support(*file_paths) path = file_paths[0] sep = self.get_path_separator(file_paths[0]) for path_segment in file_paths[1:]: if path_segment.startswith(sep) or not path: # An absolute path path = path_segment elif path.endswith(sep): path += path_segment else: path += sep + path_segment return path @overload def _path_components(self, path: str) -> List[str]: ... @overload def _path_components(self, path: bytes) -> List[bytes]: ... def _path_components(self, path: AnyStr) -> List[AnyStr]: """Breaks the path into a list of component names. Does not include the root directory as a component, as all paths are considered relative to the root directory for the FakeFilesystem. Callers should basically follow this pattern: .. code:: python file_path = self.absnormpath(file_path) path_components = self._path_components(file_path) current_dir = self.root for component in path_components: if component not in current_dir.entries: raise OSError _do_stuff_with_component(current_dir, component) current_dir = current_dir.get_entry(component) Args: path: Path to tokenize. Returns: The list of names split from path. """ if not path or path == self.get_path_separator(path): return [] drive, path = self.splitdrive(path) sep = self.get_path_separator(path) # handle special case of Windows emulated under POSIX if self.is_windows_fs and sys.platform != "win32": path = path.replace(matching_string(sep, "\\"), sep) path_components = path.split(sep) assert drive or path_components if not path_components[0]: if len(path_components) > 1 and not path_components[1]: path_components = [] else: # This is an absolute path. path_components = path_components[1:] if drive: path_components.insert(0, drive) return path_components def starts_with_drive_letter(self, file_path: AnyStr) -> bool: """Return True if file_path starts with a drive letter. Args: file_path: the full path to be examined. Returns: `True` if drive letter support is enabled in the filesystem and the path starts with a drive letter. """ colon = matching_string(file_path, ":") if len(file_path) >= 2 and file_path[0:1].isalpha() and file_path[1:2] == colon: if self.is_windows_fs: return True if os.name == "nt": # special case if we are emulating Posix under Windows # check if the path exists because it has been mapped in # this is not foolproof, but handles most cases try: if len(file_path) == 2: # avoid recursion, check directly in the entries return any( [ entry.upper() == file_path.upper() for entry in self.root_dir.entries ] ) self.get_object_from_normpath(file_path) return True except OSError: return False return False def _starts_with_root_path(self, file_path: AnyStr) -> bool: root_name = matching_string(file_path, self.root.name) file_path = self._normalize_path_sep(file_path) return ( file_path.startswith(root_name) or not self.is_case_sensitive and file_path.lower().startswith(root_name.lower()) or self.starts_with_drive_letter(file_path) ) def replace_windows_root(self, path: AnyStr) -> AnyStr: """In windows, if a path starts with a single separator, it points to the root dir of the current mount point, usually a drive - replace it with that mount point path to get the real path. """ if path and self.is_windows_fs and self.root_dir: sep = self.get_path_separator(path) # ignore UNC paths if path[0:1] == sep and (len(path) == 1 or path[1:2] != sep): # check if we already have a mount point for that path for root_path in self.mount_points: root_path = matching_string(path, root_path) if path.startswith(root_path): return path # must be a pointer to the current drive - replace it mount_point = matching_string(path, self.root_dir_name) path = mount_point + path[1:] return path def _is_root_path(self, file_path: AnyStr) -> bool: root_name = matching_string(file_path, self.root.name) return file_path == root_name or self.is_mount_point(file_path) def is_mount_point(self, file_path: AnyStr) -> bool: """Return `True` if `file_path` points to a mount point.""" for mount_point in self.mount_points: mount_point = matching_string(file_path, mount_point) if ( file_path == mount_point or not self.is_case_sensitive and file_path.lower() == mount_point.lower() ): return True if ( self.is_windows_fs and len(file_path) == 3 and len(mount_point) == 2 and self.starts_with_drive_letter(file_path) and file_path[:2].lower() == mount_point.lower() ): return True return False def ends_with_path_separator(self, path: Union[int, AnyPath]) -> bool: """Return True if ``file_path`` ends with a valid path separator.""" if isinstance(path, int): return False file_path = make_string_path(path) if not file_path: return False sep = self.get_path_separator(file_path) altsep = self._alternative_path_separator(file_path) return file_path not in (sep, altsep) and ( file_path.endswith(sep) or altsep is not None and file_path.endswith(altsep) ) def is_filepath_ending_with_separator(self, path: AnyStr) -> bool: if not self.ends_with_path_separator(path): return False return self.isfile(self._path_without_trailing_separators(path)) def _directory_content( self, directory: FakeDirectory, component: str ) -> Tuple[Optional[str], Optional[AnyFile]]: if not isinstance(directory, FakeDirectory): return None, None if component in directory.entries: return component, directory.entries[component] if not self.is_case_sensitive: matching_content = [ (subdir, directory.entries[subdir]) for subdir in directory.entries if subdir.lower() == component.lower() ] if matching_content: return matching_content[0] return None, None def exists(self, file_path: AnyPath, check_link: bool = False) -> bool: """Return true if a path points to an existing file system object. Args: file_path: The path to examine. check_link: If True, links are not followed Returns: (bool) True if the corresponding object exists. Raises: TypeError: if file_path is None. """ if check_link and self.islink(file_path): return True path = to_string(self.make_string_path(file_path)) if path is None: raise TypeError if not path: return False if path == self.devnull: return not self.is_windows_fs or sys.version_info >= (3, 8) try: if self.is_filepath_ending_with_separator(path): return False path = self.resolve_path(path) except OSError: return False if self._is_root_path(path): return True path_components: List[str] = self._path_components(path) current_dir = self.root for component in path_components: directory = self._directory_content(current_dir, to_string(component))[1] if directory is None: return False current_dir = cast(FakeDirectory, directory) return True def resolve_path(self, file_path: AnyStr, allow_fd: bool = False) -> AnyStr: """Follow a path, resolving symlinks. ResolvePath traverses the filesystem along the specified file path, resolving file names and symbolic links until all elements of the path are exhausted, or we reach a file which does not exist. If all the elements are not consumed, they just get appended to the path resolved so far. This gives us the path which is as resolved as it can be, even if the file does not exist. This behavior mimics Unix semantics, and is best shown by example. Given a file system that looks like this: /a/b/ /a/b/c -> /a/b2 c is a symlink to /a/b2 /a/b2/x /a/c -> ../d /a/x -> y Then: /a/b/x => /a/b/x /a/c => /a/d /a/x => /a/y /a/b/c/d/e => /a/b2/d/e Args: file_path: The path to examine. allow_fd: If `True`, `file_path` may be open file descriptor. Returns: The resolved_path (str or byte). Raises: TypeError: if `file_path` is `None`. OSError: if `file_path` is '' or a part of the path doesn't exist. """ if allow_fd and isinstance(file_path, int): return self.get_open_file(file_path).get_object().path path = make_string_path(file_path) if path is None: # file.open(None) raises TypeError, so mimic that. raise TypeError("Expected file system path string, received None") if sys.platform == "win32" and self.os != OSType.WINDOWS: path = path.replace( matching_string(path, os.sep), matching_string(path, self.path_separator), ) if not path or not self._valid_relative_path(path): # file.open('') raises OSError, so mimic that, and validate that # all parts of a relative path exist. self.raise_os_error(errno.ENOENT, path) path = self.absnormpath(self._original_path(path)) path = self.replace_windows_root(path) if self._is_root_path(path): return path if path == matching_string(path, self.devnull): return path path_components = self._path_components(path) resolved_components = self._resolve_components(path_components) path = self._components_to_path(resolved_components) # after resolving links, we have to check again for Windows root return self.replace_windows_root(path) # pytype: disable=bad-return-type def _components_to_path(self, component_folders): sep = ( self.get_path_separator(component_folders[0]) if component_folders else self.path_separator ) path = sep.join(component_folders) if not self._starts_with_root_path(path): path = sep + path return path def _resolve_components(self, components: List[AnyStr]) -> List[str]: current_dir = self.root link_depth = 0 path_components = [to_string(comp) for comp in components] resolved_components: List[str] = [] while path_components: component = path_components.pop(0) resolved_components.append(component) directory = self._directory_content(current_dir, component)[1] if directory is None: # The component of the path at this point does not actually # exist in the folder. We can't resolve the path any more. # It is legal to link to a file that does not yet exist, so # rather than raise an error, we just append the remaining # components to what return path we have built so far and # return that. resolved_components.extend(path_components) break # Resolve any possible symlinks in the current path component. elif S_ISLNK(directory.st_mode): # This link_depth check is not really meant to be an accurate # check. It is just a quick hack to prevent us from looping # forever on cycles. if link_depth > _MAX_LINK_DEPTH: self.raise_os_error( errno.ELOOP, self._components_to_path(resolved_components), ) link_path = self._follow_link(resolved_components, directory) # Following the link might result in the complete replacement # of the current_dir, so we evaluate the entire resulting path. target_components = self._path_components(link_path) path_components = target_components + path_components resolved_components = [] current_dir = self.root link_depth += 1 else: current_dir = cast(FakeDirectory, directory) return resolved_components def _valid_relative_path(self, file_path: AnyStr) -> bool: if self.is_windows_fs: return True slash_dotdot = matching_string(file_path, self.path_separator + "..") while file_path and slash_dotdot in file_path: file_path = file_path[: file_path.rfind(slash_dotdot)] if not self.exists(self.absnormpath(file_path)): return False return True def _follow_link(self, link_path_components: List[str], link: AnyFile) -> str: """Follow a link w.r.t. a path resolved so far. The component is either a real file, which is a no-op, or a symlink. In the case of a symlink, we have to modify the path as built up so far /a/b => ../c should yield /a/../c (which will normalize to /a/c) /a/b => x should yield /a/x /a/b => /x/y/z should yield /x/y/z The modified path may land us in a new spot which is itself a link, so we may repeat the process. Args: link_path_components: The resolved path built up to the link so far. link: The link object itself. Returns: (string) The updated path resolved after following the link. Raises: OSError: if there are too many levels of symbolic link """ link_path = link.contents if link_path is not None: # ignore UNC prefix for local files if self.is_windows_fs and link_path.startswith("\\\\?\\"): link_path = link_path[4:] sep = self.get_path_separator(link_path) # For links to absolute paths, we want to throw out everything # in the path built so far and replace with the link. For relative # links, we have to append the link to what we have so far, if not self._starts_with_root_path(link_path): # Relative path. Append remainder of path to what we have # processed so far, excluding the name of the link itself. # /a/b => ../c should yield /a/../c # (which will normalize to /c) # /a/b => d should yield a/d components = link_path_components[:-1] components.append(link_path) link_path = sep.join(components) # Don't call self.NormalizePath(), as we don't want to prepend # self.cwd. return self.normpath(link_path) # pytype: disable=bad-return-type raise ValueError("Invalid link") def get_object_from_normpath( self, file_path: AnyPath, check_read_perm: bool = True, check_exe_perm: bool = True, check_owner: bool = False, ) -> AnyFile: """Search for the specified filesystem object within the fake filesystem. Args: file_path: Specifies target FakeFile object to retrieve, with a path that has already been normalized/resolved. check_read_perm: If True, raises OSError if a parent directory does not have read permission check_exe_perm: If True, raises OSError if a parent directory does not have execute (e.g. search) permission check_owner: If True, and check_read_perm is also True, only checks read permission if the current user id is different from the file object user id Returns: The FakeFile object corresponding to file_path. Raises: OSError: if the object is not found. """ path = make_string_path(file_path) if path == matching_string(path, self.root.name): return self.root if path == matching_string(path, self.devnull): return self.dev_null path = self._original_path(path) path_components = self._path_components(path) target = self.root try: for component in path_components: if S_ISLNK(target.st_mode): if target.contents: target = cast(FakeDirectory, self.resolve(target.contents)) if not S_ISDIR(target.st_mode): if not self.is_windows_fs: self.raise_os_error(errno.ENOTDIR, path) self.raise_os_error(errno.ENOENT, path) target = target.get_entry(component) # type: ignore if ( not is_root() and (check_read_perm or check_exe_perm) and target and not self._can_read( target, check_read_perm, check_exe_perm, check_owner ) ): self.raise_os_error(errno.EACCES, target.path) except KeyError: self.raise_os_error(errno.ENOENT, path) return target @staticmethod def _can_read(target, check_read_perm, check_exe_perm, owner_can_read): if owner_can_read and target.st_uid == helpers.get_uid(): return True permission = helpers.PERM_READ if check_read_perm else 0 if S_ISDIR(target.st_mode) and check_exe_perm: permission |= helpers.PERM_EXE if not permission: return True return target.has_permission(permission) def _get_object( self, file_path: AnyPath, check_read_perm: bool = True, check_exe_perm: bool = True, ) -> FakeFile: """Search for the specified filesystem object within the fake filesystem. By default, consider read and execute permissions. Args: file_path: Specifies the target :py:class:`FakeFile` object to retrieve. check_read_perm: If True, raises OSError if a parent directory does not have read permission check_exe_perm: If True, raises OSError if a parent directory does not have execute (e.g. search) permission Returns: The :py:class:`FakeFile` object corresponding to `file_path`. Raises: OSError: if the object is not found. """ path = make_string_path(file_path) path = self.absnormpath(self._original_path(path)) return self.get_object_from_normpath(path, check_read_perm, check_exe_perm) def get_object(self, file_path: AnyPath) -> FakeFile: """Search for the specified filesystem object within the fake filesystem and return it. Ignore any read permissions. Args: file_path: Specifies the target :py:class:`FakeFile` object to retrieve. Returns: The :py:class:`FakeFile` object corresponding to `file_path`. Raises: OSError: if the object is not found. """ return self._get_object(file_path, check_read_perm=False, check_exe_perm=False) def resolve( self, file_path: Union[AnyStr, int], follow_symlinks: bool = True, allow_fd: bool = False, check_read_perm: bool = True, check_exe_perm: bool = True, check_owner: bool = False, ) -> FakeFile: """Search for the specified filesystem object, resolving all links. Args: file_path: Specifies the target FakeFile object to retrieve. follow_symlinks: If `False`, the link itself is resolved, otherwise the object linked to. allow_fd: If `True`, `file_path` may be an open file descriptor check_read_perm: If True, raises OSError if a parent directory does not have read permission check_read_perm: If True, raises OSError if a parent directory does not have execute permission check_owner: If True, and check_read_perm is also True, only checks read permission if the current user id is different from the file object user id Returns: The FakeFile object corresponding to `file_path`. Raises: OSError: if the object is not found. """ if isinstance(file_path, int): if allow_fd: open_file = self.get_open_file(file_path).get_object() assert isinstance(open_file, FakeFile) return open_file raise TypeError("path should be string, bytes or os.PathLike, not int") if follow_symlinks: return self.get_object_from_normpath( self.resolve_path(file_path, allow_fd), check_read_perm, check_exe_perm, check_owner, ) return self.lresolve(file_path) def lresolve(self, path: AnyPath) -> FakeFile: """Search for the specified object, resolving only parent links. This is analogous to the stat/lstat difference. This resolves links *to* the object but not of the final object itself. Args: path: Specifies target FakeFile object to retrieve. Returns: The FakeFile object corresponding to path. Raises: OSError: if the object is not found. """ path_str = make_string_path(path) if not path_str: raise OSError(errno.ENOENT, path_str) if path_str == matching_string(path_str, self.root.name): # The root directory will never be a link return self.root # remove trailing separator path_str = self._path_without_trailing_separators(path_str) if path_str == matching_string(path_str, "."): path_str = matching_string(path_str, self.cwd) path_str = self._original_path(path_str) parent_directory, child_name = self.splitpath(path_str) if not parent_directory: parent_directory = matching_string(path_str, self.cwd) try: parent_obj = self.resolve(parent_directory) assert parent_obj if not isinstance(parent_obj, FakeDirectory): if not self.is_windows_fs and isinstance(parent_obj, FakeFile): self.raise_os_error(errno.ENOTDIR, path_str) self.raise_os_error(errno.ENOENT, path_str) if not parent_obj.has_permission(helpers.PERM_READ): self.raise_os_error(errno.EACCES, parent_directory) return ( parent_obj.get_entry(to_string(child_name)) if child_name else parent_obj ) except KeyError: pass raise OSError(errno.ENOENT, path_str) def add_object(self, file_path: AnyStr, file_object: AnyFile) -> None: """Add a fake file or directory into the filesystem at file_path. Args: file_path: The path to the file to be added relative to self. file_object: File or directory to add. Raises: OSError: if file_path does not correspond to a directory. """ if not file_path: target_directory = self.root_dir else: target_directory = cast( FakeDirectory, self.resolve(file_path, check_read_perm=False, check_exe_perm=True), ) if not S_ISDIR(target_directory.st_mode): error = errno.ENOENT if self.is_windows_fs else errno.ENOTDIR self.raise_os_error(error, file_path) target_directory.add_entry(file_object) def rename( self, old_file_path: AnyPath, new_file_path: AnyPath, force_replace: bool = False, ) -> None: """Renames a FakeFile object at old_file_path to new_file_path, preserving all properties. Args: old_file_path: Path to filesystem object to rename. new_file_path: Path to where the filesystem object will live after this call. force_replace: If set and destination is an existing file, it will be replaced even under Windows if the user has permissions, otherwise replacement happens under Unix only. Raises: OSError: if old_file_path does not exist. OSError: if new_file_path is an existing directory (Windows, or Posix if old_file_path points to a regular file) OSError: if old_file_path is a directory and new_file_path a file OSError: if new_file_path is an existing file and force_replace not set (Windows only). OSError: if new_file_path is an existing file and could not be removed (Posix, or Windows with force_replace set). OSError: if dirname(new_file_path) does not exist. OSError: if the file would be moved to another filesystem (e.g. mount point). """ old_path = make_string_path(old_file_path) new_path = make_string_path(new_file_path) ends_with_sep = self.ends_with_path_separator(old_path) old_path = self.absnormpath(old_path) new_path = self.absnormpath(new_path) if not self.exists(old_path, check_link=True): self.raise_os_error(errno.ENOENT, old_path, 2) if ends_with_sep: self._handle_broken_link_with_trailing_sep(old_path) old_object = self.lresolve(old_path) if not self.is_windows_fs: self._handle_posix_dir_link_errors(new_path, old_path, ends_with_sep) if self.exists(new_path, check_link=True): renamed_path = self._rename_to_existing_path( force_replace, new_path, old_path, old_object, ends_with_sep ) if renamed_path is None: return else: new_path = renamed_path old_dir, old_name = self.splitpath(old_path) new_dir, new_name = self.splitpath(new_path) if not self.exists(new_dir): self.raise_os_error(errno.ENOENT, new_dir) old_dir_object = self.resolve(old_dir) new_dir_object = self.resolve(new_dir) if old_dir_object.st_dev != new_dir_object.st_dev: self.raise_os_error(errno.EXDEV, old_path) if not S_ISDIR(new_dir_object.st_mode): self.raise_os_error( errno.EACCES if self.is_windows_fs else errno.ENOTDIR, new_path ) if new_dir_object.has_parent_object(old_object): self.raise_os_error(errno.EINVAL, new_path) self._do_rename(old_dir_object, old_name, new_dir_object, new_name) def _do_rename(self, old_dir_object, old_name, new_dir_object, new_name): object_to_rename = old_dir_object.get_entry(old_name) old_dir_object.remove_entry(old_name, recursive=False) object_to_rename.name = new_name new_name = new_dir_object._normalized_entryname(new_name) old_entry = ( new_dir_object.get_entry(new_name) if new_name in new_dir_object.entries else None ) try: if old_entry: # in case of overwriting remove the old entry first new_dir_object.remove_entry(new_name) new_dir_object.add_entry(object_to_rename) except OSError: # adding failed, roll back the changes before re-raising if old_entry and new_name not in new_dir_object.entries: new_dir_object.add_entry(old_entry) object_to_rename.name = old_name old_dir_object.add_entry(object_to_rename) raise def _handle_broken_link_with_trailing_sep(self, path: AnyStr) -> None: # note that the check for trailing sep has to be done earlier if self.islink(path): if not self.exists(path): error = ( errno.ENOENT if self.is_macos else errno.EINVAL if self.is_windows_fs else errno.ENOTDIR ) self.raise_os_error(error, path) def _handle_posix_dir_link_errors( self, new_file_path: AnyStr, old_file_path: AnyStr, ends_with_sep: bool ) -> None: if self.isdir(old_file_path, follow_symlinks=False) and self.islink( new_file_path ): self.raise_os_error(errno.ENOTDIR, new_file_path) if self.isdir(new_file_path, follow_symlinks=False) and self.islink( old_file_path ): if ends_with_sep and self.is_macos: return error = errno.ENOTDIR if ends_with_sep else errno.EISDIR self.raise_os_error(error, new_file_path) if ( ends_with_sep and self.islink(old_file_path) and old_file_path == new_file_path and not self.is_windows_fs ): self.raise_os_error(errno.ENOTDIR, new_file_path) def _rename_to_existing_path( self, force_replace: bool, new_file_path: AnyStr, old_file_path: AnyStr, old_object: FakeFile, ends_with_sep: bool, ) -> Optional[AnyStr]: new_object = self._get_object(new_file_path) if old_file_path == new_file_path: if not S_ISLNK(new_object.st_mode) and ends_with_sep: error = errno.EINVAL if self.is_windows_fs else errno.ENOTDIR self.raise_os_error(error, old_file_path) return None # Nothing to do here if old_object == new_object: return self._rename_same_object(new_file_path, old_file_path) if S_ISDIR(new_object.st_mode) or S_ISLNK(new_object.st_mode): self._handle_rename_error_for_dir_or_link( force_replace, new_file_path, new_object, old_object, ends_with_sep, ) elif S_ISDIR(old_object.st_mode): error = errno.EEXIST if self.is_windows_fs else errno.ENOTDIR self.raise_os_error(error, new_file_path) elif self.is_windows_fs and not force_replace: self.raise_os_error(errno.EEXIST, new_file_path) else: self.remove_object(new_file_path) return new_file_path def _handle_rename_error_for_dir_or_link( self, force_replace: bool, new_file_path: AnyStr, new_object: FakeFile, old_object: FakeFile, ends_with_sep: bool, ) -> None: if self.is_windows_fs: if force_replace: self.raise_os_error(errno.EACCES, new_file_path) else: self.raise_os_error(errno.EEXIST, new_file_path) if not S_ISLNK(new_object.st_mode): if new_object.entries: if ( not S_ISLNK(old_object.st_mode) or not ends_with_sep or not self.is_macos ): self.raise_os_error(errno.ENOTEMPTY, new_file_path) if S_ISREG(old_object.st_mode): self.raise_os_error(errno.EISDIR, new_file_path) def _rename_same_object( self, new_file_path: AnyStr, old_file_path: AnyStr ) -> Optional[AnyStr]: do_rename = old_file_path.lower() == new_file_path.lower() if not do_rename: try: real_old_path = self.resolve_path(old_file_path) original_old_path = self._original_path(real_old_path) real_new_path = self.resolve_path(new_file_path) if real_new_path == original_old_path and ( new_file_path == real_old_path ) == (new_file_path.lower() == real_old_path.lower()): real_object = self.resolve(old_file_path, follow_symlinks=False) do_rename = ( os.path.basename(old_file_path) == real_object.name or not self.is_macos ) else: do_rename = real_new_path.lower() == real_old_path.lower() if do_rename: # only case is changed in case-insensitive file # system - do the rename parent, file_name = self.splitpath(new_file_path) new_file_path = self.joinpaths( self._original_path(parent), file_name ) except OSError: # ResolvePath may fail due to symlink loop issues or # similar - in this case just assume the paths # to be different pass if not do_rename: # hard links to the same file - nothing to do return None return new_file_path def remove_object(self, file_path: AnyStr) -> None: """Remove an existing file or directory. Args: file_path: The path to the file relative to self. Raises: OSError: if file_path does not correspond to an existing file, or if part of the path refers to something other than a directory. OSError: if the directory is in use (eg, if it is '/'). """ file_path = self.absnormpath(self._original_path(file_path)) if self._is_root_path(file_path): self.raise_os_error(errno.EBUSY, file_path) try: dirname, basename = self.splitpath(file_path) target_directory = self.resolve(dirname, check_read_perm=False) target_directory.remove_entry(basename) except KeyError: self.raise_os_error(errno.ENOENT, file_path) except AttributeError: self.raise_os_error(errno.ENOTDIR, file_path) def make_string_path(self, path: AnyPath) -> AnyStr: # type: ignore[type-var] path_str = make_string_path(path) os_sep = matching_string(path_str, os.sep) fake_sep = self.get_path_separator(path_str) return path_str.replace(os_sep, fake_sep) # type: ignore[return-value] def create_dir( self, directory_path: AnyPath, perm_bits: int = helpers.PERM_DEF, apply_umask: bool = True, ) -> FakeDirectory: """Create `directory_path` and all the parent directories, and return the created :py:class:`FakeDirectory` object. Helper method to set up your test faster. Args: directory_path: The full directory path to create. perm_bits: The permission bits as set by ``chmod``. apply_umask: If `True` (default), the current umask is applied to `perm_bits`. Returns: The newly created :py:class:`FakeDirectory` object. Raises: OSError: if the directory already exists. """ dir_path = self.make_string_path(directory_path) dir_path = self.absnormpath(dir_path) self._auto_mount_drive_if_needed(dir_path) if self.exists(dir_path, check_link=True) and dir_path not in self.mount_points: self.raise_os_error(errno.EEXIST, dir_path) path_components = self._path_components(dir_path) current_dir = self.root new_dirs = [] for component in [to_string(p) for p in path_components]: directory = self._directory_content(current_dir, to_string(component))[1] if not directory: new_dir = FakeDirectory(component, filesystem=self) new_dirs.append(new_dir) if self.is_windows_fs and current_dir == self.root: current_dir = self.root_dir current_dir.add_entry(new_dir) current_dir = new_dir else: if S_ISLNK(directory.st_mode): assert directory.contents directory = self.resolve(directory.contents) assert directory current_dir = cast(FakeDirectory, directory) if directory.st_mode & S_IFDIR != S_IFDIR: self.raise_os_error(errno.ENOTDIR, current_dir.path) # set the permission after creating the directories # to allow directory creation inside a read-only directory for new_dir in new_dirs: if apply_umask: perm_bits &= ~self.umask new_dir.st_mode = S_IFDIR | perm_bits return current_dir def create_file( self, file_path: AnyPath, st_mode: int = S_IFREG | helpers.PERM_DEF_FILE, contents: AnyString = "", st_size: Optional[int] = None, create_missing_dirs: bool = True, apply_umask: bool = True, encoding: Optional[str] = None, errors: Optional[str] = None, side_effect: Optional[Callable] = None, ) -> FakeFile: """Create `file_path`, including all the parent directories along the way, and return the created :py:class:`FakeFile` object. This helper method can be used to set up tests more easily. Args: file_path: The path to the file to create. st_mode: The `stat` constant representing the file type. contents: the contents of the file. If not given and `st_size` is `None`, an empty file is assumed. st_size: file size; only valid if contents not given. If given, the file is considered to be in "large file mode" and trying to read from or write to the file will result in an exception. create_missing_dirs: If `True`, auto create missing directories. apply_umask: If `True` (default), the current umask is applied to `st_mode`. encoding: If `contents` is of type `str`, the encoding used for serialization. errors: The error mode used for encoding/decoding errors. side_effect: function handle that is executed when the file is written, must accept the file object as an argument. Returns: The newly created :py:class:`FakeFile` object. Raises: OSError: if the file already exists. OSError: if the containing directory is required and missing. """ return self.create_file_internally( file_path, st_mode, contents, st_size, create_missing_dirs, apply_umask, encoding, errors, side_effect=side_effect, ) def add_real_file( self, source_path: AnyPath, read_only: bool = True, target_path: Optional[AnyPath] = None, ) -> FakeFile: """Create `file_path`, including all the parent directories along the way, for an existing real file, and return the created :py:class:`FakeFile` object. The contents of the real file are read only on demand. Args: source_path: Path to an existing file in the real file system read_only: If `True` (the default), writing to the fake file raises an exception. Otherwise, writing to the file changes the fake file only. target_path: If given, the path of the target direction, otherwise it is equal to `source_path`. Returns: the newly created :py:class:`FakeFile` object. Raises: OSError: if the file does not exist in the real file system. OSError: if the file already exists in the fake file system. .. note:: On most systems, accessing the fake file's contents may update both the real and fake files' `atime` (access time). In this particular case, `add_real_file()` violates the rule that `pyfakefs` must not modify the real file system. """ target_path = target_path or source_path source_path_str = make_string_path(source_path) real_stat = os.stat(source_path_str) fake_file = self.create_file_internally(target_path, read_from_real_fs=True) # for read-only mode, remove the write/executable permission bits fake_file.stat_result.set_from_stat_result(real_stat) if read_only: fake_file.st_mode &= 0o777444 fake_file.file_path = source_path_str self.change_disk_usage(fake_file.size, fake_file.name, fake_file.st_dev) return fake_file def add_real_symlink( self, source_path: AnyPath, target_path: Optional[AnyPath] = None ) -> FakeFile: """Create a symlink at `source_path` (or `target_path`, if given) and return the created :py:class:`FakeFile` object. It will point to the same path as the symlink on the real filesystem. Relative symlinks will point relative to their new location. Absolute symlinks will point to the same, absolute path as on the real filesystem. Args: source_path: The path to the existing symlink. target_path: If given, the name of the symlink in the fake filesystem, otherwise, the same as `source_path`. Returns: the newly created :py:class:`FakeFile` object. Raises: OSError: if the directory does not exist in the real file system. OSError: if the symlink could not be created (see :py:meth:`create_file`). OSError: if the directory already exists in the fake file system. """ source_path_str = make_string_path(source_path) # TODO: add test source_path_str = self._path_without_trailing_separators(source_path_str) if not os.path.exists(source_path_str) and not os.path.islink(source_path_str): self.raise_os_error(errno.ENOENT, source_path_str) target = os.readlink(source_path_str) if target_path: return self.create_symlink(target_path, target) else: return self.create_symlink(source_path_str, target) def add_real_directory( self, source_path: AnyPath, read_only: bool = True, lazy_read: bool = True, target_path: Optional[AnyPath] = None, ) -> FakeDirectory: """Create a fake directory corresponding to the real directory at the specified path, and return the created :py:class:`FakeDirectory` object. Add entries in the fake directory corresponding to the entries in the real directory. Symlinks are supported. If the target directory already exists in the fake filesystem, the directory contents are merged. Overwriting existing files is not allowed. Args: source_path: The path to the existing directory. read_only: If set, all files under the directory are treated as read-only, e.g. a write access raises an exception; otherwise, writing to the files changes the fake files only as usually. lazy_read: If set (default), directory contents are only read when accessed, and only until the needed subdirectory level. .. note:: This means that the file system size is only updated at the time the directory contents are read; set this to `False` only if you are dependent on accurate file system size in your test target_path: If given, the target directory, otherwise, the target directory is the same as `source_path`. Returns: the newly created :py:class:`FakeDirectory` object. Raises: OSError: if the directory does not exist in the real filesystem. OSError: if a file or link exists in the fake filesystem where a real file or directory shall be mapped. """ source_path_str = make_string_path(source_path) source_path_str = self._path_without_trailing_separators(source_path_str) if not os.path.exists(source_path_str): self.raise_os_error(errno.ENOENT, source_path_str) target_path_str = make_string_path(target_path or source_path_str) # get rid of inconsistencies between real and fake path separators if os.altsep is not None: target_path_str = os.path.normpath(target_path_str) if os.sep != self.path_separator: target_path_str = target_path_str.replace(os.sep, self.path_separator) self._auto_mount_drive_if_needed(target_path_str) if lazy_read: self._create_fake_from_real_dir_lazily( source_path_str, target_path_str, read_only ) else: self._create_fake_from_real_dir(source_path_str, target_path_str, read_only) return cast(FakeDirectory, self._get_object(target_path_str)) def _create_fake_from_real_dir(self, source_path_str, target_path_str, read_only): if not self.exists(target_path_str): self.create_dir(target_path_str) for base, _, files in os.walk(source_path_str): new_base = os.path.join( target_path_str, os.path.relpath(base, source_path_str), ) for file_entry in os.listdir(base): file_path = os.path.join(base, file_entry) if os.path.islink(file_path): self.add_real_symlink(file_path, os.path.join(new_base, file_entry)) for file_entry in files: path = os.path.join(base, file_entry) if not os.path.islink(path): self.add_real_file( path, read_only, os.path.join(new_base, file_entry) ) def _create_fake_from_real_dir_lazily( self, source_path_str, target_path_str, read_only ): if self.exists(target_path_str): if not self.isdir(target_path_str): raise OSError(errno.ENOTDIR, "Mapping target is not a directory") for entry in os.listdir(source_path_str): src_entry_path = os.path.join(source_path_str, entry) target_entry_path = os.path.join(target_path_str, entry) if os.path.isdir(src_entry_path): self.add_real_directory( src_entry_path, read_only, True, target_entry_path ) elif os.path.islink(src_entry_path): self.add_real_symlink(src_entry_path, target_entry_path) elif os.path.isfile(src_entry_path): self.add_real_file(src_entry_path, read_only, target_entry_path) return self._get_object(target_path_str) parent_path = os.path.split(target_path_str)[0] if self.exists(parent_path): parent_dir = self._get_object(parent_path) else: parent_dir = self.create_dir(parent_path) new_dir = FakeDirectoryFromRealDirectory( source_path_str, self, read_only, target_path_str ) parent_dir.add_entry(new_dir) return new_dir def add_real_paths( self, path_list: List[AnyStr], read_only: bool = True, lazy_dir_read: bool = True, ) -> None: """This convenience method adds multiple files and/or directories from the real file system to the fake file system. See :py:meth:`add_real_file` and :py:meth:`add_real_directory`. Args: path_list: List of file and directory paths in the real file system. read_only: If set, all files and files under the directories are treated as read-only, e.g. a write access raises an exception; otherwise, writing to the files changes the fake files only as usually. lazy_dir_read: Uses lazy reading of directory contents if set (see :py:meth:`add_real_directory`) Raises: OSError: if any of the files and directories in the list does not exist in the real file system. OSError: if a file or link exists in the fake filesystem where a real file or directory shall be mapped. """ for path in path_list: if os.path.isdir(path): self.add_real_directory(path, read_only, lazy_dir_read) else: self.add_real_file(path, read_only) def create_file_internally( self, file_path: AnyPath, st_mode: int = S_IFREG | helpers.PERM_DEF_FILE, contents: AnyString = "", st_size: Optional[int] = None, create_missing_dirs: bool = True, apply_umask: bool = True, encoding: Optional[str] = None, errors: Optional[str] = None, read_from_real_fs: bool = False, side_effect: Optional[Callable] = None, ) -> FakeFile: """Internal fake file creator that supports both normal fake files and fake files based on real files. Args: file_path: path to the file to create. st_mode: the stat.S_IF constant representing the file type. contents: the contents of the file. If not given and st_size is None, an empty file is assumed. st_size: file size; only valid if contents not given. If given, the file is considered to be in "large file mode" and trying to read from or write to the file will result in an exception. create_missing_dirs: if True, auto create missing directories. apply_umask: whether or not the current umask must be applied on st_mode. encoding: if contents is a unicode string, the encoding used for serialization. errors: the error mode used for encoding/decoding errors read_from_real_fs: if True, the contents are read from the real file system on demand. side_effect: function handle that is executed when file is written, must accept the file object as an argument. """ path = self.make_string_path(file_path) path = self.absnormpath(path) if not is_int_type(st_mode): raise TypeError( "st_mode must be of int type - did you mean to set contents?" ) if self.exists(path, check_link=True): self.raise_os_error(errno.EEXIST, path) parent_directory, new_file = self.splitpath(path) if not parent_directory: parent_directory = matching_string(path, self.cwd) self._auto_mount_drive_if_needed(parent_directory) if not self.exists(parent_directory): if not create_missing_dirs: self.raise_os_error(errno.ENOENT, parent_directory) parent_directory = matching_string( path, self.create_dir(parent_directory).path, # type: ignore ) else: parent_directory = self._original_path(parent_directory) if apply_umask: st_mode &= ~self.umask file_object: FakeFile if read_from_real_fs: file_object = FakeFileFromRealFile( to_string(path), filesystem=self, side_effect=side_effect ) else: file_object = FakeFile( new_file, st_mode, filesystem=self, encoding=encoding, errors=errors, side_effect=side_effect, ) self.add_object(parent_directory, file_object) if st_size is None and contents is None: contents = "" if not read_from_real_fs and (contents is not None or st_size is not None): try: if st_size is not None: file_object.set_large_file_size(st_size) else: file_object.set_initial_contents(contents) # type: ignore except OSError: self.remove_object(path) raise return file_object def create_symlink( self, file_path: AnyPath, link_target: AnyPath, create_missing_dirs: bool = True, ) -> FakeFile: """Create the specified symlink, pointed at the specified link target, and return the created :py:class:`FakeFile` object representing the link. Args: file_path: path to the symlink to create link_target: the target of the symlink create_missing_dirs: If `True`, any missing parent directories of `file_path` will be created Returns: The newly created :py:class:`FakeFile` object. Raises: OSError: if the symlink could not be created (see :py:meth:`create_file`). """ link_path = self.make_string_path(file_path) link_target_path = self.make_string_path(link_target) link_path = self.normcase(link_path) # the link path cannot end with a path separator if self.ends_with_path_separator(link_path): if self.exists(link_path): self.raise_os_error(errno.EEXIST, link_path) if self.exists(link_target_path): if not self.is_windows_fs: self.raise_os_error(errno.ENOENT, link_path) else: if self.is_windows_fs: self.raise_os_error(errno.EINVAL, link_target_path) if not self.exists( self._path_without_trailing_separators(link_path), check_link=True, ): self.raise_os_error(errno.ENOENT, link_target_path) if self.is_macos: # to avoid EEXIST exception, remove the link # if it already exists if self.exists(link_path, check_link=True): self.remove_object(link_path) else: self.raise_os_error(errno.EEXIST, link_target_path) # resolve the link path only if it is not a link itself if not self.islink(link_path): link_path = self.resolve_path(link_path) permission = helpers.PERM_DEF_FILE if self.is_windows_fs else helpers.PERM_DEF return self.create_file_internally( link_path, st_mode=S_IFLNK | permission, contents=link_target_path, create_missing_dirs=create_missing_dirs, apply_umask=self.is_macos, ) def create_link( self, old_path: AnyPath, new_path: AnyPath, follow_symlinks: bool = True, create_missing_dirs: bool = True, ) -> FakeFile: """Create a hard link at `new_path`, pointing at `old_path`, and return the created :py:class:`FakeFile` object representing the link. Args: old_path: An existing link to the target file. new_path: The destination path to create a new link at. follow_symlinks: If `False` and `old_path` is a symlink, link the symlink instead of the object it points to. create_missing_dirs: If `True`, any missing parent directories of `file_path` will be created Returns: The :py:class:`FakeFile` object referred to by `old_path`. Raises: OSError: if something already exists at `new_path`. OSError: if `old_path` is a directory. OSError: if the parent directory doesn't exist. """ old_path_str = make_string_path(old_path) new_path_str = make_string_path(new_path) new_path_normalized = self.absnormpath(new_path_str) if self.exists(new_path_normalized, check_link=True): self.raise_os_error(errno.EEXIST, new_path_str) new_parent_directory, new_basename = self.splitpath(new_path_normalized) if not new_parent_directory: new_parent_directory = matching_string(new_path_str, self.cwd) if not self.exists(new_parent_directory): if create_missing_dirs: self.create_dir(new_parent_directory) else: self.raise_os_error(errno.ENOENT, new_parent_directory) if self.ends_with_path_separator(old_path_str): error = errno.EINVAL if self.is_windows_fs else errno.ENOTDIR self.raise_os_error(error, old_path_str) if not self.is_windows_fs and self.ends_with_path_separator(new_path): self.raise_os_error(errno.ENOENT, old_path_str) # Retrieve the target file try: old_file = self.resolve(old_path_str, follow_symlinks=follow_symlinks) except OSError: self.raise_os_error(errno.ENOENT, old_path_str) if old_file.st_mode & S_IFDIR: self.raise_os_error( errno.EACCES if self.is_windows_fs else errno.EPERM, old_path_str, ) # abuse the name field to control the filename of the # newly created link old_file.name = new_basename # type: ignore[assignment] self.add_object(new_parent_directory, old_file) return old_file def link( self, old_path: AnyPath, new_path: AnyPath, follow_symlinks: bool = True, ) -> FakeFile: """Create a hard link at new_path, pointing at old_path. Args: old_path: An existing link to the target file. new_path: The destination path to create a new link at. follow_symlinks: If False and old_path is a symlink, link the symlink instead of the object it points to. Returns: The FakeFile object referred to by old_path. Raises: OSError: if something already exists at new_path. OSError: if old_path is a directory. OSError: if the parent directory doesn't exist. """ return self.create_link( old_path, new_path, follow_symlinks, create_missing_dirs=False ) def _is_circular_link(self, link_obj: FakeFile) -> bool: try: assert link_obj.contents self.resolve_path(link_obj.contents) except OSError as exc: return exc.errno == errno.ELOOP return False def readlink(self, path: AnyPath) -> str: """Read the target of a symlink. Args: path: symlink to read the target of. Returns: the string representing the path to which the symbolic link points. Raises: TypeError: if path is None OSError: (with errno=ENOENT) if path is not a valid path, or (with errno=EINVAL) if path is valid, but is not a symlink, or if the path ends with a path separator (Posix only) """ if path is None: raise TypeError link_path = make_string_path(path) link_obj = self.lresolve(link_path) if S_IFMT(link_obj.st_mode) != S_IFLNK: self.raise_os_error(errno.EINVAL, link_path) if self.ends_with_path_separator(link_path): if not self.is_windows_fs and self.exists(link_path): self.raise_os_error(errno.EINVAL, link_path) if not self.exists(link_obj.path): # type: ignore if self.is_windows_fs: error = errno.EINVAL elif self._is_circular_link(link_obj): if self.is_macos: return link_obj.path # type: ignore[return-value] error = errno.ELOOP else: error = errno.ENOENT self.raise_os_error(error, link_obj.path) assert link_obj.contents return link_obj.contents def makedir(self, dir_path: AnyPath, mode: int = helpers.PERM_DEF) -> None: """Create a leaf Fake directory. Args: dir_path: (str) Name of directory to create. Relative paths are assumed to be relative to '/'. mode: (int) Mode to create directory with. This argument defaults to 0o777. The umask is applied to this mode. Raises: OSError: if the directory name is invalid or parent directory is read only or as per :py:meth:`add_object`. """ dir_name = make_string_path(dir_path) ends_with_sep = self.ends_with_path_separator(dir_name) dir_name = self._path_without_trailing_separators(dir_name) if not dir_name: self.raise_os_error(errno.ENOENT, "") if self.is_windows_fs: dir_name = self.absnormpath(dir_name) parent_dir, rest = self.splitpath(dir_name) if parent_dir: base_dir = self.normpath(parent_dir) ellipsis = matching_string(parent_dir, self.path_separator + "..") if parent_dir.endswith(ellipsis) and not self.is_windows_fs: base_dir, dummy_dotdot, _ = parent_dir.partition(ellipsis) if self.is_windows_fs and not rest and not self.exists(base_dir): # under Windows, the parent dir may be a drive or UNC path # which has to be mounted self._auto_mount_drive_if_needed(parent_dir) if not self.exists(base_dir): self.raise_os_error(errno.ENOENT, base_dir) dir_name = self.absnormpath(dir_name) if self.exists(dir_name, check_link=True): if self.is_windows_fs and dir_name == self.root_dir_name: error_nr = errno.EACCES else: error_nr = errno.EEXIST if ends_with_sep and self.is_macos and not self.exists(dir_name): # to avoid EEXIST exception, remove the link self.remove_object(dir_name) else: self.raise_os_error(error_nr, dir_name) head, tail = self.splitpath(dir_name) self.add_object( to_string(head), FakeDirectory(to_string(tail), mode & ~self.umask, filesystem=self), ) def _path_without_trailing_separators(self, path: AnyStr) -> AnyStr: while self.ends_with_path_separator(path): path = path[:-1] return path def makedirs( self, dir_name: AnyStr, mode: int = helpers.PERM_DEF, exist_ok: bool = False ) -> None: """Create a leaf Fake directory and create any non-existent parent dirs. Args: dir_name: (str) Name of directory to create. mode: (int) Mode to create directory (and any necessary parent directories) with. This argument defaults to 0o777. The umask is applied to this mode. exist_ok: (boolean) If exist_ok is False (the default), an OSError is raised if the target directory already exists. Raises: OSError: if the directory already exists and exist_ok=False, or as per :py:meth:`create_dir`. """ if not dir_name: self.raise_os_error(errno.ENOENT, "") ends_with_sep = self.ends_with_path_separator(dir_name) dir_name = self.absnormpath(dir_name) if ( ends_with_sep and self.is_macos and self.exists(dir_name, check_link=True) and not self.exists(dir_name) ): # to avoid EEXIST exception, remove the link self.remove_object(dir_name) dir_name_str = to_string(dir_name) path_components = self._path_components(dir_name_str) # Raise a permission denied error if the first existing directory # is not writeable. current_dir = self.root_dir for component in path_components: if ( not hasattr(current_dir, "entries") or component not in current_dir.entries ): break else: current_dir = cast(FakeDirectory, current_dir.entries[component]) try: self.create_dir(dir_name, mode) except OSError as e: if e.errno == errno.EACCES: # permission denied - propagate exception raise if not exist_ok or not isinstance(self.resolve(dir_name), FakeDirectory): if self.is_windows_fs and e.errno == errno.ENOTDIR: e.errno = errno.ENOENT # mypy thinks that errno may be None self.raise_os_error(cast(int, e.errno), e.filename) def _is_of_type( self, path: AnyPath, st_flag: int, follow_symlinks: bool = True, ) -> bool: """Helper function to implement isdir(), islink(), etc. See the stat(2) man page for valid stat.S_I* flag values Args: path: Path to file to stat and test st_flag: The stat.S_I* flag checked for the file's st_mode follow_symlinks: If `False` and path points to a symlink, the link itself is checked instead of the linked object. Returns: (boolean) `True` if the st_flag is set in path's st_mode. Raises: TypeError: if path is None """ if path is None: raise TypeError file_path = make_string_path(path) try: obj = self.resolve(file_path, follow_symlinks, check_read_perm=False) if obj: self.raise_for_filepath_ending_with_separator( file_path, obj, macos_handling=not follow_symlinks ) return S_IFMT(obj.st_mode) == st_flag except OSError: return False return False def isdir(self, path: AnyPath, follow_symlinks: bool = True) -> bool: """Determine if path identifies a directory. Args: path: Path to filesystem object. follow_symlinks: If `False` and path points to a symlink, the link itself is checked instead of the linked object. Returns: `True` if path points to a directory (following symlinks). Raises: TypeError: if path is None. """ return self._is_of_type(path, S_IFDIR, follow_symlinks) def isfile(self, path: AnyPath, follow_symlinks: bool = True) -> bool: """Determine if path identifies a regular file. Args: path: Path to filesystem object. follow_symlinks: If `False` and path points to a symlink, the link itself is checked instead of the linked object. Returns: `True` if path points to a regular file (following symlinks). Raises: TypeError: if path is None. """ return self._is_of_type(path, S_IFREG, follow_symlinks) def islink(self, path: AnyPath) -> bool: """Determine if path identifies a symbolic link. Args: path: Path to filesystem object. Returns: `True` if path points to a symlink (S_IFLNK set in st_mode) Raises: TypeError: if path is None. """ return self._is_of_type(path, S_IFLNK, follow_symlinks=False) if sys.version_info >= (3, 12): def isjunction(self, path: AnyPath) -> bool: """Returns False. Junctions are never faked.""" return False def confirmdir( self, target_directory: AnyStr, check_read_perm: bool = True, check_exe_perm: bool = True, check_owner: bool = False, ) -> FakeDirectory: """Test that the target is actually a directory, raising OSError if not. Args: target_directory: Path to the target directory within the fake filesystem. check_read_perm: If True, raises OSError if the directory does not have read permission check_exe_perm: If True, raises OSError if the directory does not have execute (e.g. search) permission check_owner: If True, only checks read permission if the current user id is different from the file object user id Returns: The FakeDirectory object corresponding to target_directory. Raises: OSError: if the target is not a directory. """ directory = cast( FakeDirectory, self.resolve( target_directory, check_read_perm=check_read_perm, check_exe_perm=check_exe_perm, check_owner=check_owner, ), ) if not directory.st_mode & S_IFDIR: self.raise_os_error(errno.ENOTDIR, target_directory, 267) return directory def remove(self, path: AnyStr) -> None: """Remove the FakeFile object at the specified file path. Args: path: Path to file to be removed. Raises: OSError: if path points to a directory. OSError: if path does not exist. OSError: if removal failed. """ norm_path = make_string_path(path) norm_path = self.absnormpath(norm_path) if self.ends_with_path_separator(path): self._handle_broken_link_with_trailing_sep(norm_path) if self.exists(norm_path): obj = self.resolve(norm_path, check_read_perm=False) if S_IFMT(obj.st_mode) == S_IFDIR: link_obj = self.lresolve(norm_path) if S_IFMT(link_obj.st_mode) != S_IFLNK: if self.is_windows_fs: error = errno.EACCES elif self.is_macos: error = errno.EPERM else: error = errno.EISDIR self.raise_os_error(error, norm_path) if path.endswith(self.get_path_separator(path)): if self.is_windows_fs: error = errno.EACCES elif self.is_macos: error = errno.EPERM else: error = errno.ENOTDIR self.raise_os_error(error, norm_path) else: self.raise_for_filepath_ending_with_separator(path, obj) self.remove_object(norm_path) def rmdir(self, target_directory: AnyStr, allow_symlink: bool = False) -> None: """Remove a leaf Fake directory. Args: target_directory: (str) Name of directory to remove. allow_symlink: (bool) if `target_directory` is a symlink, the function just returns, otherwise it raises (Posix only) Raises: OSError: if target_directory does not exist. OSError: if target_directory does not point to a directory. OSError: if removal failed per FakeFilesystem.RemoveObject. Cannot remove '.'. """ if target_directory == matching_string(target_directory, "."): error_nr = errno.EACCES if self.is_windows_fs else errno.EINVAL self.raise_os_error(error_nr, target_directory) ends_with_sep = self.ends_with_path_separator(target_directory) target_directory = self.absnormpath(target_directory) if self.confirmdir(target_directory, check_owner=True): if not self.is_windows_fs and self.islink(target_directory): if allow_symlink: return if not ends_with_sep or not self.is_macos: self.raise_os_error(errno.ENOTDIR, target_directory) dir_object = self.resolve(target_directory, check_owner=True) if dir_object.entries: self.raise_os_error(errno.ENOTEMPTY, target_directory) self.remove_object(target_directory) def listdir(self, target_directory: AnyStr) -> List[AnyStr]: """Return a list of file names in target_directory. Args: target_directory: Path to the target directory within the fake filesystem. Returns: A list of file names within the target directory in arbitrary order. If `shuffle_listdir_results` is set, the order is not the same in subsequent calls to avoid tests relying on any ordering. Raises: OSError: if the target is not a directory. """ if target_directory is None: return [] target_directory = self.resolve_path(target_directory, allow_fd=True) directory = self.confirmdir(target_directory, check_exe_perm=False) directory_contents = list(directory.entries.keys()) if self.shuffle_listdir_results: random.shuffle(directory_contents) return directory_contents # type: ignore[return-value] def __str__(self) -> str: return str(self.root_dir) if sys.version_info >= (3, 13): # used for emulating Windows _WIN_RESERVED_NAMES = frozenset( {"CON", "PRN", "AUX", "NUL", "CONIN$", "CONOUT$"} | {f"COM{c}" for c in "123456789\xb9\xb2\xb3"} | {f"LPT{c}" for c in "123456789\xb9\xb2\xb3"} ) _WIN_RESERVED_CHARS = frozenset( {chr(i) for i in range(32)} | {'"', "*", ":", "<", ">", "?", "|", "/", "\\"} ) def isreserved(self, path): if not self.is_windows_fs: return False def is_reserved_name(name): if sys.platform == "win32": from os.path import _isreservedname # type: ignore[import-error] return _isreservedname(name) if name[-1:] in (".", " "): return name not in (".", "..") if self._WIN_RESERVED_CHARS.intersection(name): return True name = name.partition(".")[0].rstrip(" ").upper() return name in self._WIN_RESERVED_NAMES path = os.fsdecode(self.splitroot(path)[2]) if self.alternative_path_separator is not None: path = path.replace( self.alternative_path_separator, self.path_separator ) return any( is_reserved_name(name) for name in reversed(path.split(self.path_separator)) ) def _add_standard_streams(self) -> None: self.add_open_file(StandardStreamWrapper(sys.stdin)) self.add_open_file(StandardStreamWrapper(sys.stdout)) self.add_open_file(StandardStreamWrapper(sys.stderr)) def _tempdir_name(self): """This logic is extracted from tempdir._candidate_tempdir_list. We cannot rely on tempdir.gettempdir() in an empty filesystem, as it tries to write to the filesystem to ensure that the tempdir is valid. """ # reset the cached tempdir in tempfile tempfile.tempdir = None for env_name in "TMPDIR", "TEMP", "TMP": dir_name = os.getenv(env_name) if dir_name: return dir_name # we have to check the real OS temp path here, as this is what # tempfile assumes if os.name == "nt": return os.path.expanduser(r"~\AppData\Local\Temp") return "/tmp" def _create_temp_dir(self): # the temp directory is assumed to exist at least in `tempfile`, # so we create it here for convenience temp_dir = self._tempdir_name() if not self.exists(temp_dir): self.create_dir(temp_dir) if sys.platform != "win32" and not self.exists("/tmp"): # under Posix, we also create a link in /tmp if the path does not exist self.create_symlink("/tmp", temp_dir) # reset the used size to 0 to avoid having the link size counted # which would make disk size tests more complicated next(iter(self.mount_points.values()))["used_size"] = 0 def _run_doctest() -> TestResults: import doctest import pyfakefs return doctest.testmod(pyfakefs.fake_filesystem) def __getattr__(name): # backwards compatibility for read access to globals moved to helpers if name == "USER_ID": return helpers.USER_ID if name == "GROUP_ID": return helpers.GROUP_ID raise AttributeError(f"No attribute {name!r}.") if __name__ == "__main__": _run_doctest() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/fake_filesystem_shutil.py0000755000175100001660000000725414764107375021714 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """A fake shutil module implementation that uses fake_filesystem for unit tests. Note that only `shutildisk_usage()` is faked, the rest of the functions shall work fine with the fake file system if `os`/`os.path` are patched. :Includes: FakeShutil: Uses a FakeFilesystem to provide a fake replacement for the shutil module. :Usage: The fake implementation is automatically involved if using `fake_filesystem_unittest.TestCase`, pytest fs fixture, or directly `Patcher`. """ import os import shutil import sys class FakeShutilModule: """Uses a FakeFilesystem to provide a fake replacement for shutil module. """ @staticmethod def dir(): """Return the list of patched function names. Used for patching functions imported from the module. """ return ("disk_usage",) def __init__(self, filesystem): """Construct fake shutil module using the fake filesystem. Args: filesystem: FakeFilesystem used to provide file system information """ self.filesystem = filesystem self._shutil_module = shutil def disk_usage(self, path): """Return the total, used and free disk space in bytes as named tuple or placeholder holder values simulating unlimited space if not set. Args: path: defines the filesystem device which is queried """ return self.filesystem.get_disk_usage(path) if sys.version_info >= (3, 12) and sys.platform == "win32": def copy2(self, src, dst, *, follow_symlinks=True): """Since Python 3.12, there is an optimization fow Windows, using the Windows API. We just remove this and fall back to the previous implementation. """ if self.filesystem.isdir(dst): dst = self.filesystem.joinpaths(dst, os.path.basename(src)) self.copyfile(src, dst, follow_symlinks=follow_symlinks) self.copystat(src, dst, follow_symlinks=follow_symlinks) return dst def copytree( self, src, dst, symlinks=False, ignore=None, copy_function=shutil.copy2, ignore_dangling_symlinks=False, dirs_exist_ok=False, ): """Make sure the default argument is patched.""" if copy_function == shutil.copy2: copy_function = self.copy2 return self._shutil_module.copytree( src, dst, symlinks, ignore, copy_function, ignore_dangling_symlinks, dirs_exist_ok, ) def move(self, src, dst, copy_function=shutil.copy2): """Make sure the default argument is patched.""" if copy_function == shutil.copy2: copy_function = self.copy2 return self._shutil_module.move(src, dst, copy_function) def __getattr__(self, name): """Forwards any non-faked calls to the standard shutil module.""" return getattr(self._shutil_module, name) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/fake_filesystem_unittest.py0000644000175100001660000014727514764107375022270 0ustar00runnerdocker# Copyright 2014 Altera Corporation. All Rights Reserved. # Copyright 2015-2017 John McGehee # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """This module provides a base class derived from `unittest.TestClass` for unit tests using the :py:class:`pyfakefs` module. `fake_filesystem_unittest.TestCase` searches `sys.modules` for modules that import the `os`, `io`, `path` `shutil`, and `pathlib` modules. The `setUpPyfakefs()` method binds these modules to the corresponding fake modules from `pyfakefs`. Further, the `open()` built-in is bound to a fake `open()`. It is expected that `setUpPyfakefs()` be invoked at the beginning of the derived class' `setUp()` method. There is no need to add anything to the derived class' `tearDown()` method. During the test, everything uses the fake file system and modules. This means that even in your test fixture, familiar functions like `open()` and `os.makedirs()` manipulate the fake file system. Existing unit tests that use the real file system can be retrofitted to use pyfakefs by simply changing their base class from `:py:class`unittest.TestCase` to `:py:class`pyfakefs.fake_filesystem_unittest.TestCase`. """ import _io # type:ignore[import] import doctest import functools import genericpath import glob import inspect import io import linecache import os import shutil import sys import tempfile import tokenize import unittest import warnings from importlib import reload from importlib.abc import Loader, MetaPathFinder from importlib.machinery import ModuleSpec from importlib.util import spec_from_file_location, module_from_spec from types import ModuleType, TracebackType, FunctionType from typing import ( Any, Callable, Dict, List, Set, Tuple, Optional, Union, Type, Iterator, cast, ItemsView, Sequence, ) from unittest import TestSuite from pyfakefs import fake_filesystem, fake_io, fake_os, fake_open, fake_path, fake_file from pyfakefs import fake_filesystem_shutil from pyfakefs import fake_legacy_modules from pyfakefs import fake_pathlib from pyfakefs import mox3_stubout from pyfakefs.fake_filesystem import ( set_uid, set_gid, reset_ids, PatchMode, FakeFilesystem, ) from pyfakefs.fake_os import use_original_os from pyfakefs.helpers import IS_PYPY from pyfakefs.legacy_packages import pathlib2, scandir from pyfakefs.mox3_stubout import StubOutForTesting OS_MODULE = "nt" if sys.platform == "win32" else "posix" PATH_MODULE = "ntpath" if sys.platform == "win32" else "posixpath" class TempfilePatcher: """Handles tempfile patching for Posix systems.""" def __init__(self): self.tempfile_cleanup = None def start_patching(self): if self.tempfile_cleanup is not None: return if sys.version_info >= (3, 12): def cleanup(self_, windows=(os.name == "nt"), unlink=None): self.tempfile_cleanup(self_, windows, unlink or os.unlink) self.tempfile_cleanup = tempfile._TemporaryFileCloser.cleanup # type: ignore[module-attr] tempfile._TemporaryFileCloser.cleanup = cleanup # type: ignore[module-attr] elif sys.platform != "win32": def close(self_, unlink=None): self.tempfile_cleanup(self_, unlink or os.unlink) self.tempfile_cleanup = tempfile._TemporaryFileCloser.close # type: ignore[module-attr] tempfile._TemporaryFileCloser.close = close # type: ignore[module-attr] def stop_patching(self): if self.tempfile_cleanup is None: return if sys.version_info < (3, 12): tempfile._TemporaryFileCloser.close = self.tempfile_cleanup # type: ignore[module-attr] else: tempfile._TemporaryFileCloser.cleanup = self.tempfile_cleanup # type: ignore[module-attr] self.tempfile_cleanup = None # reset the cached tempdir in tempfile tempfile.tempdir = None class LineCachePatcher: """Handles linecache patching for newer Python versions.""" def __init__(self): self.linecache_updatecache = None self.linecache_checkcache = None def start_patching(self): if self.linecache_updatecache is not None: return def checkcache(filename=None): """Calls the original linecache.checkcache making sure no fake OS calls are used.""" with use_original_os(): return self.linecache_checkcache(filename) def updatecache(filename, module_globals=None): """Calls the original linecache.updatecache making sure no fake OS calls are used.""" with use_original_os(): # workaround for updatecache problem with pytest under Windows, see #1096 if not filename.endswith(r"pytest.exe\__main__.py"): return self.linecache_updatecache(filename, module_globals) return [] if ( sys.version_info >= (3, 13) or sys.platform == "win32" and sys.version_info >= (3, 12) ): # in linecache, 'os' is now imported locally, which involves the # dynamic patcher, therefore we patch the affected functions self.linecache_updatecache = linecache.updatecache linecache.updatecache = updatecache self.linecache_checkcache = linecache.checkcache linecache.checkcache = checkcache def stop_patching(self): if self.linecache_updatecache is None: return linecache.updatecache = self.linecache_updatecache linecache.checkcache = self.linecache_checkcache self.linecache_updatecache = None self.linecache_checkcache = None def patchfs( _func: Optional[Callable] = None, *, additional_skip_names: Optional[List[Union[str, ModuleType]]] = None, modules_to_reload: Optional[List[ModuleType]] = None, modules_to_patch: Optional[Dict[str, ModuleType]] = None, allow_root_user: bool = True, use_known_patches: bool = True, patch_open_code: PatchMode = PatchMode.OFF, patch_default_args: bool = False, use_cache: bool = True, use_dynamic_patch: bool = True, ) -> Callable: """Convenience decorator to use patcher with additional parameters in a test function. Usage:: @patchfs def test_my_function(fake_fs): fake_fs.create_file('foo') @patchfs(allow_root_user=False) def test_with_patcher_args(fs): os.makedirs('foo/bar') """ def wrap_patchfs(f: Callable) -> Callable: @functools.wraps(f) def wrapped(*args, **kwargs): with Patcher( additional_skip_names=additional_skip_names, modules_to_reload=modules_to_reload, modules_to_patch=modules_to_patch, allow_root_user=allow_root_user, use_known_patches=use_known_patches, patch_open_code=patch_open_code, patch_default_args=patch_default_args, use_cache=use_cache, use_dynamic_patch=use_dynamic_patch, ) as p: args = list(args) args.append(p.fs) return f(*args, **kwargs) return wrapped if _func: if not callable(_func): raise TypeError( "Decorator argument is not a function.\n" "Did you mean `@patchfs(additional_skip_names=...)`?" ) if hasattr(_func, "patchings"): _func.nr_patches = len(_func.patchings) # type: ignore return wrap_patchfs(_func) return wrap_patchfs DOCTEST_PATCHER = None def load_doctests( loader: Any, tests: TestSuite, ignore: Any, module: ModuleType, additional_skip_names: Optional[List[Union[str, ModuleType]]] = None, modules_to_reload: Optional[List[ModuleType]] = None, modules_to_patch: Optional[Dict[str, ModuleType]] = None, allow_root_user: bool = True, use_known_patches: bool = True, patch_open_code: PatchMode = PatchMode.OFF, patch_default_args: bool = False, use_dynamic_patch: bool = True, ) -> TestSuite: # pylint:disable=unused-argument """Load the doctest tests for the specified module into unittest. Args: loader, tests, ignore : arguments passed in from `load_tests()` module: module under test remaining args: see :py:class:`TestCase` for an explanation File `example_test.py` in the pyfakefs release provides a usage example. """ has_patcher = Patcher.DOC_PATCHER is not None if not has_patcher: Patcher.DOC_PATCHER = Patcher( additional_skip_names=additional_skip_names, modules_to_reload=modules_to_reload, modules_to_patch=modules_to_patch, allow_root_user=allow_root_user, use_known_patches=use_known_patches, patch_open_code=patch_open_code, patch_default_args=patch_default_args, use_dynamic_patch=use_dynamic_patch, is_doc_test=True, ) assert Patcher.DOC_PATCHER is not None globs = Patcher.DOC_PATCHER.replace_globs(vars(module)) tests.addTests( doctest.DocTestSuite( module, globs=globs, setUp=Patcher.DOC_PATCHER.setUp, tearDown=Patcher.DOC_PATCHER.tearDown, ) ) return tests class TestCaseMixin: """Test case mixin that automatically replaces file-system related modules by fake implementations. Attributes: additional_skip_names: names of modules where no module replacement shall be performed, in addition to the names in :py:attr:`fake_filesystem_unittest.Patcher.SKIPNAMES`. Instead of the module names, the modules themselves may be used. modules_to_reload: A list of modules that need to be reloaded to be patched dynamically; may be needed if the module imports file system modules under an alias .. caution:: Reloading modules may have unwanted side effects. modules_to_patch: A dictionary of fake modules mapped to the fully qualified patched module names. Can be used to add patching of modules not provided by `pyfakefs`. If you specify some of these attributes here, and you have DocTests, consider also specifying the same arguments to :py:func:`load_doctests`. Example usage in derived test classes:: from unittest import TestCase from fake_filesystem_unittest import TestCaseMixin class MyTestCase(TestCase, TestCaseMixin): def __init__(self, methodName='runTest'): super(MyTestCase, self).__init__( methodName=methodName, additional_skip_names=['posixpath']) import sut class AnotherTestCase(TestCase, TestCaseMixin): def __init__(self, methodName='runTest'): super(MyTestCase, self).__init__( methodName=methodName, modules_to_reload=[sut]) """ additional_skip_names: Optional[List[Union[str, ModuleType]]] = None modules_to_reload: Optional[List[ModuleType]] = None modules_to_patch: Optional[Dict[str, ModuleType]] = None @property def patcher(self): if hasattr(self, "_patcher"): return self._patcher or Patcher.PATCHER return Patcher.PATCHER @property def fs(self) -> FakeFilesystem: return cast(FakeFilesystem, self.patcher.fs) def setUpPyfakefs( self, additional_skip_names: Optional[List[Union[str, ModuleType]]] = None, modules_to_reload: Optional[List[ModuleType]] = None, modules_to_patch: Optional[Dict[str, ModuleType]] = None, allow_root_user: bool = True, use_known_patches: bool = True, patch_open_code: PatchMode = PatchMode.OFF, patch_default_args: bool = False, use_cache: bool = True, use_dynamic_patch: bool = True, ) -> None: """Bind the file-related modules to the :py:class:`pyfakefs` fake file system instead of the real file system. Also bind the fake `open()` function. Invoke this at the beginning of the `setUp()` method in your unit test class. For the arguments, see the `TestCaseMixin` attribute description. If any of the arguments is not None, it overwrites the settings for the current test case. Settings the arguments here may be a more convenient way to adapt the setting than overwriting `__init__()`. """ # if the class has already a patcher setup, we use this one if Patcher.PATCHER is not None: return if additional_skip_names is None: additional_skip_names = self.additional_skip_names if modules_to_reload is None: modules_to_reload = self.modules_to_reload if modules_to_patch is None: modules_to_patch = self.modules_to_patch self._patcher = Patcher( additional_skip_names=additional_skip_names, modules_to_reload=modules_to_reload, modules_to_patch=modules_to_patch, allow_root_user=allow_root_user, use_known_patches=use_known_patches, patch_open_code=patch_open_code, patch_default_args=patch_default_args, use_cache=use_cache, use_dynamic_patch=use_dynamic_patch, ) self._patcher.setUp() cast(TestCase, self).addCleanup(self._patcher.tearDown) @classmethod def setUpClassPyfakefs( cls, additional_skip_names: Optional[List[Union[str, ModuleType]]] = None, modules_to_reload: Optional[List[ModuleType]] = None, modules_to_patch: Optional[Dict[str, ModuleType]] = None, allow_root_user: bool = True, use_known_patches: bool = True, patch_open_code: PatchMode = PatchMode.OFF, patch_default_args: bool = False, use_cache: bool = True, use_dynamic_patch: bool = True, ) -> None: """Similar to :py:func:`setUpPyfakefs`, but as a class method that can be used in `setUpClass` instead of in `setUp`. The fake filesystem will live in all test methods in the test class and can be used in the usual way. Note that using both :py:func:`setUpClassPyfakefs` and :py:func:`setUpPyfakefs` in the same class will not work correctly. .. note:: This method is only available from Python 3.8 onwards. .. note:: If using `pytest` as testrunner, you need at least pytest 6.2 for this method to work. """ if sys.version_info < (3, 8): raise NotImplementedError( "setUpClassPyfakefs is only available in " "Python versions starting from 3.8" ) # if the class has already a patcher setup, we use this one if Patcher.PATCHER is not None: return if additional_skip_names is None: additional_skip_names = cls.additional_skip_names if modules_to_reload is None: modules_to_reload = cls.modules_to_reload if modules_to_patch is None: modules_to_patch = cls.modules_to_patch Patcher.PATCHER = Patcher( additional_skip_names=additional_skip_names, modules_to_reload=modules_to_reload, modules_to_patch=modules_to_patch, allow_root_user=allow_root_user, use_known_patches=use_known_patches, patch_open_code=patch_open_code, patch_default_args=patch_default_args, use_cache=use_cache, use_dynamic_patch=use_dynamic_patch, ) Patcher.PATCHER.setUp() cast(TestCase, cls).addClassCleanup(Patcher.PATCHER.tearDown) @classmethod def fake_fs(cls): """Convenience class method for accessing the fake filesystem. For use inside `setUpClass`, after :py:func:`setUpClassPyfakefs` has been called. """ if Patcher.PATCHER: return Patcher.PATCHER.fs return None def pause(self) -> None: """Pause the patching of the file system modules until `resume` is called. After that call, all file system calls are executed in the real file system. Calling pause() twice is silently ignored. """ self.patcher.pause() def resume(self) -> None: """Resume the patching of the file system modules if `pause` has been called before. After that call, all file system calls are executed in the fake file system. Does nothing if patching is not paused. """ self.patcher.resume() class TestCase(unittest.TestCase, TestCaseMixin): """Test case class that automatically replaces file-system related modules by fake implementations. Inherits :py:class:`TestCaseMixin`. The arguments are explained in :py:class:`TestCaseMixin`. """ def __init__( self, methodName: str = "runTest", additional_skip_names: Optional[List[Union[str, ModuleType]]] = None, modules_to_reload: Optional[List[ModuleType]] = None, modules_to_patch: Optional[Dict[str, ModuleType]] = None, ): """Creates the test class instance and the patcher used to stub out file system related modules. Args: methodName: The name of the test method (same as in unittest.TestCase) """ super().__init__(methodName) self.additional_skip_names = additional_skip_names self.modules_to_reload = modules_to_reload self.modules_to_patch = modules_to_patch def tearDownPyfakefs(self) -> None: """This method is deprecated and exists only for backward compatibility. It does nothing. """ class Patcher: """ Instantiate a stub creator to bind and un-bind the file-related modules to the :py:mod:`pyfakefs` fake modules. The arguments are explained in :py:class:`TestCaseMixin`. :py:class:`Patcher` is used in :py:class:`TestCaseMixin`. :py:class:`Patcher` also works as a context manager for other tests:: with Patcher(): doStuff() """ """Stub nothing that is imported within these modules. `sys` is included to prevent `sys.path` from being stubbed with the fake `os.path`. The `linecache` module is used to read the test file in case of test failure to get traceback information before test tear down. In order to make sure that reading the test file is not faked, we skip faking the module. We also have to set back the cached open function in tokenize. """ SKIPMODULES = { None, fake_filesystem, fake_filesystem_shutil, fake_os, fake_io, fake_open, fake_path, fake_file, sys, linecache, tokenize, os, io, _io, genericpath, os.path, } if sys.platform == "win32": import nt # type:ignore[import] import ntpath SKIPMODULES.add(nt) SKIPMODULES.add(ntpath) else: import posix import posixpath import fcntl SKIPMODULES.add(posix) SKIPMODULES.add(posixpath) SKIPMODULES.add(fcntl) # a list of modules detected at run-time # each tool defines one or more module name prefixes for modules to be skipped RUNTIME_SKIPMODULES = { "pydevd": ["_pydevd_", "pydevd", "_pydev_"], # Python debugger (PyCharm/VSCode) "_jb_runner_tools": ["_jb_"], # JetBrains tools } # caches all modules that do not have file system modules or function # to speed up _find_modules CACHED_MODULES: Set[ModuleType] = set() FS_MODULES: Dict[str, Set[Tuple[ModuleType, str]]] = {} FS_FUNCTIONS: Dict[Tuple[str, str, str], Set[ModuleType]] = {} FS_DEFARGS: List[Tuple[FunctionType, int, Callable[..., Any]]] = [] SKIPPED_FS_MODULES: Dict[str, Set[Tuple[ModuleType, str]]] = {} assert None in SKIPMODULES, "sys.modules contains 'None' values; must skip them." IS_WINDOWS = sys.platform in ("win32", "cygwin") SKIPNAMES: Set[str] = set() # hold values from last call - if changed, the cache has to be invalidated PATCHED_MODULE_NAMES: Set[str] = set() ADDITIONAL_SKIP_NAMES: Set[str] = set() PATCH_DEFAULT_ARGS = False PATCHER: Optional["Patcher"] = None DOC_PATCHER: Optional["Patcher"] = None REF_COUNT = 0 DOC_REF_COUNT = 0 def __new__(cls, *args, **kwargs): if kwargs.get("is_doc_test", False): if cls.DOC_PATCHER is None: cls.DOC_PATCHER = super().__new__(cls) return cls.DOC_PATCHER if cls.PATCHER is None: cls.PATCHER = super().__new__(cls) return cls.PATCHER def __init__( self, additional_skip_names: Optional[List[Union[str, ModuleType]]] = None, modules_to_reload: Optional[List[ModuleType]] = None, modules_to_patch: Optional[Dict[str, ModuleType]] = None, allow_root_user: bool = True, use_known_patches: bool = True, patch_open_code: PatchMode = PatchMode.OFF, patch_default_args: bool = False, use_cache: bool = True, use_dynamic_patch: bool = True, is_doc_test: bool = False, ) -> None: """ Args: additional_skip_names: names of modules where no module replacement shall be performed, in addition to the names in :py:attr:`fake_filesystem_unittest.Patcher.SKIPNAMES`. Instead of the module names, the modules themselves may be used. modules_to_reload: A list of modules that need to be reloaded to be patched dynamically; may be needed if the module imports file system modules under an alias .. caution:: Reloading modules may have unwanted side effects. modules_to_patch: A dictionary of fake modules mapped to the fully qualified patched module names. Can be used to add patching of modules not provided by `pyfakefs`. allow_root_user: If True (default), if the test is run as root user, the user in the fake file system is also considered a root user, otherwise it is always considered a regular user. use_known_patches: If True (the default), some patches for commonly used packages are applied which make them usable with pyfakefs. patch_open_code: If True, `io.open_code` is patched. The default is not to patch it, as it mostly is used to load compiled modules that are not in the fake file system. patch_default_args: If True, default arguments are checked for file system functions, which are patched. This check is expansive, so it is off by default. use_cache: If True (default), patched and non-patched modules are cached between tests for performance reasons. As this is a new feature, this argument allows to turn it off in case it causes any problems. use_dynamic_patch: If `True`, dynamic patching after setup is used (for example for modules loaded locally inside of functions). Can be switched off if it causes unwanted side effects. """ self.is_doc_test = is_doc_test if is_doc_test: if self.DOC_REF_COUNT > 0: return elif self.REF_COUNT > 0: return if not allow_root_user: # set non-root IDs even if the real user is root set_uid(1) set_gid(1) self.skip_names = self.SKIPNAMES.copy() # save the original open function for use in pytest plugin self.original_open = open self.patch_open_code = patch_open_code self.tempfile_patcher = TempfilePatcher() self.linecache_patcher = LineCachePatcher() if additional_skip_names is not None: skip_names = [ cast(ModuleType, m).__name__ if inspect.ismodule(m) else cast(str, m) for m in additional_skip_names ] self.skip_names.update(skip_names) self._fake_module_classes: Dict[str, Any] = {} self._unfaked_module_classes: Dict[str, Any] = {} self._class_modules: Dict[str, List[str]] = {} self._init_fake_module_classes() # reload tempfile under posix to patch default argument self.modules_to_reload: List[ModuleType] = [] if modules_to_reload is not None: self.modules_to_reload.extend(modules_to_reload) self.patch_default_args = patch_default_args self.use_cache = use_cache self.use_dynamic_patch = use_dynamic_patch self.cleanup_handlers: Dict[str, Callable[[str], bool]] = {} if use_known_patches: from pyfakefs.patched_packages import ( get_modules_to_patch, get_classes_to_patch, get_fake_module_classes, get_cleanup_handlers, ) modules_to_patch = modules_to_patch or {} modules_to_patch.update(get_modules_to_patch()) self._class_modules.update(get_classes_to_patch()) self._fake_module_classes.update(get_fake_module_classes()) self.cleanup_handlers.update(get_cleanup_handlers()) if modules_to_patch is not None: for name, fake_module in modules_to_patch.items(): self._fake_module_classes[name] = fake_module patched_module_names = set(modules_to_patch) else: patched_module_names = set() clear_cache = not use_cache if use_cache: if patched_module_names != self.PATCHED_MODULE_NAMES: self.__class__.PATCHED_MODULE_NAMES = patched_module_names clear_cache = True if self.skip_names != self.ADDITIONAL_SKIP_NAMES: self.__class__.ADDITIONAL_SKIP_NAMES = self.skip_names clear_cache = True if patch_default_args != self.PATCH_DEFAULT_ARGS: self.__class__.PATCH_DEFAULT_ARGS = patch_default_args clear_cache = True if clear_cache: self.clear_cache() self._fake_module_functions: Dict[str, Dict] = {} self._init_fake_module_functions() # Attributes set by _refresh() self._stubs: Optional[StubOutForTesting] = None self.fs: Optional[FakeFilesystem] = None self.fake_modules: Dict[str, Any] = {} self.unfaked_modules: Dict[str, Any] = {} # _isStale is set by tearDown(), reset by _refresh() self._isStale = True self._dyn_patcher: Optional[DynamicPatcher] = None self._patching = False self._paused = False self.has_copy_file_range = False self.has_copy_file = False @classmethod def clear_fs_cache(cls) -> None: """Clear the module cache.""" cls.CACHED_MODULES = set() cls.FS_MODULES = {} cls.FS_FUNCTIONS = {} cls.FS_DEFARGS = [] cls.SKIPPED_FS_MODULES = {} def clear_cache(self) -> None: """Clear the module cache (convenience instance method).""" self.__class__.clear_fs_cache() def register_cleanup_handler(self, name: str, handler: Callable[[str], bool]): """Register a handler for cleaning up a module after it had been loaded by the dynamic patcher. This allows to handle modules that cannot be reloaded without unwanted side effects. Args: name: The fully qualified module name. handler: A callable that may do any module cleanup, or do nothing and return `True` in case reloading shall be prevented. Returns: `True` if no further cleanup/reload shall occur after the handler is executed, `False` if the cleanup/reload shall still happen. """ self.cleanup_handlers[name] = handler def _init_fake_module_classes(self) -> None: # IMPORTANT TESTING NOTE: Whenever you add a new module below, test # it by adding an attribute in fixtures/module_with_attributes.py # and a test in fake_filesystem_unittest_test.py, class # TestAttributesWithFakeModuleNames. self._fake_module_classes = { "os": fake_os.FakeOsModule, "shutil": fake_filesystem_shutil.FakeShutilModule, "io": fake_io.FakeIoModule, "pathlib": fake_pathlib.FakePathlibModule, } if sys.version_info >= (3, 13): # for Python 3.13, we need both pathlib (path with __init__.py) and # pathlib._local (has the actual implementation); # depending on how pathlib is imported, either may be used self._fake_module_classes["pathlib._local"] = fake_pathlib.FakePathlibModule if IS_PYPY or sys.version_info >= (3, 12): # in PyPy and later cpython versions, the module is referenced as _io self._fake_module_classes["_io"] = fake_io.FakeIoModule2 if sys.platform == "win32": self._fake_module_classes["nt"] = fake_path.FakeNtModule else: self._fake_module_classes["fcntl"] = fake_filesystem.FakeFcntlModule # class modules maps class names against a list of modules they can # be contained in - this allows for alternative modules like # `pathlib` and `pathlib2` self._class_modules["Path"] = ["pathlib"] if sys.version_info >= (3, 13): self._class_modules["Path"].append("pathlib._local") self._unfaked_module_classes["pathlib"] = fake_pathlib.RealPathlibModule if sys.version_info >= (3, 13): self._unfaked_module_classes["pathlib._local"] = ( fake_pathlib.RealPathlibModule ) if pathlib2: self._fake_module_classes["pathlib2"] = ( fake_legacy_modules.FakePathlib2Module ) self._class_modules["Path"].append("pathlib2") self._unfaked_module_classes["pathlib2"] = fake_pathlib.RealPathlibModule if scandir: self._fake_module_classes["scandir"] = fake_legacy_modules.FakeScanDirModule self._fake_module_classes["Path"] = fake_pathlib.FakePathlibPathModule self._unfaked_module_classes["Path"] = fake_pathlib.RealPathlibPathModule def _init_fake_module_functions(self) -> None: # handle patching function imported separately like # `from os import stat` # each patched function name has to be looked up separately for mod_name, fake_module in self._fake_module_classes.items(): if hasattr(fake_module, "dir"): module_dir = fake_module.dir if inspect.isfunction(module_dir): for fct_name in fake_module.dir(): module_attr = (getattr(fake_module, fct_name), mod_name) self._fake_module_functions.setdefault(fct_name, {})[ mod_name ] = module_attr if mod_name == "os": self._fake_module_functions.setdefault(fct_name, {})[ OS_MODULE ] = module_attr # special handling for functions in os.path fake_module = fake_filesystem.FakePathModule for fct_name in fake_module.dir(): module_attr = (getattr(fake_module, fct_name), PATH_MODULE) self._fake_module_functions.setdefault(fct_name, {})["genericpath"] = ( module_attr ) self._fake_module_functions.setdefault(fct_name, {})[PATH_MODULE] = ( module_attr ) def __enter__(self) -> "Patcher": """Context manager for usage outside of fake_filesystem_unittest.TestCase. Ensure that all patched modules are removed in case of an unhandled exception. """ self.setUp() return self def __exit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: self.tearDown() def _is_fs_module( self, mod: ModuleType, name: str, module_names: List[str] ) -> bool: try: return ( inspect.ismodule(mod) and mod.__name__ in module_names or inspect.isclass(mod) and mod.__module__ in self._class_modules.get(name, []) ) except Exception: # handle cases where the module has no __name__ or __module__ # attribute - see #460, and any other exception triggered # by inspect functions return False def _is_fs_function(self, fct: FunctionType) -> bool: try: return ( (inspect.isfunction(fct) or inspect.isbuiltin(fct)) and fct.__name__ in self._fake_module_functions and fct.__module__ in self._fake_module_functions[fct.__name__] ) except Exception: # handle cases where the function has no __name__ or __module__ # attribute, or any other exception in inspect functions return False def _def_values( self, item: FunctionType ) -> Iterator[Tuple[FunctionType, int, Any]]: """Find default arguments that are file-system functions to be patched in top-level functions and members of top-level classes.""" # check for module-level functions try: if item.__defaults__ and inspect.isfunction(item): for i, d in enumerate(item.__defaults__): if self._is_fs_function(d): yield item, i, d except Exception: pass try: if inspect.isclass(item): # check for methods in class # (nested classes are ignored for now) # inspect.getmembers is very expansive! for m in inspect.getmembers(item, predicate=inspect.isfunction): f = cast(FunctionType, m[1]) if f.__defaults__: for i, d in enumerate(f.__defaults__): if self._is_fs_function(d): yield f, i, d except Exception: # Ignore any exception, examples: # ImportError: No module named '_gdbm' # _DontDoThat() (see #523) pass def _find_def_values(self, module_items: ItemsView[str, FunctionType]) -> None: for _, fct in module_items: for f, i, d in self._def_values(fct): self.__class__.FS_DEFARGS.append((f, i, d)) def _find_modules(self) -> None: """Find and cache all modules that import file system modules. Later, `setUp()` will stub these with the fake file system modules. """ module_names = list(self._fake_module_classes.keys()) + [PATH_MODULE] for name, module in list(sys.modules.items()): try: if ( self.use_cache and module in self.CACHED_MODULES or not inspect.ismodule(module) ): continue except Exception: # workaround for some py (part of pytest) versions # where py.error has no __name__ attribute # see https://github.com/pytest-dev/py/issues/73 # and any other exception triggered by inspect.ismodule if self.use_cache: try: self.__class__.CACHED_MODULES.add(module) except TypeError: # unhashable module - don't cache it pass continue skipped = module in self.SKIPMODULES or any( [sn.startswith(module.__name__) for sn in self.skip_names] ) module_items = module.__dict__.copy().items() modules = { name: mod for name, mod in module_items if self._is_fs_module(mod, name, module_names) } if skipped: for name, mod in modules.items(): self.__class__.SKIPPED_FS_MODULES.setdefault(name, set()).add( (module, mod.__name__) ) else: for name, mod in modules.items(): self.__class__.FS_MODULES.setdefault(name, set()).add( (module, mod.__name__) ) functions = { name: fct for name, fct in module_items if self._is_fs_function(fct) } for name, fct in functions.items(): self.__class__.FS_FUNCTIONS.setdefault( (name, fct.__name__, fct.__module__), set() ).add(module) # find default arguments that are file system functions if self.patch_default_args: self._find_def_values(module_items) if self.use_cache: self.__class__.CACHED_MODULES.add(module) def _refresh(self) -> None: """Renew the fake file system and set the _isStale flag to `False`.""" if self._stubs is not None: self._stubs.smart_unset_all() self._stubs = mox3_stubout.StubOutForTesting() self.fs = fake_filesystem.FakeFilesystem(patcher=self, create_temp_dir=True) self.fs.patch_open_code = self.patch_open_code for name in self._fake_module_classes: self.fake_modules[name] = self._fake_module_classes[name](self.fs) if hasattr(self.fake_modules[name], "skip_names"): self.fake_modules[name].skip_names = self.skip_names self.fake_modules[PATH_MODULE] = self.fake_modules["os"].path for name in self._unfaked_module_classes: self.unfaked_modules[name] = self._unfaked_module_classes[name]() self._isStale = False def setUp(self, doctester: Any = None) -> None: """Bind the file-related modules to the :py:mod:`pyfakefs` fake modules real ones. Also bind the fake `file()` and `open()` functions. """ if self.is_doc_test: self.__class__.DOC_REF_COUNT += 1 if self.__class__.DOC_REF_COUNT > 1: return else: self.__class__.REF_COUNT += 1 if self.__class__.REF_COUNT > 1: return self.has_fcopy_file = ( sys.platform == "darwin" and hasattr(shutil, "_HAS_FCOPYFILE") and shutil._HAS_FCOPYFILE ) if self.has_fcopy_file: shutil._HAS_FCOPYFILE = False # type: ignore[attr-defined] self.has_copy_file_range = ( sys.platform == "linux" and hasattr(shutil, "_USE_CP_COPY_FILE_RANGE") and shutil._USE_CP_COPY_FILE_RANGE ) if self.has_copy_file_range: shutil._USE_CP_COPY_FILE_RANGE = False # type: ignore[attr-defined] # do not use the fd functions, as they may not be available in the target OS if hasattr(shutil, "_use_fd_functions"): shutil._use_fd_functions = False # type: ignore[module-attr] # in Python 3.14, _rmtree_impl is set at load time based on _use_fd_functions # the safe version cannot be used at the moment as it used asserts of type # 'assert func is os.rmtree', which do not work with the fake versions if hasattr(shutil, "_rmtree_impl"): shutil._rmtree_impl = shutil._rmtree_unsafe # type: ignore[attr-defined] with warnings.catch_warnings(): # ignore warnings, see #542 and #614 warnings.filterwarnings("ignore") self._find_modules() self._refresh() if doctester is not None: doctester.globs = self.replace_globs(doctester.globs) self.start_patching() linecache.open = self.original_open # type: ignore[attr-defined] tokenize._builtin_open = self.original_open # type: ignore def start_patching(self) -> None: if not self._patching: self._patching = True self._paused = False self.linecache_patcher.start_patching() self.tempfile_patcher.start_patching() self.patch_modules() self.patch_functions() self.patch_defaults() self._set_glob_os_functions() self._dyn_patcher = DynamicPatcher(self) sys.meta_path.insert(0, self._dyn_patcher) for module in self.modules_to_reload: if sys.modules.get(module.__name__) is module: reload(module) if not self.use_dynamic_patch: self._dyn_patcher.cleanup() sys.meta_path.pop(0) def _set_glob_os_functions(self): # make sure the os functions cached in glob are patched if sys.version_info >= (3, 13): globber = glob._StringGlobber # type: ignore[module-attr] globber.lstat = staticmethod(os.lstat) if sys.version_info < (3, 14): globber.scandir = staticmethod(os.scandir) def patch_functions(self) -> None: assert self._stubs is not None for (name, ft_name, ft_mod), modules in self.FS_FUNCTIONS.items(): method, mod_name = self._fake_module_functions[ft_name][ft_mod] fake_module = self.fake_modules[mod_name] attr = method.__get__( fake_module, fake_module.__class__ ) # pytype: disable=attribute-error for module in modules: self._stubs.smart_set(module, name, attr) def patch_modules(self) -> None: skip_prefix_list = [] for rt_skip_module, prefixes in self.RUNTIME_SKIPMODULES.items(): if rt_skip_module in sys.modules: skip_prefix_list.extend(prefixes) skip_prefixes = tuple(skip_prefix_list) assert self._stubs is not None for name, modules in self.FS_MODULES.items(): for module, attr in modules: try: if not skip_prefixes or not module.__name__.startswith( skip_prefixes ): self._stubs.smart_set(module, name, self.fake_modules[attr]) elif attr in self.unfaked_modules: self._stubs.smart_set(module, name, self.unfaked_modules[attr]) except Exception: # handle the rare case that a module has no __name__ pass for name, modules in self.SKIPPED_FS_MODULES.items(): for module, attr in modules: if attr in self.unfaked_modules: self._stubs.smart_set(module, name, self.unfaked_modules[attr]) def patch_defaults(self) -> None: for fct, idx, ft in self.FS_DEFARGS: method, mod_name = self._fake_module_functions[ft.__name__][ft.__module__] fake_module = self.fake_modules[mod_name] attr = method.__get__( fake_module, fake_module.__class__ ) # pytype: disable=attribute-error new_defaults = [] assert fct.__defaults__ is not None for i, d in enumerate(fct.__defaults__): if i == idx: new_defaults.append(attr) else: new_defaults.append(d) fct.__defaults__ = tuple(new_defaults) def replace_globs(self, globs_: Dict[str, Any]) -> Dict[str, Any]: globs = globs_.copy() if self._isStale: self._refresh() for name in self._fake_module_classes: if name in globs: globs[name] = self._fake_module_classes[name](self.fs) return globs def tearDown(self, doctester: Any = None): """Clear the fake filesystem bindings created by `setUp()`.""" if self.is_doc_test: self.__class__.DOC_REF_COUNT -= 1 if self.__class__.DOC_REF_COUNT > 0: return else: self.__class__.REF_COUNT -= 1 if self.__class__.REF_COUNT > 0: return self.stop_patching() if self.has_fcopy_file: shutil._HAS_FCOPYFILE = True # type: ignore[attr-defined] if self.has_copy_file_range: shutil._USE_CP_COPY_FILE_RANGE = True # type: ignore[attr-defined] reset_ids() if self.is_doc_test: self.__class__.DOC_PATCHER = None else: self.__class__.PATCHER = None def stop_patching(self, temporary=False) -> None: if self._patching: self._isStale = True self._patching = False self._paused = temporary if self._stubs: self._stubs.smart_unset_all() self.unset_defaults() if self.use_dynamic_patch and self._dyn_patcher: self._dyn_patcher.cleanup() sys.meta_path.pop(0) self.tempfile_patcher.stop_patching() self.linecache_patcher.stop_patching() self._set_glob_os_functions() @property def is_patching(self): return self._patching def unset_defaults(self) -> None: for fct, idx, ft in self.FS_DEFARGS: new_defaults = [] for i, d in enumerate(cast(Tuple, fct.__defaults__)): if i == idx: new_defaults.append(ft) else: new_defaults.append(d) fct.__defaults__ = tuple(new_defaults) def pause(self) -> None: """Pause the patching of the file system modules until `resume` is called. After that call, all file system calls are executed in the real file system. Calling pause() twice is silently ignored. """ self.stop_patching(temporary=True) def resume(self) -> None: """Resume the patching of the file system modules if `pause` has been called before. After that call, all file system calls are executed in the fake file system. Does nothing if patching is not paused. """ if self._paused: self.start_patching() class Pause: """Simple context manager that allows to pause/resume patching the filesystem. Patching is paused in the context manager, and resumed after going out of its scope. """ def __init__(self, caller: Union[Patcher, TestCaseMixin, FakeFilesystem]): """Initializes the context manager with the fake filesystem. Args: caller: either the FakeFilesystem instance, the Patcher instance or the pyfakefs test case. """ if isinstance(caller, (Patcher, TestCaseMixin)): assert caller.fs is not None self._fs: FakeFilesystem = caller.fs elif isinstance(caller, FakeFilesystem): self._fs = caller else: raise ValueError( "Invalid argument - should be of type " '"fake_filesystem_unittest.Patcher", ' '"fake_filesystem_unittest.TestCase" ' 'or "fake_filesystem.FakeFilesystem"' ) def __enter__(self) -> FakeFilesystem: self._fs.pause() return self._fs def __exit__(self, *args: Any) -> None: self._fs.resume() class DynamicPatcher(MetaPathFinder, Loader): """A file loader that replaces file system related modules by their fake implementation if they are loaded after calling `setUpPyfakefs()`. Implements the protocol needed for import hooks. """ def __init__(self, patcher: Patcher) -> None: self._patcher = patcher self.sysmodules = {} self.modules = self._patcher.fake_modules self._loaded_module_names: Set[str] = set() self.cleanup_handlers = patcher.cleanup_handlers # remove all modules that have to be patched from `sys.modules`, # otherwise the find_... methods will not be called for name in self.modules: if self.needs_patch(name) and name in sys.modules: self.sysmodules[name] = sys.modules[name] del sys.modules[name] for name, module in self.modules.items(): sys.modules[name] = module def cleanup(self) -> None: for module_name in self.sysmodules: sys.modules[module_name] = self.sysmodules[module_name] for module in self._patcher.modules_to_reload: if module.__name__ in sys.modules: reload(module) reloaded_module_names = [ module.__name__ for module in self._patcher.modules_to_reload ] # Delete all modules loaded during the test, ensuring that # they are reloaded after the test. for name in self._loaded_module_names: if name in sys.modules and name not in reloaded_module_names: if name in self.cleanup_handlers and self.cleanup_handlers[name](name): continue del sys.modules[name] def needs_patch(self, name: str) -> bool: """Checks if the module with the given name shall be replaced.""" if name not in self.modules: self._loaded_module_names.add(name) return False if name in sys.modules and type(sys.modules[name]) is self.modules[name]: return False return True def fake_module_path(self, name: str) -> str: """Checks if the module with the given name is a module existing in the fake filesystem and returns its path in this case. """ fs = self._patcher.fs # we assume that the module name is the absolute module path if fs is not None: base_path = name.replace(".", fs.path_separator) for path in sys.path: module_path = fs.joinpaths(path, base_path) py_module_path = module_path + ".py" if fs.exists(py_module_path): return fs.absnormpath(py_module_path) init_path = fs.joinpaths(module_path, "__init__.py") if fs.exists(init_path): return fs.absnormpath(init_path) return "" def find_spec( self, fullname: str, path: Optional[Sequence[Union[bytes, str]]], target: Optional[ModuleType] = None, ) -> Optional[ModuleSpec]: """Module finder.""" if self.needs_patch(fullname): return ModuleSpec(fullname, self) if self._patcher.patch_open_code != PatchMode.OFF: # handle modules created in the fake filesystem module_path = self.fake_module_path(fullname) if module_path: spec = spec_from_file_location(fullname, module_path) if spec: module = module_from_spec(spec) sys.modules[fullname] = module return ModuleSpec(fullname, self) return None def load_module(self, fullname: str) -> ModuleType: """Replaces the module by its fake implementation.""" sys.modules[fullname] = self.modules[fullname] return self.modules[fullname] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/fake_io.py0000644000175100001660000001431714764107375016542 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Uses :py:class:`FakeIoModule` to provide a fake ``io`` module replacement. """ import _io # pytype: disable=import-error import io import sys from enum import Enum from typing import ( List, Optional, Callable, Union, Any, AnyStr, IO, TYPE_CHECKING, ) from pyfakefs.fake_file import AnyFileWrapper from pyfakefs.fake_open import fake_open from pyfakefs.helpers import IS_PYPY, is_called_from_skipped_module if TYPE_CHECKING: from pyfakefs.fake_filesystem import FakeFilesystem class PatchMode(Enum): """Defines if patching shall be on, off, or in automatic mode. Currently only used for `patch_open_code` option. """ OFF = 1 AUTO = 2 ON = 3 class FakeIoModule: """Uses FakeFilesystem to provide a fake io module replacement. You need a fake_filesystem to use this: filesystem = fake_filesystem.FakeFilesystem() my_io_module = fake_io.FakeIoModule(filesystem) """ @staticmethod def dir() -> List[str]: """Return the list of patched function names. Used for patching functions imported from the module. """ _dir = ["open"] if sys.version_info >= (3, 8): _dir.append("open_code") return _dir def __init__(self, filesystem: "FakeFilesystem"): """ Args: filesystem: FakeFilesystem used to provide file system information. """ self.filesystem = filesystem self.skip_names: List[str] = [] self._io_module = io def open( self, file: Union[AnyStr, int], mode: str = "r", buffering: int = -1, encoding: Optional[str] = None, errors: Optional[str] = None, newline: Optional[str] = None, closefd: bool = True, opener: Optional[Callable] = None, ) -> Union[AnyFileWrapper, IO[Any]]: """Redirect the call to FakeFileOpen. See FakeFileOpen.call() for description. """ return fake_open( self.filesystem, self.skip_names, file, mode, buffering, encoding, errors, newline, closefd, opener, ) if sys.version_info >= (3, 8): def open_code(self, path): """Redirect the call to open. Note that the behavior of the real function may be overridden by an earlier call to the PyFile_SetOpenCodeHook(). This behavior is not reproduced here. """ if not isinstance(path, str) and not IS_PYPY: raise TypeError("open_code() argument 'path' must be str, not int") patch_mode = self.filesystem.patch_open_code if ( patch_mode == PatchMode.AUTO and self.filesystem.exists(path) or patch_mode == PatchMode.ON ): return self.open(path, mode="rb") # mostly this is used for compiled code - # don't patch these, as the files are probably in the real fs return self._io_module.open_code(path) def __getattr__(self, name): """Forwards any unfaked calls to the standard io module.""" return getattr(self._io_module, name) class FakeIoModule2(FakeIoModule): """Similar to ``FakeIoModule``, but fakes `_io` instead of `io`.""" def __init__(self, filesystem: "FakeFilesystem"): """ Args: filesystem: FakeFilesystem used to provide file system information. """ super().__init__(filesystem) self._io_module = _io if sys.platform != "win32": import fcntl class FakeFcntlModule: """Replaces the fcntl module. Only valid under Linux/MacOS, currently just mocks the functionality away. """ @staticmethod def dir() -> List[str]: """Return the list of patched function names. Used for patching functions imported from the module. """ return ["fcntl", "ioctl", "flock", "lockf"] def __init__(self, filesystem: "FakeFilesystem"): """ Args: filesystem: FakeFilesystem used to provide file system information (currently not used). """ self.filesystem = filesystem self._fcntl_module = fcntl def fcntl(self, fd: int, cmd: int, arg: int = 0) -> Union[int, bytes]: return 0 if isinstance(arg, int) else arg def ioctl( self, fd: int, request: int, arg: int = 0, mutate_flag: bool = True ) -> Union[int, bytes]: return 0 if isinstance(arg, int) else arg def flock(self, fd: int, operation: int) -> None: pass def lockf( self, fd: int, cmd: int, len: int = 0, start: int = 0, whence=0 ) -> Any: pass def __getattribute__(self, name): """Prevents patching of skipped modules.""" fs: FakeFilesystem = object.__getattribute__(self, "filesystem") fnctl_module = object.__getattribute__(self, "_fcntl_module") if fs.patcher: if is_called_from_skipped_module( skip_names=fs.patcher.skip_names, case_sensitive=fs.is_case_sensitive, ): # remove the `self` argument for FakeOsModule methods return getattr(fnctl_module, name) return object.__getattribute__(self, name) def __getattr__(self, name): """Forwards any unfaked calls to the standard fcntl module.""" return getattr(self._fcntl_module, name) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/fake_legacy_modules.py0000644000175100001660000000747514764107375021136 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import warnings from pyfakefs.fake_pathlib import FakePathlibModule from pyfakefs.fake_scandir import scandir, walk def legacy_warning(module_name): msg = ( f"You are using the legacy package '{module_name}' instead of the " f"built-in module." "Patching this package will no longer be supported in pyfakefs >= 6" ) warnings.warn(msg, category=DeprecationWarning) class FakePathlib2Module(FakePathlibModule): """Uses FakeFilesystem to provide a fake pathlib module replacement. for the `pathlib2` package available on PyPi. The usage of `pathlib2` is deprecated and will no longer be supported in future pyfakefs versions. """ has_warned = False def __getattribute__(self, name): attr = object.__getattribute__(self, name) if hasattr(attr, "__call__") and not FakePathlib2Module.has_warned: FakePathlib2Module.has_warned = True legacy_warning("pathlib2") return attr class FakeScanDirModule: """Uses FakeFilesystem to provide a fake module replacement for the `scandir` package available on PyPi. The usage of the `scandir` package is deprecated and will no longer be supported in future pyfakefs versions. You need a fake_filesystem to use this: `filesystem = fake_filesystem.FakeFilesystem()` `fake_scandir_module = fake_filesystem.FakeScanDirModule(filesystem)` """ @staticmethod def dir(): """Return the list of patched function names. Used for patching functions imported from the module. """ return "scandir", "walk" def __init__(self, filesystem): self.filesystem = filesystem has_warned = False def scandir(self, path="."): """Return an iterator of DirEntry objects corresponding to the entries in the directory given by path. Args: path: Path to the target directory within the fake filesystem. Returns: an iterator to an unsorted list of os.DirEntry objects for each entry in path. Raises: OSError: if the target is not a directory. """ if not self.has_warned: self.__class__.has_warned = True legacy_warning("scandir") return scandir(self.filesystem, path) def walk(self, top, topdown=True, onerror=None, followlinks=False): """Perform a walk operation over the fake filesystem. Args: top: The root directory from which to begin walk. topdown: Determines whether to return the tuples with the root as the first entry (`True`) or as the last, after all the child directory tuples (`False`). onerror: If not `None`, function which will be called to handle the `os.error` instance provided when `os.listdir()` fails. followlinks: If `True`, symbolic links are followed. Yields: (path, directories, nondirectories) for top and each of its subdirectories. See the documentation for the builtin os module for further details. """ if not self.has_warned: self.__class__.has_warned = True legacy_warning("scandir") return walk(self.filesystem, top, topdown, onerror, followlinks) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/fake_open.py0000644000175100001660000003540214764107375017072 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """A fake open() function replacement. See ``fake_filesystem`` for usage.""" import errno import io import os import sys from stat import ( S_ISDIR, ) from typing import ( Optional, Union, Any, Tuple, cast, AnyStr, TYPE_CHECKING, Callable, IO, List, ) from pyfakefs import helpers from pyfakefs.fake_file import ( FakePipeWrapper, FakeFileWrapper, FakeFile, AnyFileWrapper, ) from pyfakefs.helpers import ( AnyString, is_called_from_skipped_module, is_root, PERM_READ, PERM_WRITE, _OpenModes, ) if TYPE_CHECKING: from pyfakefs.fake_filesystem import FakeFilesystem # Work around pyupgrade auto-rewriting `io.open()` to `open()`. io_open = io.open _OPEN_MODE_MAP = { # mode name:(file must exist, can read, can write, # truncate, append, must not exist) "r": (True, True, False, False, False, False), "w": (False, False, True, True, False, False), "a": (False, False, True, False, True, False), "r+": (True, True, True, False, False, False), "w+": (False, True, True, True, False, False), "a+": (False, True, True, False, True, False), "x": (False, False, True, False, False, True), "x+": (False, True, True, False, False, True), } def fake_open( filesystem: "FakeFilesystem", skip_names: List[str], file: Union[AnyStr, int], mode: str = "r", buffering: int = -1, encoding: Optional[str] = None, errors: Optional[str] = None, newline: Optional[str] = None, closefd: bool = True, opener: Optional[Callable] = None, ) -> Union[AnyFileWrapper, IO[Any]]: """Redirect the call to FakeFileOpen. See FakeFileOpen.call() for description. """ if is_called_from_skipped_module( skip_names=skip_names, case_sensitive=filesystem.is_case_sensitive, check_open_code=sys.version_info >= (3, 12), ): return io_open( # pytype: disable=wrong-arg-count file, mode, buffering, encoding, errors, newline, closefd, opener, ) fake_file_open = FakeFileOpen(filesystem) return fake_file_open( file, mode, buffering, encoding, errors, newline, closefd, opener ) class FakeFileOpen: """Faked `file()` and `open()` function replacements. Returns FakeFile objects in a FakeFilesystem in place of the `file()` or `open()` function. """ __name__ = "FakeFileOpen" def __init__( self, filesystem: "FakeFilesystem", delete_on_close: bool = False, raw_io: bool = False, ): """ Args: filesystem: FakeFilesystem used to provide file system information delete_on_close: optional boolean, deletes file on close() """ self.filesystem = filesystem self._delete_on_close = delete_on_close self.raw_io = raw_io def __call__(self, *args: Any, **kwargs: Any) -> AnyFileWrapper: """Redirects calls to file() or open() to appropriate method.""" return self.call(*args, **kwargs) def call( self, file_: Union[AnyStr, int], mode: str = "r", buffering: int = -1, encoding: Optional[str] = None, errors: Optional[str] = None, newline: Optional[str] = None, closefd: bool = True, opener: Any = None, open_modes: Optional[_OpenModes] = None, ) -> AnyFileWrapper: """Return a file-like object with the contents of the target file object. Args: file_: Path to target file or a file descriptor. mode: Additional file modes (all modes in `open()` are supported). buffering: the buffer size used for writing. Data will only be flushed if buffer size is exceeded. The default (-1) uses a system specific default buffer size. Text line mode (e.g. buffering=1 in text mode) is not supported. encoding: The encoding used to encode unicode strings / decode bytes. errors: (str) Defines how encoding errors are handled. newline: Controls universal newlines, passed to stream object. closefd: If a file descriptor rather than file name is passed, and this is set to `False`, then the file descriptor is kept open when file is closed. opener: an optional function object that will be called with `file_` and the open flags (derived from `mode`) and returns a file descriptor. open_modes: Modes for opening files if called from low-level API. Returns: A file-like object containing the contents of the target file. Raises: OSError depending on Python version / call mode: - if the target object is a directory - on an invalid path - if the file does not exist when it should - if the file exists but should not - if permission is denied ValueError: for an invalid mode or mode combination """ binary = "b" in mode if binary and encoding: raise ValueError("binary mode doesn't take an encoding argument") newline, open_modes = self._handle_file_mode(mode, newline, open_modes) opened_as_fd = isinstance(file_, int) # the pathlib opener is defined in a Path instance that may not be # patched under some circumstances; as it just calls standard open(), # we may ignore it, as it would not change the behavior if opener is not None and opener.__module__ not in ( "pathlib", "pathlib._local", ): # opener shall return a file descriptor, which will be handled # here as if directly passed file_ = opener(file_, self._open_flags_from_open_modes(open_modes)) file_object, file_path, filedes, real_path, can_write = self._handle_file_arg( file_ ) if file_object is None and file_path is None: # file must be a fake pipe wrapper, find it... if ( filedes is None or len(self.filesystem.open_files) <= filedes or not self.filesystem.open_files[filedes] ): raise OSError(errno.EBADF, "invalid pipe file descriptor") wrappers = self.filesystem.open_files[filedes] assert wrappers is not None existing_wrapper = wrappers[0] assert isinstance(existing_wrapper, FakePipeWrapper) wrapper = FakePipeWrapper( self.filesystem, existing_wrapper.fd, existing_wrapper.can_write, mode, ) file_des = self.filesystem.add_open_file(wrapper) wrapper.filedes = file_des return wrapper assert file_path is not None if not filedes: closefd = True if ( not opener and open_modes.must_not_exist and ( file_object or self.filesystem.islink(file_path) and not self.filesystem.is_windows_fs ) ): self.filesystem.raise_os_error(errno.EEXIST, file_path) assert real_path is not None file_object = self._init_file_object( file_object, file_path, open_modes, real_path, check_file_permission=not opened_as_fd, ) if S_ISDIR(file_object.st_mode): if self.filesystem.is_windows_fs: self.filesystem.raise_os_error(errno.EACCES, file_path) else: self.filesystem.raise_os_error(errno.EISDIR, file_path) # If you print obj.name, the argument to open() must be printed. # Not the abspath, not the filename, but the actual argument. file_object.opened_as = file_path if open_modes.truncate: current_time = helpers.now() file_object.st_mtime = current_time if not self.filesystem.is_windows_fs: file_object.st_ctime = current_time fakefile = FakeFileWrapper( file_object, file_path, update=open_modes.can_write and can_write, read=open_modes.can_read, append=open_modes.append, delete_on_close=self._delete_on_close, filesystem=self.filesystem, newline=newline, binary=binary, closefd=closefd, encoding=encoding, errors=errors, buffering=buffering, raw_io=self.raw_io, opened_as_fd=opened_as_fd, ) if filedes is not None: fakefile.filedes = filedes # replace the file wrapper open_files_list = self.filesystem.open_files[filedes] assert open_files_list is not None open_files_list.append(fakefile) else: fakefile.filedes = self.filesystem.add_open_file(fakefile) return fakefile @staticmethod def _open_flags_from_open_modes(open_modes: _OpenModes) -> int: flags = 0 if open_modes.can_read and open_modes.can_write: flags |= os.O_RDWR elif open_modes.can_read: flags |= os.O_RDONLY elif open_modes.can_write: flags |= os.O_WRONLY if open_modes.append: flags |= os.O_APPEND if open_modes.truncate: flags |= os.O_TRUNC if not open_modes.must_exist and open_modes.can_write: flags |= os.O_CREAT if open_modes.must_not_exist and open_modes.can_write: flags |= os.O_EXCL return flags def _init_file_object( self, file_object: Optional[FakeFile], file_path: AnyStr, open_modes: _OpenModes, real_path: AnyString, check_file_permission: bool, ) -> FakeFile: if file_object: if ( check_file_permission and not is_root() and ( (open_modes.can_read and not file_object.has_permission(PERM_READ)) or ( open_modes.can_write and not file_object.has_permission(PERM_WRITE) ) ) ): self.filesystem.raise_os_error(errno.EACCES, file_path) if open_modes.can_write: if open_modes.truncate: file_object.set_contents("") else: if open_modes.must_exist: self.filesystem.raise_os_error(errno.ENOENT, file_path) if self.filesystem.islink(file_path): link_object = self.filesystem.resolve(file_path, follow_symlinks=False) assert link_object.contents is not None target_path = cast( AnyStr, link_object.contents ) # pytype: disable=invalid-annotation else: target_path = file_path if self.filesystem.ends_with_path_separator(target_path): error = ( errno.EINVAL if self.filesystem.is_windows_fs else errno.ENOENT if self.filesystem.is_macos else errno.EISDIR ) self.filesystem.raise_os_error(error, file_path) file_object = self.filesystem.create_file_internally( real_path, create_missing_dirs=False, apply_umask=True ) return file_object def _handle_file_arg( self, file_: Union[AnyStr, int] ) -> Tuple[ Optional[FakeFile], Optional[AnyStr], Optional[int], Optional[AnyStr], bool ]: file_object = None if isinstance(file_, int): # opening a file descriptor filedes: int = file_ wrapper = self.filesystem.get_open_file(filedes) can_write = True if isinstance(wrapper, FakePipeWrapper): return None, None, filedes, None, can_write if isinstance(wrapper, FakeFileWrapper): self._delete_on_close = wrapper.delete_on_close can_write = wrapper.allow_update file_object = cast( FakeFile, self.filesystem.get_open_file(filedes).get_object() ) assert file_object is not None path = file_object.name return ( # pytype: disable=bad-return-type file_object, cast(AnyStr, path), # pytype: disable=invalid-annotation filedes, cast(AnyStr, path), # pytype: disable=invalid-annotation can_write, ) # open a file by path file_path = cast(AnyStr, file_) # pytype: disable=invalid-annotation if file_path == self.filesystem.dev_null.name: file_object = self.filesystem.dev_null real_path = file_path else: real_path = self.filesystem.resolve_path(file_path) if self.filesystem.exists(file_path): file_object = self.filesystem.get_object_from_normpath( real_path, check_read_perm=False ) return file_object, file_path, None, real_path, True def _handle_file_mode( self, mode: str, newline: Optional[str], open_modes: Optional[_OpenModes], ) -> Tuple[Optional[str], _OpenModes]: orig_modes = mode # Save original modes for error messages. # Normalize modes. Handle 't' and 'U'. if ("b" in mode and "t" in mode) or ( sys.version_info > (3, 10) and "U" in mode ): raise ValueError("Invalid mode: " + mode) mode = mode.replace("t", "").replace("b", "") mode = mode.replace("rU", "r").replace("U", "r") if not self.raw_io: if mode not in _OPEN_MODE_MAP: raise ValueError("Invalid mode: %r" % orig_modes) open_modes = _OpenModes(*_OPEN_MODE_MAP[mode]) assert open_modes is not None return newline, open_modes ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/fake_os.py0000644000175100001660000015351314764107375016556 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Uses :py:class:`FakeOsModule` to provide a fake :py:mod:`os` module replacement. """ import errno import functools import inspect import os import sys import uuid from contextlib import contextmanager from stat import ( S_IFREG, S_IFSOCK, ) from typing import ( List, Optional, Callable, Union, Any, Tuple, cast, AnyStr, TYPE_CHECKING, Set, ) from pyfakefs.fake_file import ( FakeDirectory, FakeDirWrapper, StandardStreamWrapper, FakeFileWrapper, FakePipeWrapper, FakeFile, AnyFileWrapper, ) from pyfakefs.fake_open import FakeFileOpen, _OpenModes from pyfakefs.fake_path import FakePathModule from pyfakefs.fake_scandir import scandir, walk, ScanDirIter from pyfakefs.helpers import ( FakeStatResult, is_called_from_skipped_module, is_int_type, is_byte_string, make_string_path, IS_PYPY, to_string, matching_string, AnyString, to_bytes, PERM_EXE, PERM_DEF, is_root, get_uid, get_gid, ) if TYPE_CHECKING: from pyfakefs.fake_filesystem import FakeFilesystem NR_STD_STREAMS = 3 class FakeOsModule: """Uses FakeFilesystem to provide a fake os module replacement. Do not create os.path separately from os, as there is a necessary circular dependency between os and os.path to replicate the behavior of the standard Python modules. What you want to do is to just let FakeOsModule take care of `os.path` setup itself. # You always want to do this. filesystem = fake_filesystem.FakeFilesystem() my_os_module = fake_os.FakeOsModule(filesystem) """ use_original = False @staticmethod def dir() -> List[str]: """Return the list of patched function names. Used for patching functions imported from the module. """ _dir = [ "access", "chdir", "chmod", "chown", "close", "dup", "dup2", "fstat", "fsync", "getcwd", "lchmod", "link", "listdir", "lseek", "lstat", "makedirs", "mkdir", "mknod", "open", "read", "readlink", "remove", "removedirs", "rename", "rmdir", "scandir", "stat", "symlink", "umask", "unlink", "utime", "walk", "write", "getcwdb", "replace", ] if sys.platform.startswith("linux"): _dir += [ "fdatasync", "getxattr", "listxattr", "removexattr", "setxattr", ] if sys.platform != "win32": _dir += [ "getgid", "getuid", ] return _dir def __init__(self, filesystem: "FakeFilesystem"): """Also exposes self.path (to fake os.path). Args: filesystem: FakeFilesystem used to provide file system information """ self.filesystem = filesystem self.os_module: Any = os self.path = FakePathModule(self.filesystem, self) self._supports_follow_symlinks: Optional[Set] = None self._supports_dir_fd: Optional[Set] = None self._supports_effective_ids: Optional[Set] = None self._supports_fd: Optional[Set] = None @property def devnull(self) -> str: return self.path.devnull @property def sep(self) -> str: return self.path.sep @property def altsep(self) -> Optional[str]: return self.path.altsep @property def linesep(self) -> str: return self.path.linesep @property def pathsep(self) -> str: return self.path.pathsep def fdopen(self, fd: int, *args: Any, **kwargs: Any) -> AnyFileWrapper: """Redirector to open() builtin function. Args: fd: The file descriptor of the file to open. *args: Pass through args. **kwargs: Pass through kwargs. Returns: File object corresponding to file_des. Raises: TypeError: if file descriptor is not an integer. """ if not is_int_type(fd): raise TypeError("an integer is required") return FakeFileOpen(self.filesystem)(fd, *args, **kwargs) def _umask(self) -> int: """Return the current umask.""" if self.filesystem.is_windows_fs: # windows always returns 0 - it has no real notion of umask return 0 if sys.platform == "win32": # if we are testing Unix under Windows we assume a default mask return 0o002 else: # under Unix, we return the real umask; # there is no pure getter for umask, so we have to first # set a mode to get the previous one and then re-set that mask = os.umask(0) os.umask(mask) return mask def open( self, path: AnyStr, flags: int, mode: Optional[int] = None, *, dir_fd: Optional[int] = None, ) -> int: """Return the file descriptor for a FakeFile. Args: path: the path to the file flags: low-level bits to indicate io operation mode: bits to define default permissions Note: only basic modes are supported, OS-specific modes are ignored dir_fd: If not `None`, the file descriptor of a directory, with `file_path` being relative to this directory. Returns: A file descriptor. Raises: OSError: if the path cannot be found ValueError: if invalid mode is given NotImplementedError: if `os.O_EXCL` is used without `os.O_CREAT` """ path = self._path_with_dir_fd(path, self.open, dir_fd) if mode is None: if self.filesystem.is_windows_fs: mode = 0o666 else: mode = 0o777 & ~self._umask() has_directory_flag = ( hasattr(os, "O_DIRECTORY") and flags & os.O_DIRECTORY == os.O_DIRECTORY ) if ( has_directory_flag and self.filesystem.exists(path) and not self.filesystem.isdir(path) ): raise OSError(errno.ENOTDIR, "path is not a directory", path) has_follow_flag = ( hasattr(os, "O_NOFOLLOW") and flags & os.O_NOFOLLOW == os.O_NOFOLLOW ) if has_follow_flag and self.filesystem.islink(path): raise OSError(errno.ELOOP, "path is a symlink", path) has_tmpfile_flag = ( hasattr(os, "O_TMPFILE") and flags & os.O_TMPFILE == os.O_TMPFILE ) open_modes = _OpenModes( must_exist=not flags & os.O_CREAT and not has_tmpfile_flag, can_read=not flags & os.O_WRONLY, can_write=flags & (os.O_RDWR | os.O_WRONLY) != 0, truncate=flags & os.O_TRUNC != 0, append=flags & os.O_APPEND != 0, must_not_exist=flags & os.O_EXCL != 0, ) if open_modes.must_not_exist and open_modes.must_exist: raise NotImplementedError("O_EXCL without O_CREAT mode is not supported") if has_tmpfile_flag: # this is a workaround for tempfiles that do not have a filename # as we do not support this directly, we just add a unique filename # and set the file to delete on close path = self.filesystem.joinpaths( path, matching_string(path, str(uuid.uuid4())) ) if not self.filesystem.is_windows_fs and self.filesystem.exists(path): # handle opening directory - only allowed under Posix # with read-only mode obj = self.filesystem.resolve(path) if isinstance(obj, FakeDirectory): if ( not open_modes.must_exist and not self.filesystem.is_macos ) or open_modes.can_write: self.filesystem.raise_os_error(errno.EISDIR, path) dir_wrapper = FakeDirWrapper(obj, path, self.filesystem) file_des = self.filesystem.add_open_file(dir_wrapper) dir_wrapper.filedes = file_des return file_des # low level open is always binary str_flags = "b" delete_on_close = has_tmpfile_flag if hasattr(os, "O_TEMPORARY"): delete_on_close = flags & os.O_TEMPORARY == os.O_TEMPORARY fake_file = FakeFileOpen( self.filesystem, delete_on_close=delete_on_close, raw_io=True )(path, str_flags, open_modes=open_modes) assert not isinstance(fake_file, StandardStreamWrapper) if fake_file.file_object != self.filesystem.dev_null: self.chmod(path, mode) return fake_file.fileno() def close(self, fd: int) -> None: """Close a file descriptor. Args: fd: An integer file descriptor for the file object requested. Raises: OSError: bad file descriptor. TypeError: if file descriptor is not an integer. """ file_handle = self.filesystem.get_open_file(fd) file_handle.close_fd(fd) def dup(self, fd: int) -> int: file_handle = self.filesystem.get_open_file(fd) return self.filesystem.add_open_file(file_handle) def dup2(self, fd: int, fd2: int, inheritable: bool = True) -> int: if fd == fd2: return fd file_handle = self.filesystem.get_open_file(fd) return self.filesystem.add_open_file(file_handle, fd2) def read(self, fd: int, n: int) -> bytes: """Read number of bytes from a file descriptor, returns bytes read. Args: fd: An integer file descriptor for the file object requested. n: Number of bytes to read from file. Returns: Bytes read from file. Raises: OSError: bad file descriptor. TypeError: if file descriptor is not an integer. """ file_handle = self.filesystem.get_open_file(fd) if isinstance(file_handle, FakeFileWrapper): file_handle.raw_io = True if isinstance(file_handle, FakeDirWrapper): self.filesystem.raise_os_error(errno.EBADF, file_handle.file_path) return file_handle.read(n) def write(self, fd: int, contents: bytes) -> int: """Write string to file descriptor, returns number of bytes written. Args: fd: An integer file descriptor for the file object requested. contents: String of bytes to write to file. Returns: Number of bytes written. Raises: OSError: bad file descriptor. TypeError: if file descriptor is not an integer. """ file_handle = cast(FakeFileWrapper, self.filesystem.get_open_file(fd)) if isinstance(file_handle, FakeDirWrapper): self.filesystem.raise_os_error(errno.EBADF, file_handle.file_path) if isinstance(file_handle, FakePipeWrapper): return file_handle.write(contents) file_handle.raw_io = True file_handle._sync_io() file_handle.update_flush_pos() file_handle.write(contents) file_handle.flush() return len(contents) def lseek(self, fd: int, pos: int, whence: int): file_handle = self.filesystem.get_open_file(fd) if isinstance(file_handle, FakeFileWrapper): file_handle.seek(pos, whence) else: raise OSError(errno.EBADF, "Bad file descriptor for fseek") def pipe(self) -> Tuple[int, int]: read_fd, write_fd = os.pipe() read_wrapper = FakePipeWrapper(self.filesystem, read_fd, False) file_des = self.filesystem.add_open_file(read_wrapper) read_wrapper.filedes = file_des write_wrapper = FakePipeWrapper(self.filesystem, write_fd, True) file_des = self.filesystem.add_open_file(write_wrapper) write_wrapper.filedes = file_des return read_wrapper.filedes, write_wrapper.filedes def fstat(self, fd: int) -> FakeStatResult: """Return the os.stat-like tuple for the FakeFile object of file_des. Args: fd: The file descriptor of filesystem object to retrieve. Returns: The FakeStatResult object corresponding to entry_path. Raises: OSError: if the filesystem object doesn't exist. """ # stat should return the tuple representing return value of os.stat file_object = self.filesystem.get_open_file(fd).get_object() assert isinstance(file_object, FakeFile) return file_object.stat_result.copy() def umask(self, mask: int) -> int: """Change the current umask. Args: mask: (int) The new umask value. Returns: The old umask. Raises: TypeError: if new_mask is of an invalid type. """ if not is_int_type(mask): raise TypeError("an integer is required") old_umask = self.filesystem.umask self.filesystem.umask = mask return old_umask def chdir(self, path: AnyStr) -> None: """Change current working directory to target directory. Args: path: The path to new current working directory. Raises: OSError: if user lacks permission to enter the argument directory or if the target is not a directory. """ try: path = self.filesystem.resolve_path(path, allow_fd=True) except OSError as exc: if self.filesystem.is_macos and exc.errno == errno.EBADF: raise OSError(errno.ENOTDIR, "Not a directory: " + str(path)) elif ( self.filesystem.is_windows_fs and exc.errno == errno.ENOENT and path == "" ): raise OSError(errno.EINVAL, "Invalid argument: + str(path)") raise try: self.filesystem.confirmdir(path) except OSError as exc: if exc.errno == errno.EACCES: # no access rights to the parent directory - do nothing return raise directory = self.filesystem.resolve(path) # A full implementation would check permissions all the way # up the tree. if not is_root() and not directory.has_permission(PERM_EXE): self.filesystem.raise_os_error(errno.EACCES, directory.name) self.filesystem.cwd = path # type: ignore[assignment] def getcwd(self) -> str: """Return current working directory.""" return to_string(self.filesystem.cwd) def getcwdb(self) -> bytes: """Return current working directory as bytes.""" return to_bytes(self.filesystem.cwd) def listdir(self, path: AnyStr) -> List[AnyStr]: """Return a list of file names in target_directory. Args: path: Path to the target directory within the fake filesystem. Returns: A list of file names within the target directory in arbitrary order. Raises: OSError: if the target is not a directory. """ return self.filesystem.listdir(path) XATTR_CREATE = 1 XATTR_REPLACE = 2 def getxattr( self, path: AnyStr, attribute: AnyString, *, follow_symlinks: bool = True ) -> Optional[bytes]: """Return the value of the given extended filesystem attribute for `path`. Args: path: File path, file descriptor or path-like object. attribute: (str or bytes) The attribute name. follow_symlinks: (bool) If True (the default), symlinks in the path are traversed. Returns: The contents of the extended attribute as bytes or None if the attribute does not exist. Raises: OSError: if the path does not exist. """ if not self.filesystem.is_linux: raise AttributeError("module 'os' has no attribute 'getxattr'") if isinstance(attribute, bytes): attribute = attribute.decode(sys.getfilesystemencoding()) file_obj = self.filesystem.resolve(path, follow_symlinks, allow_fd=True) if attribute not in file_obj.xattr: raise OSError(errno.ENODATA, "No data available", path) return file_obj.xattr.get(attribute) def listxattr( self, path: Optional[AnyStr] = None, *, follow_symlinks: bool = True ) -> List[str]: """Return a list of the extended filesystem attributes on `path`. Args: path: File path, file descriptor or path-like object. If None, the current directory is used. follow_symlinks: (bool) If True (the default), symlinks in the path are traversed. Returns: A list of all attribute names for the given path as str. Raises: OSError: if the path does not exist. """ if not self.filesystem.is_linux: raise AttributeError("module 'os' has no attribute 'listxattr'") path_str = self.filesystem.cwd if path is None else path file_obj = self.filesystem.resolve( cast(AnyStr, path_str), # pytype: disable=invalid-annotation follow_symlinks, allow_fd=True, ) return list(file_obj.xattr.keys()) def removexattr( self, path: AnyStr, attribute: AnyString, *, follow_symlinks: bool = True ) -> None: """Removes the extended filesystem attribute from `path`. Args: path: File path, file descriptor or path-like object attribute: (str or bytes) The attribute name. follow_symlinks: (bool) If True (the default), symlinks in the path are traversed. Raises: OSError: if the path does not exist. """ if not self.filesystem.is_linux: raise AttributeError("module 'os' has no attribute 'removexattr'") if isinstance(attribute, bytes): attribute = attribute.decode(sys.getfilesystemencoding()) file_obj = self.filesystem.resolve(path, follow_symlinks, allow_fd=True) if attribute in file_obj.xattr: del file_obj.xattr[attribute] def setxattr( self, path: AnyStr, attribute: AnyString, value: bytes, flags: int = 0, *, follow_symlinks: bool = True, ) -> None: """Sets the value of the given extended filesystem attribute for `path`. Args: path: File path, file descriptor or path-like object. attribute: The attribute name (str or bytes). value: (byte-like) The value to be set. follow_symlinks: (bool) If True (the default), symlinks in the path are traversed. Raises: OSError: if the path does not exist. TypeError: if `value` is not a byte-like object. """ if not self.filesystem.is_linux: raise AttributeError("module 'os' has no attribute 'setxattr'") if isinstance(attribute, bytes): attribute = attribute.decode(sys.getfilesystemencoding()) if not is_byte_string(value): raise TypeError("a bytes-like object is required") file_obj = self.filesystem.resolve(path, follow_symlinks, allow_fd=True) exists = attribute in file_obj.xattr if exists and flags == self.XATTR_CREATE: self.filesystem.raise_os_error(errno.ENODATA, file_obj.path) if not exists and flags == self.XATTR_REPLACE: self.filesystem.raise_os_error(errno.EEXIST, file_obj.path) file_obj.xattr[attribute] = value def scandir(self, path: str = ".") -> ScanDirIter: """Return an iterator of DirEntry objects corresponding to the entries in the directory given by path. Args: path: Path to the target directory within the fake filesystem. Returns: An iterator to an unsorted list of os.DirEntry objects for each entry in path. Raises: OSError: if the target is not a directory. """ return scandir(self.filesystem, path) def walk( self, top: AnyStr, topdown: bool = True, onerror: Optional[bool] = None, followlinks: bool = False, ): """Perform an os.walk operation over the fake filesystem. Args: top: The root directory from which to begin walk. topdown: Determines whether to return the tuples with the root as the first entry (`True`) or as the last, after all the child directory tuples (`False`). onerror: If not `None`, function which will be called to handle the `os.error` instance provided when `os.listdir()` fails. followlinks: If `True`, symbolic links are followed. Yields: (path, directories, nondirectories) for top and each of its subdirectories. See the documentation for the builtin os module for further details. """ return walk(self.filesystem, top, topdown, onerror, followlinks) def readlink(self, path: AnyStr, dir_fd: Optional[int] = None) -> str: """Read the target of a symlink. Args: path: Symlink to read the target of. dir_fd: If not `None`, the file descriptor of a directory, with `path` being relative to this directory. Returns: the string representing the path to which the symbolic link points. Raises: TypeError: if `path` is None OSError: (with errno=ENOENT) if path is not a valid path, or (with errno=EINVAL) if path is valid, but is not a symlink """ path = self._path_with_dir_fd(path, self.readlink, dir_fd) return self.filesystem.readlink(path) def stat( self, path: AnyStr, *, dir_fd: Optional[int] = None, follow_symlinks: bool = True, ) -> FakeStatResult: """Return the os.stat-like tuple for the FakeFile object of entry_path. Args: path: path to filesystem object to retrieve. dir_fd: (int) If not `None`, the file descriptor of a directory, with `entry_path` being relative to this directory. follow_symlinks: (bool) If `False` and `entry_path` points to a symlink, the link itself is changed instead of the linked object. Returns: The FakeStatResult object corresponding to entry_path. Raises: OSError: if the filesystem object doesn't exist. """ path = self._path_with_dir_fd(path, self.stat, dir_fd) return self.filesystem.stat(path, follow_symlinks) def lstat(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> FakeStatResult: """Return the os.stat-like tuple for entry_path, not following symlinks. Args: path: path to filesystem object to retrieve. dir_fd: If not `None`, the file descriptor of a directory, with `path` being relative to this directory. Returns: the FakeStatResult object corresponding to `path`. Raises: OSError: if the filesystem object doesn't exist. """ # stat should return the tuple representing return value of os.stat path = self._path_with_dir_fd(path, self.lstat, dir_fd, check_supported=False) return self.filesystem.stat(path, follow_symlinks=False) def remove(self, path: AnyStr, dir_fd: Optional[int] = None) -> None: """Remove the FakeFile object at the specified file path. Args: path: Path to file to be removed. dir_fd: If not `None`, the file descriptor of a directory, with `path` being relative to this directory. Raises: OSError: if path points to a directory. OSError: if path does not exist. OSError: if removal failed. """ path = self._path_with_dir_fd(path, self.remove, dir_fd, check_supported=False) self.filesystem.remove(path) def unlink(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> None: """Remove the FakeFile object at the specified file path. Args: path: Path to file to be removed. dir_fd: If not `None`, the file descriptor of a directory, with `path` being relative to this directory. Raises: OSError: if path points to a directory. OSError: if path does not exist. OSError: if removal failed. """ path = self._path_with_dir_fd(path, self.unlink, dir_fd) self.filesystem.remove(path) def rename( self, src: AnyStr, dst: AnyStr, *, src_dir_fd: Optional[int] = None, dst_dir_fd: Optional[int] = None, ) -> None: """Rename a FakeFile object at old_file_path to new_file_path, preserving all properties. Also replaces existing new_file_path object, if one existed (Unix only). Args: src: Path to filesystem object to rename. dst: Path to where the filesystem object will live after this call. src_dir_fd: If not `None`, the file descriptor of a directory, with `src` being relative to this directory. dst_dir_fd: If not `None`, the file descriptor of a directory, with `dst` being relative to this directory. Raises: OSError: if old_file_path does not exist. OSError: if new_file_path is an existing directory. OSError: if new_file_path is an existing file (Windows only) OSError: if new_file_path is an existing file and could not be removed (Unix) OSError: if `dirname(new_file)` does not exist OSError: if the file would be moved to another filesystem (e.g. mount point) """ src = self._path_with_dir_fd(src, self.rename, src_dir_fd) dst = self._path_with_dir_fd(dst, self.rename, dst_dir_fd) self.filesystem.rename(src, dst) def renames(self, old: AnyStr, new: AnyStr): """Fakes `os.renames`, documentation taken from there. Super-rename; create directories as necessary and delete any left empty. Works like rename, except creation of any intermediate directories needed to make the new pathname good is attempted first. After the rename, directories corresponding to rightmost path segments of the old name will be pruned until either the whole path is consumed or a nonempty directory is found. Note: this function can fail with the new directory structure made if you lack permissions needed to unlink the leaf directory or file. """ head, tail = self.filesystem.splitpath(new) if head and tail and not self.filesystem.exists(head): self.makedirs(head) self.rename(old, new) head, tail = self.filesystem.splitpath(old) if head and tail: try: self.removedirs(head) except OSError: pass def replace( self, src: AnyStr, dst: AnyStr, *, src_dir_fd: Optional[int] = None, dst_dir_fd: Optional[int] = None, ) -> None: """Renames a FakeFile object at old_file_path to new_file_path, preserving all properties. Also replaces existing new_file_path object, if one existed. Arg src: Path to filesystem object to rename. dst: Path to where the filesystem object will live after this call. src_dir_fd: If not `None`, the file descriptor of a directory, with `src` being relative to this directory. dst_dir_fd: If not `None`, the file descriptor of a directory, with `dst` being relative to this directory. Raises: OSError: if old_file_path does not exist. OSError: if new_file_path is an existing directory. OSError: if new_file_path is an existing file and could not be removed OSError: if `dirname(new_file)` does not exist OSError: if the file would be moved to another filesystem (e.g. mount point) """ src = self._path_with_dir_fd( src, self.rename, src_dir_fd, check_supported=False ) dst = self._path_with_dir_fd( dst, self.rename, dst_dir_fd, check_supported=False ) self.filesystem.rename(src, dst, force_replace=True) def rmdir(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> None: """Remove a leaf Fake directory. Args: path: (str) Name of directory to remove. dir_fd: If not `None`, the file descriptor of a directory, with `path` being relative to this directory. Raises: OSError: if `path` does not exist or is not a directory, or as per FakeFilesystem.remove_object. Cannot remove '.'. """ path = self._path_with_dir_fd(path, self.rmdir, dir_fd) self.filesystem.rmdir(path) def removedirs(self, name: AnyStr) -> None: """Remove a leaf fake directory and all empty intermediate ones. Args: name: the directory to be removed. Raises: OSError: if target_directory does not exist or is not a directory. OSError: if target_directory is not empty. """ name = self.filesystem.absnormpath(name) directory = self.filesystem.confirmdir(name) if directory.entries: self.filesystem.raise_os_error(errno.ENOTEMPTY, self.path.basename(name)) else: self.rmdir(name) head, tail = self.path.split(name) if not tail: head, tail = self.path.split(head) while head and tail: head_dir = self.filesystem.confirmdir(head) if head_dir.entries: break # only the top-level dir may not be a symlink self.filesystem.rmdir(head, allow_symlink=True) head, tail = self.path.split(head) def mkdir( self, path: AnyStr, mode: int = PERM_DEF, *, dir_fd: Optional[int] = None ) -> None: """Create a leaf Fake directory. Args: path: (str) Name of directory to create. Relative paths are assumed to be relative to '/'. mode: (int) Mode to create directory with. This argument defaults to 0o777. The umask is applied to this mode. dir_fd: If not `None`, the file descriptor of a directory, with `path` being relative to this directory. Raises: OSError: if the directory name is invalid or parent directory is read only or as per FakeFilesystem.add_object. """ path = self._path_with_dir_fd(path, self.mkdir, dir_fd) try: self.filesystem.makedir(path, mode) except OSError as e: if e.errno == errno.EACCES: self.filesystem.raise_os_error(e.errno, path) raise def makedirs( self, name: AnyStr, mode: int = PERM_DEF, exist_ok: Optional[bool] = None ) -> None: """Create a leaf Fake directory + create any non-existent parent dirs. Args: name: (str) Name of directory to create. mode: (int) Mode to create directory (and any necessary parent directories) with. This argument defaults to 0o777. The umask is applied to this mode. exist_ok: (boolean) If exist_ok is False (the default), an OSError is raised if the target directory already exists. Raises: OSError: if the directory already exists and exist_ok=False, or as per :py:meth:`FakeFilesystem.create_dir`. """ if exist_ok is None: exist_ok = False # copied and adapted from real implementation in os.py (Python 3.12) head, tail = self.filesystem.splitpath(name) if not tail: head, tail = self.filesystem.splitpath(head) if head and tail and not self.filesystem.exists(head): try: self.makedirs(head, exist_ok=exist_ok) except FileExistsError: pass cdir = self.filesystem.cwd if isinstance(tail, bytes): if tail == bytes(cdir, "ASCII"): return elif tail == cdir: return try: self.mkdir(name, mode) except OSError: if not exist_ok or not self.filesystem.isdir(name): raise def _path_with_dir_fd( self, path: AnyStr, fct: Callable, dir_fd: Optional[int], check_supported: bool = True, ) -> AnyStr: """Return the path considering dir_fd. Raise on invalid parameters.""" try: path = make_string_path(path) except TypeError: # the error is handled later path = path if dir_fd is not None: # check if fd is supported for the built-in real function if check_supported and (fct not in self.supports_dir_fd): raise NotImplementedError("dir_fd unavailable on this platform") if isinstance(path, int): raise ValueError( "%s: Can't specify dir_fd without matching path_str" % fct.__name__ ) if not self.path.isabs(path): open_file = self.filesystem.get_open_file(dir_fd) return self.path.join( # type: ignore[type-var, return-value] cast(FakeFile, open_file.get_object()).path, path ) return path def truncate(self, path: AnyStr, length: int) -> None: """Truncate the file corresponding to path, so that it is length bytes in size. If length is larger than the current size, the file is filled up with zero bytes. Args: path: (str or int) Path to the file, or an integer file descriptor for the file object. length: (int) Length of the file after truncating it. Raises: OSError: if the file does not exist or the file descriptor is invalid. """ file_object = self.filesystem.resolve(path, allow_fd=True) file_object.size = length def ftruncate(self, fd: int, length: int) -> None: """Truncate the file corresponding to fd, so that it is length bytes in size. If length is larger than the current size, the file is filled up with zero bytes. Args: fd: (int) File descriptor for the file object. length: (int) Maximum length of the file after truncating it. Raises: OSError: if the file descriptor is invalid """ file_object = self.filesystem.get_open_file(fd).get_object() if isinstance(file_object, FakeFileWrapper): file_object.size = length else: raise OSError(errno.EBADF, "Invalid file descriptor") def access( self, path: AnyStr, mode: int, *, dir_fd: Optional[int] = None, effective_ids: Optional[bool] = None, follow_symlinks: Optional[bool] = None, ) -> bool: """Check if a file exists and has the specified permissions. Args: path: (str) Path to the file. mode: (int) Permissions represented as a bitwise-OR combination of os.F_OK, os.R_OK, os.W_OK, and os.X_OK. dir_fd: If not `None`, the file descriptor of a directory, with `path` being relative to this directory. effective_ids: (bool) Unused. Only here to match the signature. follow_symlinks: (bool) If `False` and `path` points to a symlink, the link itself is queried instead of the linked object. Returns: bool, `True` if file is accessible, `False` otherwise. """ if effective_ids is not None and self.filesystem.is_windows_fs: raise NotImplementedError( "access: effective_ids unavailable on this platform" ) if follow_symlinks is None: # different behavior under Windows follow_symlinks = not self.filesystem.is_windows_fs elif self.filesystem.is_windows_fs: raise NotImplementedError( "access: follow_symlinks unavailable on this platform" ) path = self._path_with_dir_fd(path, self.access, dir_fd) try: stat_result = self.stat(path, follow_symlinks=follow_symlinks) except OSError as os_error: if os_error.errno == errno.ENOENT: return False raise if is_root(): if mode & os.X_OK: return stat_result.st_mode & 0o111 != 0 return True return (mode & ((stat_result.st_mode >> 6) & 7)) == mode def fchmod( self, fd: int, mode: int, ) -> None: """Change the permissions of an open file as encoded in integer mode. Args: fd: (int) File descriptor. mode: (int) Permissions. """ if self.filesystem.is_windows_fs and sys.version_info < (3, 13): raise AttributeError( "module 'os' has no attribute 'fchmod'. Did you mean: 'chmod'?" ) self.filesystem.chmod(fd, mode) def chmod( self, path: AnyStr, mode: int, *, dir_fd: Optional[int] = None, follow_symlinks: bool = True, ) -> None: """Change the permissions of a file as encoded in integer mode. Args: path: (str) Path to the file. mode: (int) Permissions. dir_fd: If not `None`, the file descriptor of a directory, with `path` being relative to this directory. follow_symlinks: (bool) If `False` and `path` points to a symlink, the link itself is queried instead of the linked object. """ if not follow_symlinks and ( self.chmod not in self.supports_follow_symlinks or IS_PYPY ): raise NotImplementedError( "`follow_symlinks` for chmod() is not available on this system" ) path = self._path_with_dir_fd(path, self.chmod, dir_fd) self.filesystem.chmod(path, mode, follow_symlinks) def lchmod(self, path: AnyStr, mode: int) -> None: """Change the permissions of a file as encoded in integer mode. If the file is a link, the permissions of the link are changed. Args: path: (str) Path to the file. mode: (int) Permissions. """ if self.filesystem.is_windows_fs: raise NameError("name 'lchmod' is not defined") self.filesystem.chmod(path, mode, follow_symlinks=False) def utime( self, path: AnyStr, times: Optional[Tuple[Union[int, float], Union[int, float]]] = None, ns: Optional[Tuple[int, int]] = None, dir_fd: Optional[int] = None, follow_symlinks: bool = True, ) -> None: """Change the access and modified times of a file. Args: path: (str) Path to the file. times: 2-tuple of int or float numbers, of the form (atime, mtime) which is used to set the access and modified times in seconds. If None, both times are set to the current time. ns: 2-tuple of int numbers, of the form (atime, mtime) which is used to set the access and modified times in nanoseconds. If None, both times are set to the current time. dir_fd: If not `None`, the file descriptor of a directory, with `path` being relative to this directory. follow_symlinks: (bool) If `False` and `path` points to a symlink, the link itself is queried instead of the linked object. Raises: TypeError: If anything other than the expected types is specified in the passed `times` or `ns` tuple, or if the tuple length is not equal to 2. ValueError: If both times and ns are specified. """ path = self._path_with_dir_fd(path, self.utime, dir_fd) self.filesystem.utime(path, times=times, ns=ns, follow_symlinks=follow_symlinks) def chown( self, path: AnyStr, uid: int, gid: int, *, dir_fd: Optional[int] = None, follow_symlinks: bool = True, ) -> None: """Set ownership of a faked file. Args: path: (str) Path to the file or directory. uid: (int) Numeric uid to set the file or directory to. gid: (int) Numeric gid to set the file or directory to. dir_fd: (int) If not `None`, the file descriptor of a directory, with `path` being relative to this directory. follow_symlinks: (bool) If `False` and path points to a symlink, the link itself is changed instead of the linked object. Raises: OSError: if path does not exist. `None` is also allowed for `uid` and `gid`. This permits `os.rename` to use `os.chown` even when the source file `uid` and `gid` are `None` (unset). """ path = self._path_with_dir_fd(path, self.chown, dir_fd) file_object = self.filesystem.resolve(path, follow_symlinks, allow_fd=True) if not isinstance(uid, int) or not isinstance(gid, int): raise TypeError("An integer is required") if uid != -1: file_object.st_uid = uid if gid != -1: file_object.st_gid = gid def mknod( self, path: AnyStr, mode: Optional[int] = None, device: int = 0, *, dir_fd: Optional[int] = None, ) -> None: """Create a filesystem node named 'filename'. Does not support device special files or named pipes as the real os module does. Args: path: (str) Name of the file to create mode: (int) Permissions to use and type of file to be created. Default permissions are 0o666. Only the stat.S_IFREG file type is supported by the fake implementation. The umask is applied to this mode. device: not supported in fake implementation dir_fd: If not `None`, the file descriptor of a directory, with `path` being relative to this directory. Raises: OSError: if called with unsupported options or the file can not be created. """ if self.filesystem.is_windows_fs: raise AttributeError("module 'os' has no attribute 'mknode'") if mode is None: # note that a default value of 0o600 without a device type is # documented - this is not how it seems to work mode = S_IFREG | 0o600 if device or not mode & S_IFREG and not is_root(): self.filesystem.raise_os_error(errno.EPERM) path = self._path_with_dir_fd(path, self.mknod, dir_fd) head, tail = self.path.split(path) if not tail: if self.filesystem.exists(head, check_link=True): self.filesystem.raise_os_error(errno.EEXIST, path) self.filesystem.raise_os_error(errno.ENOENT, path) if tail in (matching_string(tail, "."), matching_string(tail, "..")): self.filesystem.raise_os_error(errno.ENOENT, path) if self.filesystem.exists(path, check_link=True): self.filesystem.raise_os_error(errno.EEXIST, path) self.filesystem.add_object( head, FakeFile(tail, mode & ~self.filesystem.umask, filesystem=self.filesystem), ) def symlink( self, src: AnyStr, dst: AnyStr, target_is_directory: bool = False, *, dir_fd: Optional[int] = None, ) -> None: """Creates the specified symlink, pointed at the specified link target. Args: src: The target of the symlink. dst: Path to the symlink to create. target_is_directory: Currently ignored. dir_fd: If not `None`, the file descriptor of a directory, with `dst` being relative to this directory. Raises: OSError: if the file already exists. """ dst = self._path_with_dir_fd(dst, self.symlink, dir_fd) self.filesystem.create_symlink(dst, src, create_missing_dirs=False) def link( self, src: AnyStr, dst: AnyStr, *, src_dir_fd: Optional[int] = None, dst_dir_fd: Optional[int] = None, follow_symlinks: Optional[bool] = None, ) -> None: """Create a hard link at dst, pointing at src. Args: src: An existing path to the target file. dst: The destination path to create a new link at. src_dir_fd: If not `None`, the file descriptor of a directory, with `src` being relative to this directory. dst_dir_fd: If not `None`, the file descriptor of a directory, with `dst` being relative to this directory. follow_symlinks: (bool) If True (the default), symlinks in the path are traversed. Raises: OSError: if something already exists at new_path. OSError: if the parent directory doesn't exist. """ if IS_PYPY and follow_symlinks is not None: raise OSError(errno.EINVAL, "Invalid argument: follow_symlinks") if follow_symlinks is None: follow_symlinks = True src = self._path_with_dir_fd(src, self.link, src_dir_fd) dst = self._path_with_dir_fd(dst, self.link, dst_dir_fd) self.filesystem.link(src, dst, follow_symlinks=follow_symlinks) def fsync(self, fd: int) -> None: """Perform fsync for a fake file (in other words, do nothing). Args: fd: The file descriptor of the open file. Raises: OSError: file_des is an invalid file descriptor. TypeError: file_des is not an integer. """ # Throw an error if file_des isn't valid if 0 <= fd < NR_STD_STREAMS: self.filesystem.raise_os_error(errno.EINVAL) file_object = cast(FakeFileWrapper, self.filesystem.get_open_file(fd)) if self.filesystem.is_windows_fs: if not hasattr(file_object, "allow_update") or not file_object.allow_update: self.filesystem.raise_os_error(errno.EBADF, file_object.file_path) def fdatasync(self, fd: int) -> None: """Perform fdatasync for a fake file (in other words, do nothing). Args: fd: The file descriptor of the open file. Raises: OSError: `fd` is an invalid file descriptor. TypeError: `fd` is not an integer. """ if self.filesystem.is_windows_fs or self.filesystem.is_macos: raise AttributeError("module 'os' has no attribute 'fdatasync'") # Throw an error if file_des isn't valid if 0 <= fd < NR_STD_STREAMS: self.filesystem.raise_os_error(errno.EINVAL) self.filesystem.get_open_file(fd) def sendfile(self, fd_out: int, fd_in: int, offset: int, count: int) -> int: """Copy count bytes from file descriptor fd_in to file descriptor fd_out starting at offset. Args: fd_out: The file descriptor of the destination file. fd_in: The file descriptor of the source file. offset: The offset in bytes where to start the copy in the source file. If `None` (Linux only), copying is started at the current position, and the position is updated. count: The number of bytes to copy. If 0, all remaining bytes are copied (MacOs only). Raises: OSError: If `fd_in` or `fd_out` is an invalid file descriptor. TypeError: If `fd_in` or `fd_out` is not an integer. TypeError: If `offset` is None under MacOs. """ if self.filesystem.is_windows_fs: raise AttributeError("module 'os' has no attribute 'sendfile'") if 0 <= fd_in < NR_STD_STREAMS: self.filesystem.raise_os_error(errno.EINVAL) if 0 <= fd_out < NR_STD_STREAMS: self.filesystem.raise_os_error(errno.EINVAL) source = cast(FakeFileWrapper, self.filesystem.get_open_file(fd_in)) dest = cast(FakeFileWrapper, self.filesystem.get_open_file(fd_out)) if self.filesystem.is_macos: if dest.get_object().stat_result.st_mode & 0o777000 != S_IFSOCK: raise OSError("Socket operation on non-socket") if offset is None: if self.filesystem.is_macos: raise TypeError("None is not a valid offset") contents = source.read(count) else: position = source.tell() source.seek(offset) if count == 0 and self.filesystem.is_macos: contents = source.read() else: contents = source.read(count) source.seek(position) if contents: written = dest.write(contents) dest.flush() return written return 0 def getuid(self) -> int: """Returns the user id set in the fake filesystem. If not changed using ``set_uid``, this is the uid of the real system. """ if self.filesystem.is_windows_fs: raise NameError("name 'getuid' is not defined") return get_uid() def getgid(self) -> int: """Returns the group id set in the fake filesystem. If not changed using ``set_gid``, this is the gid of the real system. """ if self.filesystem.is_windows_fs: raise NameError("name 'getgid' is not defined") return get_gid() def fake_functions(self, original_functions) -> Set: functions = set() for fn in original_functions: if hasattr(self, fn.__name__): functions.add(getattr(self, fn.__name__)) else: functions.add(fn) return functions @property def supports_follow_symlinks(self) -> Set[Callable]: if self._supports_follow_symlinks is None: self._supports_follow_symlinks = self.fake_functions( self.os_module.supports_follow_symlinks ) return self._supports_follow_symlinks @property def supports_dir_fd(self) -> Set[Callable]: if self._supports_dir_fd is None: self._supports_dir_fd = self.fake_functions(self.os_module.supports_dir_fd) return self._supports_dir_fd @property def supports_fd(self) -> Set[Callable]: if self._supports_fd is None: self._supports_fd = self.fake_functions(self.os_module.supports_fd) return self._supports_fd @property def supports_effective_ids(self) -> Set[Callable]: if self._supports_effective_ids is None: self._supports_effective_ids = self.fake_functions( self.os_module.supports_effective_ids ) return self._supports_effective_ids def __getattr__(self, name: str) -> Any: """Forwards any unfaked calls to the standard os module.""" return getattr(self.os_module, name) def handle_original_call(f: Callable) -> Callable: """Decorator used for real pathlib Path methods to ensure that real os functions instead of faked ones are used. Applied to all non-private methods of `FakeOsModule`.""" @functools.wraps(f) def wrapped(*args, **kwargs): should_use_original = FakeOsModule.use_original if not should_use_original and args: self = args[0] fs: FakeFilesystem = self.filesystem if self.filesystem.patcher: skip_names = fs.patcher.skip_names if is_called_from_skipped_module( skip_names=skip_names, case_sensitive=fs.is_case_sensitive, ): should_use_original = True if should_use_original: # remove the `self` argument for FakeOsModule methods if args and isinstance(args[0], FakeOsModule): args = args[1:] return getattr(os, f.__name__)(*args, **kwargs) return f(*args, **kwargs) return wrapped for name, fn in inspect.getmembers(FakeOsModule, inspect.isfunction): if not fn.__name__.startswith("_"): setattr(FakeOsModule, name, handle_original_call(fn)) @contextmanager def use_original_os(): """Temporarily use original os functions instead of faked ones. Used to ensure that skipped modules do not use faked calls. """ try: FakeOsModule.use_original = True yield finally: FakeOsModule.use_original = False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/fake_path.py0000644000175100001660000004742614764107375017076 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Faked ``os.path`` module replacement. See ``fake_filesystem`` for usage.""" import errno import functools import inspect import os import sys from stat import ( S_IFDIR, S_IFMT, ) from types import ModuleType from typing import ( Callable, List, Optional, Union, Any, Dict, Tuple, AnyStr, overload, ClassVar, TYPE_CHECKING, ) from pyfakefs.helpers import ( is_called_from_skipped_module, make_string_path, to_string, matching_string, to_bytes, ) if TYPE_CHECKING: from pyfakefs.fake_filesystem import FakeFilesystem from pyfakefs.fake_os import FakeOsModule def _copy_module(old: ModuleType) -> ModuleType: """Recompiles and creates new module object.""" saved = sys.modules.pop(old.__name__, None) new = __import__(old.__name__) if saved is not None: sys.modules[old.__name__] = saved return new class FakePathModule: """Faked os.path module replacement. FakePathModule should *only* be instantiated by FakeOsModule. See the FakeOsModule docstring for details. """ _OS_PATH_COPY: Any = _copy_module(os.path) devnull: ClassVar[str] = "" sep: ClassVar[str] = "" altsep: ClassVar[Optional[str]] = None linesep: ClassVar[str] = "" pathsep: ClassVar[str] = "" @staticmethod def dir() -> List[str]: """Return the list of patched function names. Used for patching functions imported from the module. """ dir_list = [ "abspath", "dirname", "exists", "expanduser", "getatime", "getctime", "getmtime", "getsize", "isabs", "isdir", "isfile", "islink", "ismount", "join", "lexists", "normcase", "normpath", "realpath", "relpath", "split", "splitdrive", "samefile", ] if sys.version_info >= (3, 12): dir_list += ["isjunction", "splitroot"] return dir_list def __init__(self, filesystem: "FakeFilesystem", os_module: "FakeOsModule"): """Init. Args: filesystem: FakeFilesystem used to provide file system information """ self.filesystem = filesystem self._os_path = self._OS_PATH_COPY self._os_path.os = self.os = os_module # type: ignore[attr-defined] self.reset(filesystem) @classmethod def reset(cls, filesystem: "FakeFilesystem") -> None: cls.sep = filesystem.path_separator cls.altsep = filesystem.alternative_path_separator cls.linesep = filesystem.line_separator cls.devnull = filesystem.devnull cls.pathsep = filesystem.pathsep def exists(self, path: AnyStr) -> bool: """Determine whether the file object exists within the fake filesystem. Args: path: The path to the file object. Returns: (bool) `True` if the file exists. """ return self.filesystem.exists(path) def lexists(self, path: AnyStr) -> bool: """Test whether a path exists. Returns True for broken symbolic links. Args: path: path to the symlink object. Returns: bool (if file exists). """ return self.filesystem.exists(path, check_link=True) def getsize(self, path: AnyStr): """Return the file object size in bytes. Args: path: path to the file object. Returns: file size in bytes. """ file_obj = self.filesystem.resolve(path) if ( self.filesystem.ends_with_path_separator(path) and S_IFMT(file_obj.st_mode) != S_IFDIR ): error_nr = errno.EINVAL if self.filesystem.is_windows_fs else errno.ENOTDIR self.filesystem.raise_os_error(error_nr, path) return file_obj.st_size def isabs(self, path: AnyStr) -> bool: """Return True if path is an absolute pathname.""" empty = matching_string(path, "") if self.filesystem.is_windows_fs: drive, path = self.splitdrive(path) else: drive = empty path = make_string_path(path) if not self.filesystem.starts_with_sep(path): return False if self.filesystem.is_windows_fs and sys.version_info >= (3, 13): # from Python 3.13 on, a path under Windows starting with a single separator # (e.g. not a drive and not an UNC path) is no more considered absolute return drive != empty return True def isdir(self, path: AnyStr) -> bool: """Determine if path identifies a directory.""" return self.filesystem.isdir(path) def isfile(self, path: AnyStr) -> bool: """Determine if path identifies a regular file.""" return self.filesystem.isfile(path) def islink(self, path: AnyStr) -> bool: """Determine if path identifies a symbolic link. Args: path: Path to filesystem object. Returns: `True` if path points to a symbolic link. Raises: TypeError: if path is None. """ return self.filesystem.islink(path) if sys.version_info >= (3, 12): def isjunction(self, path: AnyStr) -> bool: """Returns False. Junctions are never faked.""" return self.filesystem.isjunction(path) def splitroot(self, path: AnyStr): """Split a pathname into drive, root and tail. Implementation taken from ntpath and posixpath. """ return self.filesystem.splitroot(path) if sys.version_info >= (3, 13): def isreserved(self, path): if not self.filesystem.is_windows_fs: raise AttributeError("module 'os' has no attribute 'isreserved'") return self.filesystem.isreserved(path) def getmtime(self, path: AnyStr) -> float: """Returns the modification time of the fake file. Args: path: the path to fake file. Returns: (int, float) the modification time of the fake file in number of seconds since the epoch. Raises: OSError: if the file does not exist. """ try: file_obj = self.filesystem.resolve(path) return file_obj.st_mtime except OSError: self.filesystem.raise_os_error( errno.ENOENT, winerror=3 ) # pytype: disable=bad-return-type def getatime(self, path: AnyStr) -> float: """Returns the last access time of the fake file. Note: Access time is not set automatically in fake filesystem on access. Args: path: the path to fake file. Returns: (int, float) the access time of the fake file in number of seconds since the epoch. Raises: OSError: if the file does not exist. """ try: file_obj = self.filesystem.resolve(path) except OSError: self.filesystem.raise_os_error(errno.ENOENT) return file_obj.st_atime # pytype: disable=name-error def getctime(self, path: AnyStr) -> float: """Returns the creation time of the fake file. Args: path: the path to fake file. Returns: (int, float) the creation time of the fake file in number of seconds since the epoch. Raises: OSError: if the file does not exist. """ try: file_obj = self.filesystem.resolve(path) except OSError: self.filesystem.raise_os_error(errno.ENOENT) return file_obj.st_ctime # pytype: disable=name-error def abspath(self, path: AnyStr) -> AnyStr: """Return the absolute version of a path.""" def getcwd(): """Return the current working directory.""" # pylint: disable=undefined-variable if isinstance(path, bytes): return self.os.getcwdb() else: return self.os.getcwd() path = make_string_path(path) if not self.isabs(path): path = self.join(getcwd(), path) elif self.filesystem.is_windows_fs and self.filesystem.starts_with_sep(path): cwd = getcwd() if self.filesystem.starts_with_drive_letter(cwd): path = self.join(cwd[:2], path) return self.normpath(path) def join(self, *p: AnyStr) -> AnyStr: """Return the completed path with a separator of the parts.""" return self.filesystem.joinpaths(*p) def split(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]: """Split the path into the directory and the filename of the path.""" return self.filesystem.splitpath(path) def splitdrive(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]: """Split the path into the drive part and the rest of the path, if supported.""" return self.filesystem.splitdrive(path) def normpath(self, path: AnyStr) -> AnyStr: """Normalize path, eliminating double slashes, etc.""" return self.filesystem.normpath(path) def normcase(self, path: AnyStr) -> AnyStr: """Convert to lower case under windows, replaces additional path separator.""" path = self.filesystem.normcase(path) if self.filesystem.is_windows_fs: path = path.lower() return path def relpath(self, path: AnyStr, start: Optional[AnyStr] = None) -> AnyStr: """We mostly rely on the native implementation and adapt the path separator.""" if not path: raise ValueError("no path specified") path = make_string_path(path) path = self.filesystem.replace_windows_root(path) sep = matching_string(path, self.filesystem.path_separator) if start is not None: start = make_string_path(start) else: start = matching_string(path, self.filesystem.cwd) start = self.filesystem.replace_windows_root(start) system_sep = matching_string(path, self._os_path.sep) if self.filesystem.alternative_path_separator is not None: altsep = matching_string(path, self.filesystem.alternative_path_separator) path = path.replace(altsep, system_sep) start = start.replace(altsep, system_sep) path = path.replace(sep, system_sep) start = start.replace(sep, system_sep) path = self._os_path.relpath(path, start) return path.replace(system_sep, sep) def realpath(self, filename: AnyStr, strict: Optional[bool] = None) -> AnyStr: """Return the canonical path of the specified filename, eliminating any symbolic links encountered in the path. """ if strict is not None and sys.version_info < (3, 10): raise TypeError("realpath() got an unexpected keyword argument 'strict'") if strict: # raises in strict mode if the file does not exist self.filesystem.resolve(filename) if self.filesystem.is_windows_fs: return self.abspath(filename) filename = make_string_path(filename) path, ok = self._join_real_path(filename[:0], filename, {}) path = self.abspath(path) return path def samefile(self, path1: AnyStr, path2: AnyStr) -> bool: """Return whether path1 and path2 point to the same file. Args: path1: first file path or path object path2: second file path or path object Raises: OSError: if one of the paths does not point to an existing file system object. """ stat1 = self.filesystem.stat(path1) stat2 = self.filesystem.stat(path2) return stat1.st_ino == stat2.st_ino and stat1.st_dev == stat2.st_dev @overload def _join_real_path( self, path: str, rest: str, seen: Dict[str, Optional[str]] ) -> Tuple[str, bool]: ... @overload def _join_real_path( self, path: bytes, rest: bytes, seen: Dict[bytes, Optional[bytes]] ) -> Tuple[bytes, bool]: ... def _join_real_path( self, path: AnyStr, rest: AnyStr, seen: Dict[AnyStr, Optional[AnyStr]] ) -> Tuple[AnyStr, bool]: """Join two paths, normalizing and eliminating any symbolic links encountered in the second path. Taken from Python source and adapted. """ curdir = matching_string(path, ".") pardir = matching_string(path, "..") sep = self.filesystem.get_path_separator(path) if self.isabs(rest): rest = rest[1:] path = sep while rest: name, _, rest = rest.partition(sep) if not name or name == curdir: # current dir continue if name == pardir: # parent dir if path: path, name = self.filesystem.splitpath(path) if name == pardir: path = self.filesystem.joinpaths(path, pardir, pardir) else: path = pardir continue newpath = self.filesystem.joinpaths(path, name) if not self.filesystem.islink(newpath): path = newpath continue # Resolve the symbolic link if newpath in seen: # Already seen this path seen_path = seen[newpath] if seen_path is not None: # use cached value path = seen_path continue # The symlink is not resolved, so we must have a symlink loop. # Return already resolved part + rest of the path unchanged. return self.filesystem.joinpaths(newpath, rest), False seen[newpath] = None # not resolved symlink path, ok = self._join_real_path( path, matching_string(path, self.filesystem.readlink(newpath)), seen, ) if not ok: return self.filesystem.joinpaths(path, rest), False seen[newpath] = path # resolved symlink return path, True def dirname(self, path: AnyStr) -> AnyStr: """Returns the first part of the result of `split()`.""" return self.split(path)[0] def expanduser(self, path: AnyStr) -> AnyStr: """Return the argument with an initial component of ~ or ~user replaced by that user's home directory. """ path = self._os_path.expanduser(path) return path.replace( matching_string(path, self._os_path.sep), matching_string(path, self.sep), ) def ismount(self, path: AnyStr) -> bool: """Return true if the given path is a mount point. Args: path: Path to filesystem object to be checked Returns: `True` if path is a mount point added to the fake file system. Under Windows also returns True for drive and UNC roots (independent of their existence). """ if not path: return False path_str = to_string(make_string_path(path)) normed_path = self.filesystem.absnormpath(path_str) sep = self.filesystem.path_separator if self.filesystem.is_windows_fs: path_seps: Union[Tuple[str, Optional[str]], Tuple[str]] if self.filesystem.alternative_path_separator is not None: path_seps = (sep, self.filesystem.alternative_path_separator) else: path_seps = (sep,) drive, rest = self.filesystem.splitdrive(normed_path) if drive and drive[:1] in path_seps: return (not rest) or (rest in path_seps) if rest in path_seps: return True for mount_point in self.filesystem.mount_points: if to_string(normed_path).rstrip(sep) == to_string(mount_point).rstrip(sep): return True return False def __getattr__(self, name: str) -> Any: """Forwards any non-faked calls to the real os.path.""" return getattr(self._os_path, name) if sys.platform == "win32": class FakeNtModule: """Under windows, a few function of `os.path` are taken from the `nt` module for performance reasons. These are patched here. """ @staticmethod def dir(): if sys.version_info >= (3, 12): return ["_path_exists", "_path_isfile", "_path_isdir", "_path_islink"] else: return ["_isdir"] def __init__(self, filesystem: "FakeFilesystem"): """Init. Args: filesystem: FakeFilesystem used to provide file system information """ import nt # type:ignore[import] self.filesystem = filesystem self.nt_module: Any = nt def getcwd(self) -> str: """Return current working directory.""" return to_string(self.filesystem.cwd) def getcwdb(self) -> bytes: """Return current working directory as bytes.""" return to_bytes(self.filesystem.cwd) if sys.version_info >= (3, 12): def _path_isdir(self, path: AnyStr) -> bool: return self.filesystem.isdir(path) def _path_isfile(self, path: AnyStr) -> bool: return self.filesystem.isfile(path) def _path_islink(self, path: AnyStr) -> bool: return self.filesystem.islink(path) def _path_exists(self, path: AnyStr) -> bool: return self.filesystem.exists(path) else: def _isdir(self, path: AnyStr) -> bool: return self.filesystem.isdir(path) def __getattr__(self, name: str) -> Any: """Forwards any non-faked calls to the real nt module.""" return getattr(self.nt_module, name) def handle_original_call(f: Callable) -> Callable: """Decorator used for real pathlib Path methods to ensure that real os functions instead of faked ones are used. Applied to all non-private methods of `FakePathModule`.""" @functools.wraps(f) def wrapped(*args, **kwargs): if args: self = args[0] should_use_original = self.os.use_original if not should_use_original and self.filesystem.patcher: skip_names = self.filesystem.patcher.skip_names if is_called_from_skipped_module( skip_names=skip_names, case_sensitive=self.filesystem.is_case_sensitive, ): should_use_original = True if should_use_original: # remove the `self` argument for FakePathModule methods if args and isinstance(args[0], FakePathModule): args = args[1:] return getattr(os.path, f.__name__)(*args, **kwargs) return f(*args, **kwargs) return wrapped for name, fn in inspect.getmembers(FakePathModule, inspect.isfunction): if not fn.__name__.startswith("_"): setattr(FakePathModule, name, handle_original_call(fn)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/fake_pathlib.py0000644000175100001660000011672514764107375017564 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """A fake implementation for pathlib working with FakeFilesystem. New in pyfakefs 3.0. Usage: * With fake_filesystem_unittest: If using fake_filesystem_unittest.TestCase, pathlib gets replaced by fake_pathlib together with other file system related modules. * Stand-alone with FakeFilesystem: `filesystem = fake_filesystem.FakeFilesystem()` `fake_pathlib_module = fake_filesystem.FakePathlibModule(filesystem)` `path = fake_pathlib_module.Path('/foo/bar')` Note: as the implementation is based on FakeFilesystem, all faked classes (including PurePosixPath, PosixPath, PureWindowsPath and WindowsPath) get the properties of the underlying fake filesystem. """ import errno import fnmatch import functools import inspect import ntpath import os import pathlib import posixpath import re import sys import warnings from pathlib import PurePath from typing import Callable, List, Optional from urllib.parse import quote_from_bytes as urlquote_from_bytes from pyfakefs import fake_scandir from pyfakefs.fake_filesystem import FakeFilesystem from pyfakefs.fake_open import fake_open from pyfakefs.fake_os import FakeOsModule, use_original_os from pyfakefs.fake_path import FakePathModule from pyfakefs.helpers import IS_PYPY, is_called_from_skipped_module, FSType _WIN_RESERVED_NAMES = ( {"CON", "PRN", "AUX", "NUL"} | {"COM%d" % i for i in range(1, 10)} | {"LPT%d" % i for i in range(1, 10)} ) def init_module(filesystem): """Initializes the fake module with the fake file system.""" # pylint: disable=protected-access FakePath.filesystem = filesystem if sys.version_info < (3, 12): FakePathlibModule.WindowsPath._flavour = _FakeWindowsFlavour(filesystem) FakePathlibModule.PosixPath._flavour = _FakePosixFlavour(filesystem) # Pure POSIX path separators must be filesystem-independent. fake_pure_posix_flavour = _FakePosixFlavour(filesystem) fake_pure_posix_flavour.sep = "/" fake_pure_posix_flavour.altsep = None FakePathlibModule.PurePosixPath._flavour = fake_pure_posix_flavour # Pure Windows path separators must be filesystem-independent. fake_pure_nt_flavour = _FakeWindowsFlavour(filesystem) fake_pure_nt_flavour.sep = "\\" fake_pure_nt_flavour.altsep = "/" FakePathlibModule.PureWindowsPath._flavour = fake_pure_nt_flavour else: # in Python > 3.11, the flavour is no longer a separate class, # but points to the os-specific path module (posixpath/ntpath) fake_os_posix = FakeOsModule(filesystem) if filesystem.is_windows_fs: fake_os_posix.path = FakePosixPathModule(filesystem, fake_os_posix) fake_os_windows = FakeOsModule(filesystem) if not filesystem.is_windows_fs: fake_os_windows.path = FakeWindowsPathModule(filesystem, fake_os_windows) parser_name = "_flavour" if sys.version_info < (3, 13) else "parser" # Pure POSIX path properties must be filesystem independent. setattr(FakePathlibModule.PurePosixPath, parser_name, fake_os_posix.path) # Pure Windows path properties must be filesystem independent. setattr(FakePathlibModule.PureWindowsPath, parser_name, fake_os_windows.path) def _wrap_strfunc(fake_fct, original_fct): @functools.wraps(fake_fct) def _wrapped(pathobj, *args, **kwargs): fs: FakeFilesystem = pathobj.filesystem if fs.patcher: if is_called_from_skipped_module( skip_names=fs.patcher.skip_names, case_sensitive=fs.is_case_sensitive, ): return original_fct(str(pathobj), *args, **kwargs) return fake_fct(fs, str(pathobj), *args, **kwargs) return staticmethod(_wrapped) def _wrap_binary_strfunc(fake_fct, original_fct): @functools.wraps(fake_fct) def _wrapped(pathobj1, pathobj2, *args): fs: FakeFilesystem = pathobj1.filesystem if fs.patcher: if is_called_from_skipped_module( skip_names=fs.patcher.skip_names, case_sensitive=fs.is_case_sensitive, ): return original_fct(str(pathobj1), str(pathobj2), *args) return fake_fct(fs, str(pathobj1), str(pathobj2), *args) return staticmethod(_wrapped) def _wrap_binary_strfunc_reverse(fake_fct, original_fct): @functools.wraps(fake_fct) def _wrapped(pathobj1, pathobj2, *args): fs: FakeFilesystem = pathobj2.filesystem if fs.patcher: if is_called_from_skipped_module( skip_names=fs.patcher.skip_names, case_sensitive=fs.is_case_sensitive, ): return original_fct(str(pathobj2), str(pathobj1), *args) return fake_fct(fs, str(pathobj2), str(pathobj1), *args) return staticmethod(_wrapped) try: accessor = pathlib._Accessor # type: ignore[attr-defined] except AttributeError: accessor = object class _FakeAccessor(accessor): # type: ignore[valid-type, misc] """Accessor which forwards some of the functions to FakeFilesystem methods. """ stat = _wrap_strfunc(FakeFilesystem.stat, os.stat) lstat = _wrap_strfunc( lambda fs, path: FakeFilesystem.stat(fs, path, follow_symlinks=False), os.lstat ) listdir = _wrap_strfunc(FakeFilesystem.listdir, os.listdir) scandir = _wrap_strfunc(fake_scandir.scandir, os.scandir) if hasattr(os, "lchmod"): lchmod = _wrap_strfunc( lambda fs, path, mode: FakeFilesystem.chmod( fs, path, mode, follow_symlinks=False ), os.lchmod, ) else: def lchmod(self, pathobj, *args, **kwargs): """Raises not implemented for Windows systems.""" raise NotImplementedError("lchmod() not available on this system") def chmod(self, pathobj, *args, **kwargs): if "follow_symlinks" in kwargs: if sys.version_info < (3, 10): raise TypeError( "chmod() got an unexpected keyword argument 'follow_symlinks'" ) if not kwargs["follow_symlinks"] and ( os.chmod not in os.supports_follow_symlinks or IS_PYPY ): raise NotImplementedError( "`follow_symlinks` for chmod() is not available on this system" ) return pathobj.filesystem.chmod(str(pathobj), *args, **kwargs) mkdir = _wrap_strfunc(FakeFilesystem.makedir, os.mkdir) unlink = _wrap_strfunc(FakeFilesystem.remove, os.unlink) rmdir = _wrap_strfunc(FakeFilesystem.rmdir, os.rmdir) rename = _wrap_binary_strfunc(FakeFilesystem.rename, os.rename) replace = _wrap_binary_strfunc( lambda fs, old_path, new_path: FakeFilesystem.rename( fs, old_path, new_path, force_replace=True ), os.replace, ) symlink = _wrap_binary_strfunc_reverse( lambda fs, fpath, target, target_is_dir: FakeFilesystem.create_symlink( fs, fpath, target, create_missing_dirs=False ), os.symlink, ) if (3, 8) <= sys.version_info: link_to = _wrap_binary_strfunc( lambda fs, file_path, link_target: FakeFilesystem.link( fs, file_path, link_target ), os.link, ) if sys.version_info >= (3, 10): link = _wrap_binary_strfunc( lambda fs, file_path, link_target: FakeFilesystem.link( fs, file_path, link_target ), os.link, ) # this will use the fake filesystem because os is patched def getcwd(self): return os.getcwd() readlink = _wrap_strfunc(FakeFilesystem.readlink, os.readlink) utime = _wrap_strfunc(FakeFilesystem.utime, os.utime) _fake_accessor = _FakeAccessor() if sys.version_info < (3, 12): flavour = pathlib._Flavour # type: ignore[attr-defined] class _FakeFlavour(flavour): # type: ignore[valid-type, misc] """Fake Flavour implementation used by PurePath and _Flavour""" filesystem = None ext_namespace_prefix = "\\\\?\\" drive_letters = {chr(x) for x in range(ord("a"), ord("z") + 1)} | { chr(x) for x in range(ord("A"), ord("Z") + 1) } def __init__(self, filesystem): self.filesystem = filesystem super().__init__() @staticmethod def _split_extended_path(path, ext_prefix=ext_namespace_prefix): prefix = "" if path.startswith(ext_prefix): prefix = path[:4] path = path[4:] if path.startswith("UNC\\"): prefix += path[:3] path = "\\" + path[3:] return prefix, path def _splitroot_with_drive(self, path, sep): first = path[0:1] second = path[1:2] if second == sep and first == sep: # extended paths should also disable the collapsing of "." # components (according to MSDN docs). prefix, path = self._split_extended_path(path) first = path[0:1] second = path[1:2] else: prefix = "" third = path[2:3] if second == sep and first == sep and third != sep: # is a UNC path: # vvvvvvvvvvvvvvvvvvvvv root # \\machine\mountpoint\directory\etc\... # directory ^^^^^^^^^^^^^^ index = path.find(sep, 2) if index != -1: index2 = path.find(sep, index + 1) # a UNC path can't have two slashes in a row # (after the initial two) if index2 != index + 1: if index2 == -1: index2 = len(path) if prefix: return prefix + path[1:index2], sep, path[index2 + 1 :] return path[:index2], sep, path[index2 + 1 :] drv = root = "" if second == ":" and first in self.drive_letters: drv = path[:2] path = path[2:] first = third if first == sep: root = first path = path.lstrip(sep) return prefix + drv, root, path @staticmethod def _splitroot_posix(path, sep): if path and path[0] == sep: stripped_part = path.lstrip(sep) if len(path) - len(stripped_part) == 2: return "", sep * 2, stripped_part return "", sep, stripped_part else: return "", "", path def splitroot(self, path, sep=None): """Split path into drive, root and rest.""" is_windows = isinstance(self, _FakeWindowsFlavour) if sep is None: if is_windows == self.filesystem.is_windows_fs: sep = self.filesystem.path_separator else: sep = self.sep if is_windows: return self._splitroot_with_drive(path, sep) return self._splitroot_posix(path, sep) def casefold(self, path): """Return the lower-case version of s for a Windows filesystem.""" if self.filesystem.is_windows_fs: return path.lower() return path def casefold_parts(self, parts): """Return the lower-case version of parts for a Windows filesystem.""" if self.filesystem.is_windows_fs: return [p.lower() for p in parts] return parts def _resolve_posix(self, path, strict): sep = self.sep seen = {} def _resolve(path, rest): if rest.startswith(sep): path = "" for name in rest.split(sep): if not name or name == ".": # current dir continue if name == "..": # parent dir path, _, _ = path.rpartition(sep) continue newpath = path + sep + name if newpath in seen: # Already seen this path path = seen[newpath] if path is not None: # use cached value continue # The symlink is not resolved, so we must have # a symlink loop. raise RuntimeError("Symlink loop from %r" % newpath) # Resolve the symbolic link try: target = self.filesystem.readlink(newpath) except OSError as e: if e.errno != errno.EINVAL and strict: raise # Not a symlink, or non-strict mode. We just leave the path # untouched. path = newpath else: seen[newpath] = None # not resolved symlink path = _resolve(path, target) seen[newpath] = path # resolved symlink return path # NOTE: according to POSIX, getcwd() cannot contain path components # which are symlinks. base = "" if path.is_absolute() else self.filesystem.cwd return _resolve(base, str(path)) or sep def _resolve_windows(self, path, strict): path = str(path) if not path: return os.getcwd() previous_s = None if strict: if not self.filesystem.exists(path): self.filesystem.raise_os_error(errno.ENOENT, path) return self.filesystem.resolve_path(path) else: while True: try: path = self.filesystem.resolve_path(path) except OSError: previous_s = path path = self.filesystem.splitpath(path)[0] else: if previous_s is None: return path return self.filesystem.joinpaths( path, os.path.basename(previous_s) ) def resolve(self, path, strict): """Make the path absolute, resolving any symlinks.""" if self.filesystem.is_windows_fs: return self._resolve_windows(path, strict) return self._resolve_posix(path, strict) def gethomedir(self, username): """Return the home directory of the current user.""" if not username: try: return os.environ["HOME"] except KeyError: import pwd return pwd.getpwuid(os.getuid()).pw_dir else: import pwd try: return pwd.getpwnam(username).pw_dir except KeyError: raise RuntimeError( "Can't determine home directory for %r" % username ) class _FakeWindowsFlavour(_FakeFlavour): """Flavour used by PureWindowsPath with some Windows specific implementations independent of FakeFilesystem properties. """ sep = "\\" altsep = "/" has_drv = True pathmod = ntpath def is_reserved(self, parts): """Return True if the path is considered reserved under Windows.""" # NOTE: the rules for reserved names seem somewhat complicated # (e.g. r"..\NUL" is reserved but not r"foo\NUL"). # We err on the side of caution and return True for paths which are # not considered reserved by Windows. if not parts: return False if self.filesystem.is_windows_fs and parts[0].startswith("\\\\"): # UNC paths are never reserved return False return parts[-1].partition(".")[0].upper() in _WIN_RESERVED_NAMES def make_uri(self, path): """Return a file URI for the given path""" # Under Windows, file URIs use the UTF-8 encoding. # original version, not faked drive = path.drive if len(drive) == 2 and drive[1] == ":": # It's a path on a local drive => 'file:///c:/a/b' rest = path.as_posix()[2:].lstrip("/") return "file:///{}/{}".format( drive, urlquote_from_bytes(rest.encode("utf-8")), ) else: # It's a path on a network drive => 'file://host/share/a/b' return "file:" + urlquote_from_bytes(path.as_posix().encode("utf-8")) def gethomedir(self, username): """Return the home directory of the current user.""" # original version, not faked if "HOME" in os.environ: userhome = os.environ["HOME"] elif "USERPROFILE" in os.environ: userhome = os.environ["USERPROFILE"] elif "HOMEPATH" in os.environ: try: drv = os.environ["HOMEDRIVE"] except KeyError: drv = "" userhome = drv + os.environ["HOMEPATH"] else: raise RuntimeError("Can't determine home directory") if username: # Try to guess user home directory. By default all users # directories are located in the same place and are named by # corresponding usernames. If current user home directory points # to nonstandard place, this guess is likely wrong. if os.environ["USERNAME"] != username: drv, root, parts = self.parse_parts((userhome,)) if parts[-1] != os.environ["USERNAME"]: raise RuntimeError( "Can't determine home directory for %r" % username ) parts[-1] = username if drv or root: userhome = drv + root + self.join(parts[1:]) else: userhome = self.join(parts) return userhome def compile_pattern(self, pattern): return re.compile(fnmatch.translate(pattern), re.IGNORECASE).fullmatch class _FakePosixFlavour(_FakeFlavour): """Flavour used by PurePosixPath with some Unix specific implementations independent of FakeFilesystem properties. """ sep = "/" altsep: Optional[str] = None has_drv = False pathmod = posixpath def is_reserved(self, parts): return False def make_uri(self, path): # We represent the path using the local filesystem encoding, # for portability to other applications. bpath = bytes(path) return "file://" + urlquote_from_bytes(bpath) def gethomedir(self, username): # original version, not faked if not username: try: return os.environ["HOME"] except KeyError: import pwd return pwd.getpwuid(os.getuid()).pw_dir else: import pwd try: return pwd.getpwnam(username).pw_dir except KeyError: raise RuntimeError( "Can't determine home directory for %r" % username ) def compile_pattern(self, pattern): return re.compile(fnmatch.translate(pattern)).fullmatch else: # Python >= 3.12 class FakePosixPathModule(FakePathModule): def __init__(self, filesystem: "FakeFilesystem", os_module: "FakeOsModule"): super().__init__(filesystem, os_module) with self.filesystem.use_fs_type(FSType.POSIX): self.reset(self.filesystem) class FakeWindowsPathModule(FakePathModule): def __init__(self, filesystem: "FakeFilesystem", os_module: "FakeOsModule"): super().__init__(filesystem, os_module) with self.filesystem.use_fs_type(FSType.WINDOWS): self.reset(self.filesystem) def with_fs_type(f: Callable, fs_type: FSType) -> Callable: """Decorator used for fake_path methods to ensure that the correct filesystem type is used.""" @functools.wraps(f) def wrapped(self, *args, **kwargs): with self.filesystem.use_fs_type(fs_type): return f(self, *args, **kwargs) return wrapped # decorate all public functions to use the correct fs type for fct_name in FakePathModule.dir(): fn = getattr(FakePathModule, fct_name) setattr(FakeWindowsPathModule, fct_name, with_fs_type(fn, FSType.WINDOWS)) setattr(FakePosixPathModule, fct_name, with_fs_type(fn, FSType.POSIX)) class FakePath(pathlib.Path): """Replacement for pathlib.Path. Reimplement some methods to use fake filesystem. The rest of the methods work as they are, as they will use the fake accessor. New in pyfakefs 3.0. """ # the underlying fake filesystem filesystem = None skip_names: List[str] = [] def __new__(cls, *args, **kwargs): """Creates the correct subclass based on OS.""" if cls is FakePathlibModule.Path: cls = ( FakePathlibModule.WindowsPath if cls.filesystem.is_windows_fs else FakePathlibModule.PosixPath ) if sys.version_info < (3, 12): return cls._from_parts(args) # pytype: disable=attribute-error else: return object.__new__(cls) if sys.version_info[:2] == (3, 10): # Overwritten class methods to call _init to set the fake accessor, # which is not done in Python 3.10, and not needed from Python 3.11 on @classmethod def _from_parts(cls, args): self = object.__new__(cls) self._init() drv, root, parts = self._parse_args(args) # pytype: disable=attribute-error self._drv = drv self._root = root self._parts = parts return self @classmethod def _from_parsed_parts(cls, drv, root, parts): self = object.__new__(cls) self._drv = drv self._root = root self._parts = parts self._init() return self if sys.version_info < (3, 11): def _init(self, template=None): """Initializer called from base class.""" # only needed until Python 3.10 self._accessor = _fake_accessor # only needed until Python 3.8 self._closed = False def _path(self): """Returns the underlying path string as used by the fake filesystem. """ return str(self) @classmethod def cwd(cls): """Return a new path pointing to the current working directory (as returned by os.getcwd()). """ return cls(cls.filesystem.cwd) if sys.version_info < (3, 12): # in 3.12, we can use the pathlib implementation def resolve(self, strict=None): """Make the path absolute, resolving all symlinks on the way and also normalizing it (for example turning slashes into backslashes under Windows). Args: strict: If False (default) no exception is raised if the path does not exist. Raises: OSError: if the path doesn't exist (strict=True) """ if strict is None: strict = False self._raise_on_closed() path = self._flavour.resolve( self, strict=strict ) # pytype: disable=attribute-error if path is None: self.stat() path = str(self.absolute()) path = self.filesystem.absnormpath(path) return FakePath(path) def open(self, mode="r", buffering=-1, encoding=None, errors=None, newline=None): """Open the file pointed by this path and return a fake file object. Raises: OSError: if the target object is a directory, the path is invalid or permission is denied. """ self._raise_on_closed() return fake_open( self.filesystem, self.skip_names, self._path(), mode, buffering, encoding, errors, newline, ) def read_bytes(self): """Open the fake file in bytes mode, read it, and close the file. Raises: OSError: if the target object is a directory, the path is invalid or permission is denied. """ with fake_open( self.filesystem, self.skip_names, self._path(), mode="rb", ) as f: return f.read() def read_text(self, encoding=None, errors=None): """ Open the fake file in text mode, read it, and close the file. """ with fake_open( self.filesystem, self.skip_names, self._path(), mode="r", encoding=encoding, errors=errors, ) as f: return f.read() def write_bytes(self, data): """Open the fake file in bytes mode, write to it, and close the file. Args: data: the bytes to be written Raises: OSError: if the target object is a directory, the path is invalid or permission is denied. """ # type-check for the buffer interface before truncating the file view = memoryview(data) with fake_open( self.filesystem, self.skip_names, self._path(), mode="wb", ) as f: return f.write(view) def write_text(self, data, encoding=None, errors=None, newline=None): """Open the fake file in text mode, write to it, and close the file. Args: data: the string to be written encoding: the encoding used for the string; if not given, the default locale encoding is used errors: (str) Defines how encoding errors are handled. newline: Controls universal newlines, passed to stream object. New in Python 3.10. Raises: TypeError: if data is not of type 'str'. OSError: if the target object is a directory, the path is invalid or permission is denied. """ if not isinstance(data, str): raise TypeError("data must be str, not %s" % data.__class__.__name__) if newline is not None and sys.version_info < (3, 10): raise TypeError("write_text() got an unexpected keyword argument 'newline'") with fake_open( self.filesystem, self.skip_names, self._path(), mode="w", encoding=encoding, errors=errors, newline=newline, ) as f: return f.write(data) @classmethod def home(cls): """Return a new path pointing to the user's home directory (as returned by os.path.expanduser('~')). """ home = os.path.expanduser("~") if cls.filesystem.is_windows_fs != (os.name == "nt"): username = os.path.split(home)[1] if cls.filesystem.is_windows_fs: home = os.path.join("C:", "Users", username) else: home = os.path.join("home", username) if not cls.filesystem.exists(home): cls.filesystem.create_dir(home) return cls(home.replace(os.sep, cls.filesystem.path_separator)) def samefile(self, other_path): """Return whether other_path is the same or not as this file (as returned by os.path.samefile()). Args: other_path: A path object or string of the file object to be compared with Raises: OSError: if the filesystem object doesn't exist. """ st = self.stat() try: other_st = other_path.stat() except AttributeError: other_st = self.filesystem.stat(other_path) return st.st_ino == other_st.st_ino and st.st_dev == other_st.st_dev def expanduser(self): """Return a new path with expanded ~ and ~user constructs (as returned by os.path.expanduser) """ return FakePath( os.path.expanduser(self._path()).replace( os.path.sep, self.filesystem.path_separator ) ) def _raise_on_closed(self): if sys.version_info < (3, 9) and self._closed: self._raise_closed() def touch(self, mode=0o666, exist_ok=True): """Create a fake file for the path with the given access mode, if it doesn't exist. Args: mode: the file mode for the file if it does not exist exist_ok: if the file already exists and this is True, nothing happens, otherwise FileExistError is raised Raises: FileExistsError: if the file exists and exits_ok is False. """ self._raise_on_closed() if self.exists(): if exist_ok: self.filesystem.utime(self._path(), times=None) else: self.filesystem.raise_os_error(errno.EEXIST, self._path()) else: fake_file = self.open("w", encoding="utf8") fake_file.close() self.chmod(mode) def _warn_is_reserved_deprecated(): if sys.version_info >= (3, 13): warnings.warn( "pathlib.PurePath.is_reserved() is deprecated and scheduled " "for removal in Python 3.15. Use os.path.isreserved() to detect " "reserved paths on Windows.", DeprecationWarning, ) class FakePathlibModule: """Uses FakeFilesystem to provide a fake pathlib module replacement. You need a fake_filesystem to use this: `filesystem = fake_filesystem.FakeFilesystem()` `fake_pathlib_module = fake_filesystem.FakePathlibModule(filesystem)` """ def __init__(self, filesystem): """ Initializes the module with the given filesystem. Args: filesystem: FakeFilesystem used to provide file system information """ init_module(filesystem) self._pathlib_module = pathlib class PurePosixPath(PurePath): """A subclass of PurePath, that represents non-Windows filesystem paths""" __slots__ = () if sys.version_info >= (3, 12): def is_reserved(self): _warn_is_reserved_deprecated() return False def is_absolute(self): with os.path.filesystem.use_fs_type(FSType.POSIX): # type: ignore[module-attr] return os.path.isabs(self) def joinpath(self, *pathsegments): with os.path.filesystem.use_fs_type(FSType.POSIX): # type: ignore[module-attr] return super().joinpath(*pathsegments) class PureWindowsPath(PurePath): """A subclass of PurePath, that represents Windows filesystem paths""" __slots__ = () if sys.version_info >= (3, 12): """These are reimplemented because the PurePath implementation checks the flavour against ntpath/posixpath. """ def is_reserved(self): _warn_is_reserved_deprecated() if sys.version_info < (3, 13): if not self._tail or self._tail[0].startswith("\\\\"): # UNC paths are never reserved. return False name = ( self._tail[-1].partition(".")[0].partition(":")[0].rstrip(" ") ) return name.upper() in _WIN_RESERVED_NAMES with os.path.filesystem.use_fs_type(FSType.WINDOWS): # type: ignore[module-attr] return os.path.isreserved(self) def is_absolute(self): with os.path.filesystem.use_fs_type(FSType.WINDOWS): return bool(self.drive and self.root) class WindowsPath(FakePath, PureWindowsPath): """A subclass of Path and PureWindowsPath that represents concrete Windows filesystem paths. """ __slots__ = () def owner(self): raise NotImplementedError("Path.owner() is unsupported on this system") def group(self): raise NotImplementedError("Path.group() is unsupported on this system") def is_mount(self): raise NotImplementedError("Path.is_mount() is unsupported on this system") class PosixPath(FakePath, PurePosixPath): """A subclass of Path and PurePosixPath that represents concrete non-Windows filesystem paths. """ __slots__ = () def owner(self): """Return the username of the file owner. It is assumed that `st_uid` is related to a real user, otherwise `KeyError` is raised. """ import pwd return pwd.getpwuid(self.stat().st_uid).pw_name def group(self): """Return the group name of the file group. It is assumed that `st_gid` is related to a real group, otherwise `KeyError` is raised. """ import grp return grp.getgrgid(self.stat().st_gid).gr_name if sys.version_info >= (3, 14): # in Python 3.14, case-sensitivity is checked using an is-check # (self.parser is posixpath) if not given, which we cannot fake # therefore we already provide the case sensitivity under Posix def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=False): if case_sensitive is None: case_sensitive = True return super().glob( # pytype: disable=wrong-keyword-args pattern, case_sensitive=case_sensitive, recurse_symlinks=recurse_symlinks, ) Path = FakePath def __getattr__(self, name): """Forwards any unfaked calls to the standard pathlib module.""" return getattr(self._pathlib_module, name) class FakePathlibPathModule: """Patches `pathlib.Path` by passing all calls to FakePathlibModule.""" fake_pathlib = None def __init__(self, filesystem=None): if self.fake_pathlib is None: self.__class__.fake_pathlib = FakePathlibModule(filesystem) @property def skip_names(self): return [] # not used, here to allow a setter @skip_names.setter def skip_names(self, value): # this is set from the patcher and passed to the fake Path class self.fake_pathlib.Path.skip_names = value def __call__(self, *args, **kwargs): return self.fake_pathlib.Path(*args, **kwargs) def __getattr__(self, name): return getattr(self.fake_pathlib.Path, name) @classmethod def __instancecheck__(cls, instance): # fake the inheritance to pass isinstance checks - see #666 return isinstance(instance, PurePath) class RealPath(pathlib.Path): """Replacement for `pathlib.Path` if it shall not be faked. Needed because `Path` in `pathlib` is always faked, even if `pathlib` itself is not. """ if sys.version_info < (3, 12): _flavour = ( pathlib._WindowsFlavour() # type:ignore if os.name == "nt" else pathlib._PosixFlavour() # type:ignore ) # type:ignore elif sys.version_info < (3, 13): _flavour = ntpath if os.name == "nt" else posixpath else: parser = ntpath if os.name == "nt" else posixpath def __new__(cls, *args, **kwargs): """Creates the correct subclass based on OS.""" if cls is RealPathlibModule.Path: cls = ( RealPathlibModule.WindowsPath # pytype: disable=attribute-error if os.name == "nt" else RealPathlibModule.PosixPath # pytype: disable=attribute-error ) if sys.version_info < (3, 12): return cls._from_parts(args) # pytype: disable=attribute-error else: return object.__new__(cls) if sys.version_info > (3, 10): def with_original_os(f: Callable) -> Callable: """Decorator used for real pathlib Path methods to ensure that real os functions instead of faked ones are used.""" @functools.wraps(f) def wrapped(*args, **kwargs): with use_original_os(): return f(*args, **kwargs) return wrapped for fct_name, fn in inspect.getmembers(RealPath, inspect.isfunction): if not fct_name.startswith("__"): setattr(RealPath, fct_name, with_original_os(fn)) class RealPathlibPathModule: """Patches `pathlib.Path` by passing all calls to RealPathlibModule.""" real_pathlib = None @classmethod def __instancecheck__(cls, instance): # as we cannot derive from pathlib.Path, we fake # the inheritance to pass isinstance checks - see #666 return isinstance(instance, PurePath) def __init__(self): if self.real_pathlib is None: self.__class__.real_pathlib = RealPathlibModule() def __call__(self, *args, **kwargs): return RealPath(*args, **kwargs) def __getattr__(self, name): return getattr(self.real_pathlib.Path, name) class RealPathlibModule: """Used to replace `pathlib` for skipped modules. As the original `pathlib` is always patched to use the fake path, we need to provide a version which does not do this. """ def __init__(self): self._pathlib_module = pathlib class PurePosixPath(PurePath): """A subclass of PurePath, that represents Posix filesystem paths""" __slots__ = () class PureWindowsPath(PurePath): """A subclass of PurePath, that represents Windows filesystem paths""" __slots__ = () if sys.platform == "win32": class WindowsPath(RealPath, PureWindowsPath): """A subclass of Path and PureWindowsPath that represents concrete Windows filesystem paths. """ __slots__ = () else: class PosixPath(RealPath, PurePosixPath): """A subclass of Path and PurePosixPath that represents concrete non-Windows filesystem paths. """ __slots__ = () Path = RealPath def __getattr__(self, name): """Forwards any unfaked calls to the standard pathlib module.""" return getattr(self._pathlib_module, name) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/fake_scandir.py0000644000175100001660000002136714764107375017561 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """A fake implementation for the `scandir` function working with FakeFilesystem. Works with both the function integrated into the `os` module since Python 3.5 and the standalone function available in the standalone `scandir` python package. """ import os import sys from pyfakefs.helpers import to_string, make_string_path class DirEntry(os.PathLike): """Emulates os.DirEntry. Note that we did not enforce keyword only arguments.""" def __init__(self, filesystem): """Initialize the dir entry with unset values. Args: filesystem: the fake filesystem used for implementation. """ self._filesystem = filesystem self.name = "" self.path = "" self._abspath = "" self._inode = None self._islink = False self._isdir = False self._statresult = None self._statresult_symlink = None def inode(self): """Return the inode number of the entry.""" if self._inode is None: self.stat(follow_symlinks=False) return self._inode def is_dir(self, follow_symlinks=True): """Return True if this entry is a directory entry. Args: follow_symlinks: If True, also return True if this entry is a symlink pointing to a directory. Returns: True if this entry is an existing directory entry, or if follow_symlinks is set, and this entry points to an existing directory entry. """ return self._isdir and (follow_symlinks or not self._islink) def is_file(self, follow_symlinks=True): """Return True if this entry is a regular file entry. Args: follow_symlinks: If True, also return True if this entry is a symlink pointing to a regular file. Returns: True if this entry is an existing file entry, or if follow_symlinks is set, and this entry points to an existing file entry. """ return not self._isdir and (follow_symlinks or not self._islink) def is_symlink(self): """Return True if this entry is a symbolic link (even if broken).""" return self._islink def stat(self, follow_symlinks=True): """Return a stat_result object for this entry. Args: follow_symlinks: If False and the entry is a symlink, return the result for the symlink, otherwise for the object it points to. """ if follow_symlinks: if self._statresult_symlink is None: file_object = self._filesystem.resolve(self._abspath) self._statresult_symlink = file_object.stat_result.copy() if self._filesystem.is_windows_fs: self._statresult_symlink.st_nlink = 0 return self._statresult_symlink if self._statresult is None: file_object = self._filesystem.lresolve(self._abspath) self._inode = file_object.st_ino self._statresult = file_object.stat_result.copy() if self._filesystem.is_windows_fs: self._statresult.st_nlink = 0 return self._statresult def __fspath__(self): return self.path if sys.version_info >= (3, 12): def is_junction(self) -> bool: """Return True if this entry is a junction. Junctions are not a part of posix semantic.""" if not self._filesystem.is_windows_fs: return False file_object = self._filesystem.resolve(self._abspath) return file_object.is_junction class ScanDirIter: """Iterator for DirEntry objects returned from `scandir()` function.""" def __init__(self, filesystem, path): self.filesystem = filesystem if isinstance(path, int): if self.filesystem.is_windows_fs: raise NotImplementedError( "scandir does not support file descriptor path argument" ) self.abspath = self.filesystem.absnormpath( self.filesystem.get_open_file(path).get_object().path ) self.path = "" self.entry_iter = iter(tuple()) else: if path is None: path = "." path = make_string_path(path) self.abspath = self.filesystem.absnormpath(path) self.path = to_string(path) entries = self.filesystem.confirmdir(self.abspath, check_exe_perm=False).entries self.entry_iter = iter(tuple(entries)) def __iter__(self): return self def __next__(self): entry = self.entry_iter.__next__() dir_entry = DirEntry(self.filesystem) dir_entry.name = entry dir_entry.path = self.filesystem.joinpaths(self.path, dir_entry.name) dir_entry._abspath = self.filesystem.joinpaths(self.abspath, dir_entry.name) dir_entry._isdir = self.filesystem.isdir(dir_entry._abspath) dir_entry._islink = self.filesystem.islink(dir_entry._abspath) return dir_entry def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() def close(self): pass def scandir(filesystem, path=""): """Return an iterator of DirEntry objects corresponding to the entries in the directory given by path. Args: filesystem: The fake filesystem used for implementation path: Path to the target directory within the fake filesystem. Returns: an iterator to an unsorted list of os.DirEntry objects for each entry in path. Raises: OSError: if the target is not a directory. """ return ScanDirIter(filesystem, path) def _classify_directory_contents(filesystem, root): """Classify contents of a directory as files/directories. Args: filesystem: The fake filesystem used for implementation root: (str) Directory to examine. Returns: (tuple) A tuple consisting of three values: the directory examined, a list containing all of the directory entries, and a list containing all of the non-directory entries. (This is the same format as returned by the `os.walk` generator.) Raises: Nothing on its own, but be ready to catch exceptions generated by underlying mechanisms like `os.listdir`. """ dirs = [] files = [] for entry in filesystem.listdir(root): if filesystem.isdir(filesystem.joinpaths(root, entry)): dirs.append(entry) else: files.append(entry) return root, dirs, files def walk(filesystem, top, topdown=True, onerror=None, followlinks=False): """Perform an os.walk operation over the fake filesystem. Args: filesystem: The fake filesystem used for implementation top: The root directory from which to begin walk. topdown: Determines whether to return the tuples with the root as the first entry (`True`) or as the last, after all the child directory tuples (`False`). onerror: If not `None`, function which will be called to handle the `os.error` instance provided when `os.listdir()` fails. followlinks: If `True`, symbolic links are followed. Yields: (path, directories, nondirectories) for top and each of its subdirectories. See the documentation for the builtin os module for further details. """ def do_walk(top_dir, top_most=False): if not top_most and not followlinks and filesystem.islink(top_dir): return try: top_contents = _classify_directory_contents(filesystem, top_dir) except OSError as exc: top_contents = None if onerror is not None: onerror(exc) if top_contents is not None: if topdown: yield top_contents for directory in top_contents[1]: path = filesystem.joinpaths(top_dir, directory) if not followlinks and filesystem.islink(path): continue yield from do_walk(path) if not topdown: yield top_contents return do_walk(make_string_path(to_string(top)), top_most=True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/helpers.py0000644000175100001660000004116114764107375016604 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Helper classes use for fake file system implementation.""" import ctypes import importlib import io import locale import os import platform import stat import sys import sysconfig import time import traceback from collections import namedtuple from copy import copy from dataclasses import dataclass from enum import Enum from stat import S_IFLNK from typing import Union, Optional, Any, AnyStr, overload, cast AnyString = Union[str, bytes] AnyPath = Union[AnyStr, os.PathLike] IS_PYPY = platform.python_implementation() == "PyPy" IS_WIN = sys.platform == "win32" IN_DOCKER = os.path.exists("/.dockerenv") PERM_READ = 0o400 # Read permission bit. PERM_WRITE = 0o200 # Write permission bit. PERM_EXE = 0o100 # Execute permission bit. PERM_DEF = 0o777 # Default permission bits. PERM_DEF_FILE = 0o666 # Default permission bits (regular file) PERM_ALL = 0o7777 # All permission bits. STDLIB_PATH = os.path.realpath(sysconfig.get_path("stdlib")) PYFAKEFS_PATH = os.path.dirname(__file__) PYFAKEFS_TEST_PATHS = [ os.path.join(PYFAKEFS_PATH, "tests"), os.path.join(PYFAKEFS_PATH, "pytest_tests"), ] _OpenModes = namedtuple( "_OpenModes", "must_exist can_read can_write truncate append must_not_exist", ) if sys.platform == "win32": fake_id = 0 if ctypes.windll.shell32.IsUserAnAdmin() else 1 USER_ID = fake_id GROUP_ID = fake_id else: USER_ID = os.getuid() GROUP_ID = os.getgid() def get_uid() -> int: """Get the global user id. Same as ``os.getuid()``""" return USER_ID def set_uid(uid: int) -> None: """Set the global user id. This is used as st_uid for new files and to differentiate between a normal user and the root user (uid 0). For the root user, some permission restrictions are ignored. Args: uid: (int) the user ID of the user calling the file system functions. """ global USER_ID USER_ID = uid def get_gid() -> int: """Get the global group id. Same as ``os.getgid()``""" return GROUP_ID def set_gid(gid: int) -> None: """Set the global group id. This is only used to set st_gid for new files, no permission checks are performed. Args: gid: (int) the group ID of the user calling the file system functions. """ global GROUP_ID GROUP_ID = gid def reset_ids() -> None: """Set the global user ID and group ID back to default values.""" if sys.platform == "win32": reset_id = 0 if ctypes.windll.shell32.IsUserAnAdmin() else 1 set_uid(reset_id) set_gid(reset_id) else: set_uid(os.getuid()) set_gid(os.getgid()) def is_root() -> bool: """Return True if the current user is the root user.""" return USER_ID == 0 def is_int_type(val: Any) -> bool: """Return True if `val` is of integer type.""" return isinstance(val, int) def is_byte_string(val: Any) -> bool: """Return True if `val` is a bytes-like object, False for a unicode string.""" return not hasattr(val, "encode") def is_unicode_string(val: Any) -> bool: """Return True if `val` is a unicode string, False for a bytes-like object.""" return hasattr(val, "encode") def get_locale_encoding(): if sys.version_info >= (3, 11): return locale.getencoding() return locale.getpreferredencoding(False) @overload def make_string_path(dir_name: AnyStr) -> AnyStr: ... @overload def make_string_path(dir_name: os.PathLike) -> str: ... def make_string_path(dir_name: AnyPath) -> AnyStr: # type: ignore[type-var] return cast(AnyStr, os.fspath(dir_name)) # pytype: disable=invalid-annotation def to_string(path: Union[AnyStr, Union[str, bytes]]) -> str: """Return the string representation of a byte string using the preferred encoding, or the string itself if path is a str.""" if isinstance(path, bytes): return path.decode(get_locale_encoding()) return path def to_bytes(path: Union[AnyStr, Union[str, bytes]]) -> bytes: """Return the bytes representation of a string using the preferred encoding, or the byte string itself if path is a byte string.""" if isinstance(path, str): return bytes(path, get_locale_encoding()) return path def join_strings(s1: AnyStr, s2: AnyStr) -> AnyStr: """This is a bit of a hack to satisfy mypy - may be refactored.""" return s1 + s2 def real_encoding(encoding: Optional[str]) -> Optional[str]: """Since Python 3.10, the new function ``io.text_encoding`` returns "locale" as the encoding if None is defined. This will be handled as no encoding in pyfakefs.""" if sys.version_info >= (3, 10): return encoding if encoding != "locale" else None return encoding def now(): return time.time() @overload def matching_string(matched: bytes, string: AnyStr) -> bytes: ... @overload def matching_string(matched: str, string: AnyStr) -> str: ... @overload def matching_string(matched: AnyStr, string: None) -> None: ... def matching_string( # type: ignore[misc] matched: AnyStr, string: Optional[AnyStr] ) -> Optional[AnyString]: """Return the string as byte or unicode depending on the type of matched, assuming string is an ASCII string. """ if string is None: return string if isinstance(matched, bytes) and isinstance(string, str): return string.encode(get_locale_encoding()) return string # pytype: disable=bad-return-type @dataclass class FSProperties: sep: str altsep: Optional[str] pathsep: str linesep: str devnull: str # pure POSIX file system properties, for use with PosixPath POSIX_PROPERTIES = FSProperties( sep="/", altsep=None, pathsep=":", linesep="\n", devnull="/dev/null", ) # pure Windows file system properties, for use with WindowsPath WINDOWS_PROPERTIES = FSProperties( sep="\\", altsep="/", pathsep=";", linesep="\r\n", devnull="NUL", ) class FSType(Enum): """Defines which file system properties to use.""" DEFAULT = 0 # use current OS file system + modifications in fake file system POSIX = 1 # pure POSIX properties, for use in PosixPath WINDOWS = 2 # pure Windows properties, for use in WindowsPath class FakeStatResult: """Mimics os.stat_result for use as return type of `stat()` and similar. This is needed as `os.stat_result` has no possibility to set nanosecond times directly. """ def __init__( self, is_windows: bool, user_id: int, group_id: int, initial_time: Optional[float] = None, ): self.st_mode: int = 0 self.st_ino: Optional[int] = None self.st_dev: int = 0 self.st_nlink: int = 0 self.st_uid: int = user_id self.st_gid: int = group_id self._st_size: int = 0 self.is_windows: bool = is_windows self._st_atime_ns: int = int((initial_time or 0) * 1e9) self._st_mtime_ns: int = self._st_atime_ns self._st_ctime_ns: int = self._st_atime_ns def __eq__(self, other: Any) -> bool: return ( isinstance(other, FakeStatResult) and self._st_atime_ns == other._st_atime_ns and self._st_ctime_ns == other._st_ctime_ns and self._st_mtime_ns == other._st_mtime_ns and self.st_size == other.st_size and self.st_gid == other.st_gid and self.st_uid == other.st_uid and self.st_nlink == other.st_nlink and self.st_dev == other.st_dev and self.st_ino == other.st_ino and self.st_mode == other.st_mode ) def __ne__(self, other: Any) -> bool: return not self == other def copy(self) -> "FakeStatResult": """Return a copy where the float usage is hard-coded to mimic the behavior of the real os.stat_result. """ stat_result = copy(self) return stat_result def set_from_stat_result(self, stat_result: os.stat_result) -> None: """Set values from a real os.stat_result. Note: values that are controlled by the fake filesystem are not set. This includes st_ino, st_dev and st_nlink. """ self.st_mode = stat_result.st_mode self.st_uid = stat_result.st_uid self.st_gid = stat_result.st_gid self._st_size = stat_result.st_size self._st_atime_ns = stat_result.st_atime_ns self._st_mtime_ns = stat_result.st_mtime_ns self._st_ctime_ns = stat_result.st_ctime_ns @property def st_ctime(self) -> Union[int, float]: """Return the creation time in seconds.""" return self._st_ctime_ns / 1e9 @st_ctime.setter def st_ctime(self, val: Union[int, float]) -> None: """Set the creation time in seconds.""" self._st_ctime_ns = int(val * 1e9) @property def st_atime(self) -> Union[int, float]: """Return the access time in seconds.""" return self._st_atime_ns / 1e9 @st_atime.setter def st_atime(self, val: Union[int, float]) -> None: """Set the access time in seconds.""" self._st_atime_ns = int(val * 1e9) @property def st_mtime(self) -> Union[int, float]: """Return the modification time in seconds.""" return self._st_mtime_ns / 1e9 @st_mtime.setter def st_mtime(self, val: Union[int, float]) -> None: """Set the modification time in seconds.""" self._st_mtime_ns = int(val * 1e9) @property def st_size(self) -> int: if self.st_mode & S_IFLNK == S_IFLNK and self.is_windows: return 0 return self._st_size @st_size.setter def st_size(self, val: int) -> None: self._st_size = val @property def st_blocks(self) -> int: """Return the number of 512-byte blocks allocated for the file. Assumes a page size of 4096 (matches most systems). Ignores that this may not be available under some systems, and that the result may differ if the file has holes. """ if self.is_windows: raise AttributeError("'os.stat_result' object has no attribute 'st_blocks'") page_size = 4096 blocks_in_page = page_size // 512 pages = self._st_size // page_size if self._st_size % page_size: pages += 1 return pages * blocks_in_page @property def st_file_attributes(self) -> int: if not self.is_windows: raise AttributeError( "module 'os.stat_result' has no attribute 'st_file_attributes'" ) mode = 0 st_mode = self.st_mode if st_mode & stat.S_IFDIR: mode |= stat.FILE_ATTRIBUTE_DIRECTORY # type:ignore[attr-defined] if st_mode & stat.S_IFREG: mode |= stat.FILE_ATTRIBUTE_NORMAL # type:ignore[attr-defined] if st_mode & (stat.S_IFCHR | stat.S_IFBLK): mode |= stat.FILE_ATTRIBUTE_DEVICE # type:ignore[attr-defined] if st_mode & stat.S_IFLNK: mode |= stat.FILE_ATTRIBUTE_REPARSE_POINT # type:ignore return mode @property def st_reparse_tag(self) -> int: if not self.is_windows or sys.version_info < (3, 8): raise AttributeError( "module 'os.stat_result' has no attribute 'st_reparse_tag'" ) if self.st_mode & stat.S_IFLNK: return stat.IO_REPARSE_TAG_SYMLINK # type: ignore[attr-defined] return 0 def __getitem__(self, item: int) -> Optional[int]: """Implement item access to mimic `os.stat_result` behavior.""" import stat if item == stat.ST_MODE: return self.st_mode if item == stat.ST_INO: return self.st_ino if item == stat.ST_DEV: return self.st_dev if item == stat.ST_NLINK: return self.st_nlink if item == stat.ST_UID: return self.st_uid if item == stat.ST_GID: return self.st_gid if item == stat.ST_SIZE: return self.st_size if item == stat.ST_ATIME: # item access always returns int for backward compatibility return int(self.st_atime) if item == stat.ST_MTIME: return int(self.st_mtime) if item == stat.ST_CTIME: return int(self.st_ctime) raise ValueError("Invalid item") @property def st_atime_ns(self) -> int: """Return the access time in nanoseconds.""" return self._st_atime_ns @st_atime_ns.setter def st_atime_ns(self, val: int) -> None: """Set the access time in nanoseconds.""" self._st_atime_ns = val @property def st_mtime_ns(self) -> int: """Return the modification time in nanoseconds.""" return self._st_mtime_ns @st_mtime_ns.setter def st_mtime_ns(self, val: int) -> None: """Set the modification time of the fake file in nanoseconds.""" self._st_mtime_ns = val @property def st_ctime_ns(self) -> int: """Return the creation time in nanoseconds.""" return self._st_ctime_ns @st_ctime_ns.setter def st_ctime_ns(self, val: int) -> None: """Set the creation time of the fake file in nanoseconds.""" self._st_ctime_ns = val class BinaryBufferIO(io.BytesIO): """Stream class that handles byte contents for files.""" def __init__(self, contents: Optional[bytes]): super().__init__(contents or b"") def putvalue(self, value: bytes) -> None: self.write(value) class TextBufferIO(io.TextIOWrapper): """Stream class that handles Python string contents for files.""" def __init__( self, contents: Optional[bytes] = None, newline: Optional[str] = None, encoding: Optional[str] = None, errors: str = "strict", ): self._bytestream = io.BytesIO(contents or b"") super().__init__(self._bytestream, encoding, errors, newline) def getvalue(self) -> bytes: return self._bytestream.getvalue() def putvalue(self, value: bytes) -> None: self._bytestream.write(value) def is_called_from_skipped_module( skip_names: list, case_sensitive: bool, check_open_code: bool = False ) -> bool: def starts_with(path, string): if case_sensitive: return path.startswith(string) return path.lower().startswith(string.lower()) # in most cases we don't have skip names and won't need the overhead # of analyzing the traceback, except when checking for open_code if not skip_names and not check_open_code: return False stack = traceback.extract_stack() # handle the case that we try to call the original `open_code` # (since Python 3.12) # The stack in this case is: # -1: helpers.is_called_from_skipped_module: 'stack = traceback.extract_stack()' # -2: fake_open.fake_open: 'if is_called_from_skipped_module(' # -3: fake_io.open: 'return fake_open(' # -4: fake_io.open_code : 'return self._io_module.open_code(path)' if ( check_open_code and stack[-4].name == "open_code" and stack[-4].line == "return self._io_module.open_code(path)" ): return True if not skip_names: return False caller_filename = next( ( frame.filename for frame in stack[::-1] if not frame.filename.startswith(" str: from pyfakefs.pytest_tests import lib_using_pathlib return lib_using_pathlib.use_pathlib(path) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/pytest_tests/pytest_check_failed_plugin_test.py0000644000175100001660000000122314764107375026315 0ustar00runnerdocker"""Tests that a failed pytest properly displays the call stack. Uses the output from running pytest with pytest_plugin_failing_helper.py. Regression test for #381. """ import os import pytest @pytest.mark.skipif(not os.path.exists("testresult.txt"), reason="Only run in CI tests") def test_failed_testresult_stacktrace(): with open("testresult.txt") as f: contents = f.read() # before the fix, a triple question mark has been displayed # instead of the stacktrace assert contents print("contents", contents) assert "???" not in contents assert "AttributeError" not in contents assert "def test_fs(fs):" in contents ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/pytest_tests/pytest_doctest_test.py0000644000175100001660000000255414764107375024033 0ustar00runnerdocker""" This is a test case for pyfakefs issue #45. This problem is resolved by using PyTest version 2.8.6 or above. To run these doctests, install pytest and run: $ pytest --doctest-modules pytest_doctest_test.py Add `-s` option to enable print statements. """ def make_file_factory(func_name, fake, result): """Return a simple function with parametrized doctest.""" def make_file(name, content=""): with open(name, "w") as f: f.write(content) make_file.__doc__ = """ >>> import os >>> {command} >>> name, content = 'foo', 'bar' >>> {func_name}(name, content) >>> open(name).read() == content {result} >>> os.remove(name) # Cleanup """.format( command="getfixture('fs')" if fake else "pass", func_name=func_name, result=result, ) return make_file passes = make_file_factory("passes", fake=False, result=True) passes_too = make_file_factory("passes_too", fake=True, result=True) passes_too.__doc__ = passes_too.__doc__.replace(">>> os.remove(name)", ">>> pass") fails = make_file_factory("fails", fake=False, result=False) # Pytest versions below 2.8.6 raise an internal error when running # these doctests: crashes = make_file_factory("crashes", fake=True, result=False) crashes_too = make_file_factory(") SyntaxError", fake=True, result=False) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/pytest_tests/pytest_fixture_param_test.py0000644000175100001660000000354314764107375025233 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Example for a test using a custom pytest fixture with an argument to Patcher import os import pytest import pyfakefs.pytest_tests.example as example @pytest.mark.xfail def test_example_file_failing(fs): """Test fails because EXAMPLE_FILE is cached in the module and not patched.""" fs.create_file(example.EXAMPLE_FILE, contents="stuff here") check_that_example_file_is_in_fake_fs() @pytest.mark.parametrize("fs", [[None, [example]]], indirect=True) def test_example_file_passing_using_parametrized_fixture(fs): """Test passes if using a fixture that reloads the module containing EXAMPLE_FILE""" fs.create_file(example.EXAMPLE_FILE, contents="stuff here") check_that_example_file_is_in_fake_fs() def check_that_example_file_is_in_fake_fs(): with open(example.EXAMPLE_FILE) as file: assert file.read() == "stuff here" with example.EXAMPLE_FILE.open() as file: assert file.read() == "stuff here" assert example.EXAMPLE_FILE.read_text() == "stuff here" assert example.EXAMPLE_FILE.is_file() def test_twice_chdir(fs): # regression test for #530 - make sure that # alternative path separators are correctly handled under Windows fs.create_dir("/absolute/path/to/directory") os.chdir("/absolute/path/to/directory") os.chdir("/absolute/path/to/directory") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/pytest_tests/pytest_fixture_test.py0000644000175100001660000000401414764107375024045 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Example for a test using a custom pytest fixture with an argument to Patcher import pytest import pyfakefs.pytest_tests.example as example from pyfakefs.fake_filesystem_unittest import Patcher from pyfakefs.pytest_tests import unhashable @pytest.mark.xfail def test_example_file_failing(fs): """Test fails because EXAMPLE_FILE is cached in the module and not patched.""" fs.create_file(example.EXAMPLE_FILE, contents="stuff here") check_that_example_file_is_in_fake_fs() def test_example_file_passing_using_fixture(fs_reload_example): """Test passes if using a fixture that reloads the module containing EXAMPLE_FILE""" fs_reload_example.create_file(example.EXAMPLE_FILE, contents="stuff here") check_that_example_file_is_in_fake_fs() def test_example_file_passing_using_patcher(): """Test passes if using a Patcher instance that reloads the module containing EXAMPLE_FILE""" with Patcher(modules_to_reload=[example]) as patcher: patcher.fs.create_file(example.EXAMPLE_FILE, contents="stuff here") check_that_example_file_is_in_fake_fs() def test_unhashable(fs): # regression test for #923 print(unhashable) def check_that_example_file_is_in_fake_fs(): with open(example.EXAMPLE_FILE) as file: assert file.read() == "stuff here" with example.EXAMPLE_FILE.open() as file: assert file.read() == "stuff here" assert example.EXAMPLE_FILE.read_text() == "stuff here" assert example.EXAMPLE_FILE.is_file() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/pytest_tests/pytest_module_fixture_test.py0000644000175100001660000000202414764107375025411 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import pytest @pytest.fixture(scope="module", autouse=True) def use_fs(fs_module): fs_module.create_file(os.path.join("foo", "bar")) yield fs_module @pytest.mark.usefixtures("fs") def test_fs_uses_fs_module1(): # check that `fs` uses the same filesystem as `fs_module` assert os.path.exists(os.path.join("foo", "bar")) def test_fs_uses_fs_module2(fs): # check that patching was not stopped by the first test assert os.path.exists(os.path.join("foo", "bar")) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/pytest_tests/pytest_plugin_failing_helper.py0000644000175100001660000000020014764107375025637 0ustar00runnerdocker"""Failing test to test stacktrace output - see ``pytest_check_failed_plugin_test.py``.""" def test_fs(fs): assert 1 == 2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/pytest_tests/pytest_plugin_test.py0000644000175100001660000000477414764107375023672 0ustar00runnerdocker"""Tests that the pytest plugin properly provides the "fs" fixture""" import os import tempfile from pyfakefs.fake_filesystem import OSType from pyfakefs.fake_filesystem_unittest import Pause import pyfakefs.pytest_tests.io def test_fs_fixture(fs): fs.create_file("/var/data/xx1.txt") assert os.path.exists("/var/data/xx1.txt") def test_fs_fixture_alias(fake_filesystem): fake_filesystem.create_file("/var/data/xx1.txt") assert os.path.exists("/var/data/xx1.txt") def test_both_fixtures(fs, fake_filesystem): fake_filesystem.create_file("/var/data/xx1.txt") fs.create_file("/var/data/xx2.txt") assert os.path.exists("/var/data/xx1.txt") assert os.path.exists("/var/data/xx2.txt") assert fs == fake_filesystem def test_pause_resume(fs): fake_temp_file = tempfile.NamedTemporaryFile() assert fs.exists(fake_temp_file.name) assert os.path.exists(fake_temp_file.name) fs.pause() assert fs.exists(fake_temp_file.name) assert not os.path.exists(fake_temp_file.name) real_temp_file = tempfile.NamedTemporaryFile() assert not fs.exists(real_temp_file.name) assert os.path.exists(real_temp_file.name) fs.resume() assert not os.path.exists(real_temp_file.name) assert os.path.exists(fake_temp_file.name) def test_pause_resume_contextmanager(fs): fake_temp_file = tempfile.NamedTemporaryFile() assert fs.exists(fake_temp_file.name) assert os.path.exists(fake_temp_file.name) with Pause(fs): assert fs.exists(fake_temp_file.name) assert not os.path.exists(fake_temp_file.name) real_temp_file = tempfile.NamedTemporaryFile() assert not fs.exists(real_temp_file.name) assert os.path.exists(real_temp_file.name) assert not os.path.exists(real_temp_file.name) assert os.path.exists(fake_temp_file.name) def test_use_own_io_module(fs): filepath = "foo.txt" with open(filepath, "w") as f: f.write("bar") stream = pyfakefs.pytest_tests.io.InputStream(filepath) assert stream.read() == "bar" def test_switch_to_windows(fs): fs.os = OSType.WINDOWS assert os.path.exists(tempfile.gettempdir()) def test_switch_to_linux(fs): fs.os = OSType.LINUX assert os.path.exists(tempfile.gettempdir()) def test_switch_to_macos(fs): fs.os = OSType.MACOS assert os.path.exists(tempfile.gettempdir()) def test_updatecache_problem(fs): # regression test for #1096 filename = r"C:\source_file" fs.create_file(filename) with open(filename): assert True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/pytest_tests/pytest_reload_pandas_test.py0000644000175100001660000000132314764107375025153 0ustar00runnerdocker"""Regression test for #947. Ensures that reloading the `pandas.core.arrays.arrow.extension_types` module succeeds. """ from pathlib import Path import pytest try: import pandas as pd except ImportError: pd = None try: import parquet except ImportError: parquet = None @pytest.mark.skipif( pd is None or parquet is None, reason="pandas or parquet not installed" ) def test_1(fs): dir_ = Path(__file__).parent / "data" fs.add_real_directory(dir_) pd.read_parquet(dir_ / "test.parquet") @pytest.mark.skipif( pd is None or parquet is None, reason="pandas or parquet not installed" ) def test_2(): dir_ = Path(__file__).parent / "data" pd.read_parquet(dir_ / "test.parquet") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/pytest_tests/test_reload_local_import.py0000644000175100001660000000140314764107375024760 0ustar00runnerdockerimport pytest from pyfakefs.fake_filesystem_unittest import Patcher from pyfakefs.fake_pathlib import FakePathlibModule from pyfakefs.helpers import reload_cleanup_handler from pyfakefs.pytest_tests import local_import @pytest.fixture def test_fs(): with Patcher() as patcher: patcher.cleanup_handlers["pyfakefs.pytest_tests.lib_using_pathlib"] = ( reload_cleanup_handler ) yield patcher.fs class TestReloadCleanupHandler: def test1(self, test_fs): path = local_import.load("some_path") assert isinstance(path, FakePathlibModule.Path) def test2(self): path = local_import.load("some_path") # will fail without reload handler assert not isinstance(path, FakePathlibModule.Path) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/pytest_tests/unhashable.py0000644000175100001660000000060614764107375022025 0ustar00runnerdockerimport sys import types class Unhashable(types.ModuleType): """ Unhashable module, used for regression test for #923. """ @property def Unhashable(self): return self def __eq__(self, other): raise NotImplementedError("Cannot compare unhashable") if sys.modules[__name__] is not Unhashable: sys.modules[__name__] = Unhashable("unhashable") ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741721344.967527 pyfakefs-5.8.0/pyfakefs/tests/0000755000175100001660000000000014764107401015715 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/__init__.py0000644000175100001660000000000014764107375020026 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/all_tests.py0000644000175100001660000000505214764107375020275 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """A test suite that runs all tests for pyfakefs at once. Includes tests with external pathlib2 and scandir packages if installed.""" import sys import unittest from pyfakefs.tests import ( dynamic_patch_test, fake_stat_time_test, example_test, fake_filesystem_glob_test, fake_filesystem_shutil_test, fake_filesystem_test, fake_filesystem_unittest_test, fake_filesystem_vs_real_test, fake_open_test, fake_os_test, fake_pathlib_test, fake_tempfile_test, patched_packages_test, fake_legacy_modules_test, mox3_stubout_test, ) class AllTests(unittest.TestSuite): """A test suite that runs all tests for pyfakefs at once.""" def suite(self): # pylint: disable-msg=C6409 loader = unittest.defaultTestLoader self.addTests( [ loader.loadTestsFromModule(fake_filesystem_test), loader.loadTestsFromModule(fake_filesystem_glob_test), loader.loadTestsFromModule(fake_filesystem_shutil_test), loader.loadTestsFromModule(fake_os_test), loader.loadTestsFromModule(fake_stat_time_test), loader.loadTestsFromModule(fake_open_test), loader.loadTestsFromModule(fake_tempfile_test), loader.loadTestsFromModule(fake_filesystem_vs_real_test), loader.loadTestsFromModule(fake_filesystem_unittest_test), loader.loadTestsFromModule(example_test), loader.loadTestsFromModule(mox3_stubout_test), loader.loadTestsFromModule(dynamic_patch_test), loader.loadTestsFromModule(fake_pathlib_test), loader.loadTestsFromModule(patched_packages_test), loader.loadTestsFromModule(fake_legacy_modules_test), ] ) return self if __name__ == "__main__": result = unittest.TextTestRunner(verbosity=2).run(AllTests().suite()) sys.exit(int(not result.wasSuccessful())) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/all_tests_without_extra_packages.py0000644000175100001660000000174114764107375025122 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """A test suite that runs all tests for pyfakefs at once. Excludes tests using external scandir package.""" import sys import unittest from pyfakefs import legacy_packages legacy_packages.scandir = None legacy_packages.pathlib2 = None from pyfakefs.tests.all_tests import AllTests # noqa: E402 if __name__ == "__main__": result = unittest.TextTestRunner(verbosity=2).run(AllTests().suite()) sys.exit(int(not result.wasSuccessful())) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/dynamic_patch_test.py0000644000175100001660000000404314764107375022144 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Tests for patching modules loaded after `setUpPyfakefs()`. """ import pathlib import unittest from pyfakefs import fake_filesystem_unittest class TestPyfakefsUnittestBase(fake_filesystem_unittest.TestCase): def setUp(self): """Set up the fake file system""" self.setUpPyfakefs() class DynamicImportPatchTest(TestPyfakefsUnittestBase): def __init__(self, methodName="runTest"): super().__init__(methodName) def test_os_patch(self): import os os.mkdir("test") self.assertTrue(self.fs.exists("test")) self.assertTrue(os.path.exists("test")) def test_os_import_as_patch(self): import os as _os _os.mkdir("test") self.assertTrue(self.fs.exists("test")) self.assertTrue(_os.path.exists("test")) def test_os_path_patch(self): import os.path os.mkdir("test") self.assertTrue(self.fs.exists("test")) self.assertTrue(os.path.exists("test")) def test_shutil_patch(self): import shutil self.fs.set_disk_usage(100) self.assertEqual(100, shutil.disk_usage("/").total) def test_pathlib_path_patch(self): file_path = "test.txt" path = pathlib.Path(file_path) with path.open("w", encoding="utf8") as f: f.write("test") self.assertTrue(self.fs.exists(file_path)) file_object = self.fs.get_object(file_path) self.assertEqual("test", file_object.contents) if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/example.py0000644000175100001660000000764314764107375017746 0ustar00runnerdocker# Copyright 2014 Altera Corporation. All Rights Reserved. # Author: John McGehee # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Example module that is tested in :py:class`pyfakefs.example_test.TestExample`. This demonstrates the usage of the :py:class`pyfakefs.fake_filesystem_unittest.TestCase` base class. The modules related to file handling are bound to the respective fake modules: >>> os #doctest: +ELLIPSIS >>> os.path #doctest: +ELLIPSIS >>> shutil #doctest: +ELLIPSIS `open()` is an alias for `io.open()` and is bound to `FakeIoModule.open`. """ import glob import os import shutil try: import scandir has_scandir = True except ImportError: scandir = None has_scandir = False def create_file(path): """Create the specified file and add some content to it. Use the `open()` built in function. For example, the following file operations occur in the fake file system. In the real file system, we would not even have permission to write `/test`: >>> os.path.isdir('/test') False >>> os.mkdir('/test') >>> os.path.isdir('/test') True >>> os.path.exists('/test/file.txt') False >>> create_file('/test/file.txt') >>> os.path.exists('/test/file.txt') True >>> with open('/test/file.txt', encoding='utf8') as f: ... f.readlines() ["This is test file '/test/file.txt'.\\n", \ 'It was created using open().\\n'] """ with open(path, "w", encoding="utf8") as f: f.write(f"This is test file '{path}'.\n") f.write("It was created using open().\n") def delete_file(path): """Delete the specified file. For example: >>> os.mkdir('/test') >>> os.path.exists('/test/file.txt') False >>> create_file('/test/file.txt') >>> os.path.exists('/test/file.txt') True >>> delete_file('/test/file.txt') >>> os.path.exists('/test/file.txt') False """ os.remove(path) def path_exists(path): """Return True if the specified file exists. For example: >>> path_exists('/test') False >>> os.mkdir('/test') >>> path_exists('/test') True >>> >>> path_exists('/test/file.txt') False >>> create_file('/test/file.txt') >>> path_exists('/test/file.txt') True """ return os.path.exists(path) def get_glob(glob_path): r"""Return the list of paths matching the specified glob expression. For example: >>> os.mkdir('/test') >>> create_file('/test/file1.txt') >>> create_file('/test/file2.txt') >>> file_names = sorted(get_glob('/test/file*.txt')) >>> >>> import sys >>> if sys.platform.startswith('win'): ... # Windows style path ... file_names == [r'/test\file1.txt', r'/test\file2.txt'] ... else: ... # UNIX style path ... file_names == ['/test/file1.txt', '/test/file2.txt'] True """ return glob.glob(glob_path) def rm_tree(path): """Delete the specified file hierarchy.""" shutil.rmtree(path) def scan_dir(path): """Return a list of directory entries for the given path.""" if has_scandir: return list(scandir.scandir(path)) return list(os.scandir(path)) def file_contents(path): """Return the contents of the given path as byte array.""" with open(path, "rb") as f: return f.read() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/example_test.py0000644000175100001660000001553214764107375021001 0ustar00runnerdocker# Copyright 2014 Altera Corporation. All Rights Reserved. # Author: John McGehee # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Test the :py:class`pyfakefs.example` module to demonstrate the usage of the :py:class`pyfakefs.fake_filesystem_unittest.TestCase` base class. Fake filesystem functions like `create_file()`, `create_dir()` or `create_symlink()` are often used to set up file structures at the beginning of a test. While you could also use the familiar `open()`, `os.mkdirs()` and similar functions, these functions can make the test code shorter and more readable. `create_file()` is particularly convenient because it creates all parent directories and allows you to specify the contents or the size of the file. """ import io import os import sys import unittest from pyfakefs import fake_filesystem_unittest from pyfakefs.legacy_packages import scandir from pyfakefs.tests import example # The module under test # Work around pyupgrade auto-rewriting `io.open()` to `open()`. io_open = io.open def load_tests(loader, tests, ignore): """Load the pyfakefs/example.py doctest tests into unittest.""" return fake_filesystem_unittest.load_doctests(loader, tests, ignore, example) class TestExample(fake_filesystem_unittest.TestCase): # pylint: disable=R0904 """Test the example module. The os and shutil modules have been replaced with the fake modules, so that all of the calls to os and shutil in the tested example code occur in the fake filesystem. """ def setUp(self): """Invoke the :py:class:`pyfakefs.fake_filesystem_unittest.TestCase` `self.setUp()` method. This defines: * Attribute `self.fs`, an instance of :py:class:`pyfakefs.fake_filesystem.FakeFilesystem`. This is useful for creating test files. * Attribute `self.stubs`, an instance of :py:class:`pyfakefs.mox3_stubout.StubOutForTesting`. Use this if you need to define additional stubs. """ # This is before setUpPyfakefs(), so still using the real file system self.filepath = os.path.realpath(__file__) with io_open(self.filepath, "rb") as f: self.real_contents = f.read() self.setUpPyfakefs() def tearDown(self): # No longer need self.tearDownPyfakefs() pass def test_create_file(self): """Test example.create_file() which uses `open()` and `file.write()`. """ self.assertFalse(os.path.isdir("/test")) os.mkdir("/test") self.assertTrue(os.path.isdir("/test")) self.assertFalse(os.path.exists("/test/file.txt")) example.create_file("/test/file.txt") self.assertTrue(os.path.exists("/test/file.txt")) def test_delete_file(self): """Test example.delete_file() which uses `os.remove()`.""" self.fs.create_file("/test/full.txt", contents="First line\nSecond Line\n") self.assertTrue(os.path.exists("/test/full.txt")) example.delete_file("/test/full.txt") self.assertFalse(os.path.exists("/test/full.txt")) def test_file_exists(self): """Test example.path_exists() which uses `os.path.exists()`.""" self.assertFalse(example.path_exists("/test/empty.txt")) self.fs.create_file("/test/empty.txt") self.assertTrue(example.path_exists("/test/empty.txt")) def test_get_globs(self): """Test example.get_glob().""" self.assertFalse(os.path.isdir("/test")) self.fs.create_dir("/test/dir1/dir2a") self.assertTrue(os.path.isdir("/test/dir1/dir2a")) # os.mkdirs() works, too. os.makedirs("/test/dir1/dir2b") self.assertTrue(os.path.isdir("/test/dir1/dir2b")) self.assertEqual(example.get_glob("/test/dir1/nonexistent*"), []) is_windows = sys.platform.startswith("win") matching_paths = sorted(example.get_glob("/test/dir1/dir*")) if is_windows: self.assertEqual(matching_paths, [r"/test/dir1\dir2a", r"/test/dir1\dir2b"]) else: self.assertEqual(matching_paths, ["/test/dir1/dir2a", "/test/dir1/dir2b"]) def test_rm_tree(self): """Test example.rm_tree() using `shutil.rmtree()`.""" self.fs.create_dir("/test/dir1/dir2a") # os.mkdirs() works, too. os.makedirs("/test/dir1/dir2b") self.assertTrue(os.path.isdir("/test/dir1/dir2b")) self.assertTrue(os.path.isdir("/test/dir1/dir2a")) example.rm_tree("/test/dir1") self.assertFalse(os.path.exists("/test/dir1")) def test_os_scandir(self): """Test example.scandir() which uses `os.scandir()`. The os module has been replaced with the fake os module so the fake filesystem path entries are returned instead of `os.DirEntry` objects. """ self.fs.create_file("/test/text.txt") self.fs.create_dir("/test/dir") self.fs.create_file("/linktest/linked") self.fs.create_symlink("/test/linked_file", "/linktest/linked") entries = sorted(example.scan_dir("/test"), key=lambda e: e.name) self.assertEqual(3, len(entries)) self.assertEqual("linked_file", entries[1].name) self.assertTrue(entries[0].is_dir()) self.assertTrue(entries[1].is_symlink()) self.assertTrue(entries[2].is_file()) @unittest.skipIf(scandir is None, "Testing only if scandir module is installed") def test_scandir_scandir(self): """Test example.scandir() which uses `scandir.scandir()`. The scandir module has been replaced with the fake_scandir module so the fake filesystem path entries are returned instead of `scandir.DirEntry` objects. """ self.fs.create_file("/test/text.txt") self.fs.create_dir("/test/dir") entries = sorted(example.scan_dir("/test"), key=lambda e: e.name) self.assertEqual(2, len(entries)) self.assertEqual("text.txt", entries[1].name) self.assertTrue(entries[0].is_dir()) self.assertTrue(entries[1].is_file()) def test_real_file_access(self): """Test `example.file_contents()` for a real file after adding it using `add_real_file()`.""" with self.assertRaises(OSError): example.file_contents(self.filepath) self.fs.add_real_file(self.filepath) self.assertEqual(example.file_contents(self.filepath), self.real_contents) if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fake_filesystem_glob_test.py0000644000175100001660000000533314764107375023521 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Test for glob using fake_filesystem.""" import contextlib import glob import os import sys import unittest from pyfakefs import fake_filesystem_unittest class FakeGlobUnitTest(fake_filesystem_unittest.TestCase): def setUp(self): self.setUpPyfakefs() directory = "./xyzzy" self.fs.create_dir(directory) self.fs.create_dir("%s/subdir" % directory) self.fs.create_dir("%s/subdir2" % directory) self.fs.create_file("%s/subfile" % directory) self.fs.create_file("[Temp]") def test_glob_empty(self): self.assertEqual(glob.glob(""), []) def test_glob_star(self): basedir = "/xyzzy" self.assertEqual( [ os.path.join(basedir, "subdir"), os.path.join(basedir, "subdir2"), os.path.join(basedir, "subfile"), ], sorted(glob.glob("/xyzzy/*")), ) def test_glob_exact(self): self.assertEqual(["/xyzzy"], glob.glob("/xyzzy")) self.assertEqual(["/xyzzy/subfile"], glob.glob("/xyzzy/subfile")) def test_glob_question(self): basedir = "/xyzzy" self.assertEqual( [ os.path.join(basedir, "subdir"), os.path.join(basedir, "subdir2"), os.path.join(basedir, "subfile"), ], sorted(glob.glob("/x?zz?/*")), ) def test_glob_no_magic(self): self.assertEqual(["/xyzzy"], glob.glob("/xyzzy")) self.assertEqual(["/xyzzy/subdir"], glob.glob("/xyzzy/subdir")) def test_non_existent_path(self): self.assertEqual([], glob.glob("nonexistent")) def test_magic_dir(self): self.assertEqual(["/[Temp]"], glob.glob("/*emp*")) def test_glob1(self): with ( contextlib.nullcontext() if sys.version_info < (3, 13) else self.assertWarns(DeprecationWarning) ): self.assertEqual(["[Temp]"], glob.glob1("/", "*Tem*")) def test_has_magic(self): self.assertTrue(glob.has_magic("[")) self.assertFalse(glob.has_magic("a")) if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fake_filesystem_shutil_test.py0000644000175100001660000005414114764107375024107 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Tests for `fake_filesystem_shutil` if used in `fake_filesystem_unittest.TestCase`. Note that almost all of the functionality is delegated to the real `shutil` and works correctly with the fake filesystem because of the faked `os` module. """ import os import shutil import sys import tempfile import unittest from pathlib import Path from pyfakefs import fake_filesystem_unittest from pyfakefs.helpers import get_uid, set_uid, is_root, IS_PYPY from pyfakefs.tests.test_utils import RealFsTestMixin, skip_if_symlink_not_supported is_windows = sys.platform == "win32" class RealFsTestCase(fake_filesystem_unittest.TestCase, RealFsTestMixin): def __init__(self, methodName="runTest"): fake_filesystem_unittest.TestCase.__init__(self, methodName) RealFsTestMixin.__init__(self) def setUp(self): RealFsTestMixin.setUp(self) self.cwd = os.getcwd() self.uid = get_uid() set_uid(1000) if not self.use_real_fs(): self.setUpPyfakefs() self.filesystem = self.fs self.os = os self.open = open self.create_basepath() self.fs.set_disk_usage(1000, self.base_path) def tearDown(self): set_uid(self.uid) RealFsTestMixin.tearDown(self) @property def is_windows_fs(self): if self.use_real_fs(): return sys.platform == "win32" return self.filesystem.is_windows_fs class FakeShutilModuleTest(RealFsTestCase): @unittest.skipIf(is_windows, "Posix specific behavior") def test_catch_permission_error(self): root_path = self.make_path("rootpath") self.create_dir(root_path) dir1_path = self.os.path.join(root_path, "dir1") dir2_path = self.os.path.join(root_path, "dir2") self.create_dir(dir1_path) self.os.chmod(dir1_path, 0o555) # remove write permissions self.create_dir(dir2_path) old_file_path = self.os.path.join(dir2_path, "f1.txt") new_file_path = self.os.path.join(dir1_path, "f1.txt") self.create_file(old_file_path) with self.assertRaises(PermissionError): shutil.move(old_file_path, new_file_path) def test_rmtree(self): directory = self.make_path("xyzzy") dir_path = os.path.join(directory, "subdir") self.create_dir(dir_path) file_path = os.path.join(directory, "subfile") self.create_file(file_path) self.assertTrue(os.path.exists(directory)) shutil.rmtree(directory) self.assertFalse(os.path.exists(directory)) self.assertFalse(os.path.exists(dir_path)) self.assertFalse(os.path.exists(file_path)) def test_rmtree_with_trailing_slash(self): directory = self.make_path("xyzzy") dir_path = os.path.join(directory, "subdir") self.create_dir(dir_path) file_path = os.path.join(directory, "subfile") self.create_file(file_path) shutil.rmtree(directory + "/") self.assertFalse(os.path.exists(directory)) self.assertFalse(os.path.exists(dir_path)) self.assertFalse(os.path.exists(file_path)) @unittest.skipIf(not is_windows, "Windows specific behavior") def test_rmtree_without_permission_for_a_file_in_windows(self): self.check_windows_only() dir_path = self.make_path("foo") self.create_file(os.path.join(dir_path, "bar")) file_path = os.path.join(dir_path, "baz") self.create_file(file_path) self.os.chmod(file_path, 0o444) with self.assertRaises(OSError): shutil.rmtree(dir_path) self.assertTrue(os.path.exists(file_path)) self.os.chmod(file_path, 0o666) @unittest.skipIf(is_windows, "Posix specific behavior") def test_rmtree_without_permission_for_a_dir_in_posix(self): self.check_posix_only() dir_path = self.make_path("foo") self.create_file(os.path.join(dir_path, "bar")) file_path = os.path.join(dir_path, "baz") self.create_file(file_path) self.os.chmod(dir_path, 0o555) if not is_root(): with self.assertRaises(OSError): shutil.rmtree(dir_path) self.assertTrue(os.path.exists(file_path)) self.os.chmod(dir_path, 0o777) else: shutil.rmtree(dir_path) self.assertFalse(os.path.exists(file_path)) @unittest.skipIf(is_windows, "Posix specific behavior") def test_rmtree_with_open_file_posix(self): self.check_posix_only() dir_path = self.make_path("foo") self.create_file(os.path.join(dir_path, "bar")) file_path = os.path.join(dir_path, "baz") self.create_file(file_path) with open(file_path, encoding="utf8"): shutil.rmtree(dir_path) self.assertFalse(os.path.exists(file_path)) @unittest.skipIf(not is_windows, "Windows specific behavior") def test_rmtree_with_open_file_fails_under_windows(self): self.check_windows_only() dir_path = self.make_path("foo") self.create_file(os.path.join(dir_path, "bar")) file_path = os.path.join(dir_path, "baz") self.create_file(file_path) with open(file_path, encoding="utf8"): with self.assertRaises(OSError): shutil.rmtree(dir_path) self.assertTrue(os.path.exists(dir_path)) def test_rmtree_non_existing_dir(self): directory = "nonexisting" with self.assertRaises(OSError): shutil.rmtree(directory) try: shutil.rmtree(directory, ignore_errors=True) except OSError: self.fail("rmtree raised despite ignore_errors True") def test_rmtree_non_existing_dir_with_handler(self): class NonLocal: pass def error_handler(_, path, _error_info): NonLocal.errorHandled = True NonLocal.errorPath = path directory = self.make_path("nonexisting") NonLocal.errorHandled = False NonLocal.errorPath = "" try: shutil.rmtree(directory, onerror=error_handler) except OSError: self.fail("rmtree raised exception despite onerror defined") self.assertTrue(NonLocal.errorHandled) self.assertEqual(NonLocal.errorPath, directory) NonLocal.errorHandled = False NonLocal.errorPath = "" try: shutil.rmtree(directory, ignore_errors=True, onerror=error_handler) except OSError: self.fail("rmtree raised exception despite ignore_errors True") # ignore_errors is True, so the onerror() error handler was # not executed self.assertFalse(NonLocal.errorHandled) self.assertEqual(NonLocal.errorPath, "") def test_rmtree_in_windows(self): # regression test for #979 self.check_windows_only() base_path = self.make_path("foo", "bar") self.os.makedirs(self.os.path.join(base_path, "res")) self.assertTrue(self.os.path.exists(base_path)) shutil.rmtree(base_path) self.assertFalse(self.os.path.exists(base_path)) def test_copy(self): src_file = self.make_path("xyzzy") dst_file = self.make_path("xyzzy_copy") self.create_file(src_file) os.chmod(src_file, 0o750) self.assertTrue(os.path.exists(src_file)) self.assertFalse(os.path.exists(dst_file)) shutil.copy(src_file, dst_file) self.assertTrue(os.path.exists(dst_file)) self.assertEqual(os.stat(src_file).st_mode, os.stat(dst_file).st_mode) def test_copy_directory(self): src_file = self.make_path("xyzzy") parent_directory = self.make_path("parent") dst_file = os.path.join(parent_directory, "xyzzy") self.create_file(src_file) self.create_dir(parent_directory) os.chmod(src_file, 0o750) self.assertTrue(os.path.exists(src_file)) self.assertTrue(os.path.exists(parent_directory)) self.assertFalse(os.path.exists(dst_file)) shutil.copy(src_file, parent_directory) self.assertTrue(os.path.exists(dst_file)) self.assertEqual(os.stat(src_file).st_mode, os.stat(dst_file).st_mode) def test_copystat(self): src_file = self.make_path("xyzzy") self.create_file(src_file) os.chmod(src_file, 0o750) dst_file = self.make_path("xyzzy_copy") self.create_file(dst_file) self.assertTrue(os.path.exists(src_file)) self.assertTrue(os.path.exists(dst_file)) shutil.copystat(src_file, dst_file) src_stat = os.stat(src_file) dst_stat = os.stat(dst_file) self.assertEqual(src_stat.st_mode, dst_stat.st_mode) self.assertAlmostEqual(src_stat.st_atime, dst_stat.st_atime, places=0) self.assertAlmostEqual(src_stat.st_mtime, dst_stat.st_mtime, places=2) @unittest.skipIf(IS_PYPY, "Functionality not supported in PyPy") def test_copystat_symlinks(self): """Regression test for #799""" skip_if_symlink_not_supported() f = self.make_path("xyzzy") self.create_file(f) sym1 = self.make_path("sym1") sym2 = self.make_path("sym2") self.create_symlink(sym1, f) self.create_symlink(sym2, f) shutil.copystat(sym1, sym2, follow_symlinks=False) def test_copy2(self): src_file = self.make_path("xyzzy") self.create_file(src_file) os.chmod(src_file, 0o750) dst_file = self.make_path("xyzzy_copy") self.assertTrue(os.path.exists(src_file)) self.assertFalse(os.path.exists(dst_file)) shutil.copy2(src_file, dst_file) self.assertTrue(os.path.exists(dst_file)) src_stat = os.stat(src_file) dst_stat = os.stat(dst_file) self.assertEqual(src_stat.st_mode, dst_stat.st_mode) self.assertAlmostEqual(src_stat.st_atime, dst_stat.st_atime, places=0) self.assertAlmostEqual(src_stat.st_mtime, dst_stat.st_mtime, places=2) def test_copy2_directory(self): src_file = self.make_path("xyzzy") parent_directory = self.make_path("parent") dst_file = os.path.join(parent_directory, "xyzzy") self.create_file(src_file) self.create_dir(parent_directory) os.chmod(src_file, 0o750) self.assertTrue(os.path.exists(src_file)) self.assertTrue(os.path.exists(parent_directory)) self.assertFalse(os.path.exists(dst_file)) shutil.copy2(src_file, parent_directory) self.assertTrue(os.path.exists(dst_file)) src_stat = os.stat(src_file) dst_stat = os.stat(dst_file) self.assertEqual(src_stat.st_mode, dst_stat.st_mode) self.assertAlmostEqual(src_stat.st_atime, dst_stat.st_atime, places=0) self.assertAlmostEqual(src_stat.st_mtime, dst_stat.st_mtime, places=2) def test_copytree(self): src_directory = self.make_path("xyzzy") dst_directory = self.make_path("xyzzy_copy") self.create_dir(src_directory) self.create_dir("%s/subdir" % src_directory) self.create_file(os.path.join(src_directory, "subfile")) self.assertTrue(os.path.exists(src_directory)) self.assertFalse(os.path.exists(dst_directory)) shutil.copytree(src_directory, dst_directory) self.assertTrue(os.path.exists(dst_directory)) self.assertTrue(os.path.exists(os.path.join(dst_directory, "subdir"))) self.assertTrue(os.path.exists(os.path.join(dst_directory, "subfile"))) def test_copytree_src_is_file(self): src_file = self.make_path("xyzzy") dst_directory = self.make_path("xyzzy_copy") self.create_file(src_file) self.assertTrue(os.path.exists(src_file)) self.assertFalse(os.path.exists(dst_directory)) with self.assertRaises(OSError): shutil.copytree(src_file, dst_directory) def test_move_file_in_same_filesystem(self): self.skip_real_fs() src_file = "/original_xyzzy" dst_file = "/moved_xyzzy" src_object = self.fs.create_file(src_file) src_ino = src_object.st_ino src_dev = src_object.st_dev self.assertTrue(os.path.exists(src_file)) self.assertFalse(os.path.exists(dst_file)) shutil.move(src_file, dst_file) self.assertTrue(os.path.exists(dst_file)) self.assertFalse(os.path.exists(src_file)) dst_object = self.fs.get_object(dst_file) self.assertEqual(src_ino, dst_object.st_ino) self.assertEqual(src_dev, dst_object.st_dev) def test_move_file_into_other_filesystem(self): self.skip_real_fs() mount_point = self.create_mount_point() src_file = self.make_path("original_xyzzy") dst_file = self.os.path.join(mount_point, "moved_xyzzy") src_object = self.fs.create_file(src_file) src_ino = src_object.st_ino src_dev = src_object.st_dev shutil.move(src_file, dst_file) self.assertTrue(os.path.exists(dst_file)) self.assertFalse(os.path.exists(src_file)) dst_object = self.fs.get_object(dst_file) self.assertNotEqual(src_ino, dst_object.st_ino) self.assertNotEqual(src_dev, dst_object.st_dev) def test_move_file_into_directory(self): src_file = self.make_path("xyzzy") dst_directory = self.make_path("directory") dst_file = os.path.join(dst_directory, "xyzzy") self.create_file(src_file) self.create_dir(dst_directory) self.assertTrue(os.path.exists(src_file)) self.assertFalse(os.path.exists(dst_file)) shutil.move(src_file, dst_directory) self.assertTrue(os.path.exists(dst_file)) self.assertFalse(os.path.exists(src_file)) def test_move_directory(self): src_directory = self.make_path("original_xyzzy") dst_directory = self.make_path("moved_xyzzy") self.create_dir(src_directory) self.create_file(os.path.join(src_directory, "subfile")) self.create_dir(os.path.join(src_directory, "subdir")) self.assertTrue(os.path.exists(src_directory)) self.assertFalse(os.path.exists(dst_directory)) shutil.move(src_directory, dst_directory) self.assertTrue(os.path.exists(dst_directory)) self.assertTrue(os.path.exists(os.path.join(dst_directory, "subfile"))) self.assertTrue(os.path.exists(os.path.join(dst_directory, "subdir"))) self.assertFalse(os.path.exists(src_directory)) def test_disk_usage(self): self.skip_real_fs() file_path = self.make_path("foo", "bar") self.fs.create_file(file_path, st_size=400) disk_usage = shutil.disk_usage(file_path) self.assertEqual(1000, disk_usage.total) self.assertEqual(400, disk_usage.used) self.assertEqual(600, disk_usage.free) self.assertEqual((1000, 400, 600), disk_usage) mount_point = self.create_mount_point() dir_path = self.os.path.join(mount_point, "foo") file_path = self.os.path.join(dir_path, "bar") self.fs.create_file(file_path, st_size=400) disk_usage = shutil.disk_usage(dir_path) self.assertEqual((500, 400, 100), disk_usage) def test_disk_usage_with_path(self): self.skip_real_fs() file_path = self.make_path("foo", "bar") self.fs.create_file(file_path, st_size=400) path = Path(file_path) disk_usage = shutil.disk_usage(path) self.assertEqual(1000, disk_usage.total) self.assertEqual(400, disk_usage.used) self.assertEqual(600, disk_usage.free) self.assertEqual((1000, 400, 600), disk_usage) def create_mount_point(self): mount_point = "M:" if self.is_windows_fs else "/mount" self.fs.add_mount_point(mount_point, total_size=500) return mount_point class RealShutilModuleTest(FakeShutilModuleTest): def use_real_fs(self): return True class FakeCopyFileTest(RealFsTestCase): def tearDown(self): super().tearDown() def test_common_case(self): src_file = self.make_path("xyzzy") dst_file = self.make_path("xyzzy_copy") contents = "contents of file" self.create_file(src_file, contents=contents) self.assertTrue(os.path.exists(src_file)) self.assertFalse(os.path.exists(dst_file)) shutil.copyfile(src_file, dst_file) self.assertTrue(os.path.exists(dst_file)) self.check_contents(dst_file, contents) def test_raises_if_source_and_dest_are_the_same_file(self): src_file = self.make_path("xyzzy") dst_file = src_file contents = "contents of file" self.create_file(src_file, contents=contents) self.assertTrue(os.path.exists(src_file)) with self.assertRaises(shutil.Error): shutil.copyfile(src_file, dst_file) def test_raises_if_dest_is_a_symlink_to_src(self): skip_if_symlink_not_supported() src_file = self.make_path("foo") dst_file = self.make_path("bar") contents = "contents of file" self.create_file(src_file, contents=contents) self.create_symlink(dst_file, src_file) self.assertTrue(os.path.exists(src_file)) with self.assertRaises(shutil.Error): shutil.copyfile(src_file, dst_file) def test_succeeds_if_dest_exists_and_is_writable(self): src_file = self.make_path("xyzzy") dst_file = self.make_path("xyzzy_copy") src_contents = "contents of source file" dst_contents = "contents of dest file" self.create_file(src_file, contents=src_contents) self.create_file(dst_file, contents=dst_contents) self.assertTrue(os.path.exists(src_file)) self.assertTrue(os.path.exists(dst_file)) shutil.copyfile(src_file, dst_file) self.assertTrue(os.path.exists(dst_file)) self.check_contents(dst_file, src_contents) def test_raises_if_dest_exists_and_is_not_writable(self): src_file = self.make_path("xyzzy") dst_file = self.make_path("xyzzy_copy") src_contents = "contents of source file" dst_contents = "contents of dest file" self.create_file(src_file, contents=src_contents) self.create_file(dst_file, contents=dst_contents) os.chmod(dst_file, 0o400) self.assertTrue(os.path.exists(src_file)) self.assertTrue(os.path.exists(dst_file)) if is_root(): shutil.copyfile(src_file, dst_file) self.assertTrue(self.os.path.exists(dst_file)) with self.open(dst_file) as f: self.assertEqual("contents of source file", f.read()) else: with self.assertRaises(OSError): shutil.copyfile(src_file, dst_file) os.chmod(dst_file, 0o666) @unittest.skipIf(is_windows, "Posix specific behavior") def test_raises_if_dest_dir_is_not_writable_under_posix(self): self.check_posix_only() src_file = self.make_path("xyzzy") dst_dir = self.make_path("tmp", "foo") dst_file = os.path.join(dst_dir, "xyzzy") src_contents = "contents of source file" self.create_file(src_file, contents=src_contents) self.create_dir(dst_dir) os.chmod(dst_dir, 0o555) self.assertTrue(os.path.exists(src_file)) self.assertTrue(os.path.exists(dst_dir)) if not is_root(): with self.assertRaises(OSError): shutil.copyfile(src_file, dst_file) else: shutil.copyfile(src_file, dst_file) self.assertTrue(os.path.exists(dst_file)) self.check_contents(dst_file, src_contents) def test_raises_if_src_doesnt_exist(self): src_file = self.make_path("xyzzy") dst_file = self.make_path("xyzzy_copy") self.assertFalse(os.path.exists(src_file)) with self.assertRaises(OSError): shutil.copyfile(src_file, dst_file) @unittest.skipIf(is_windows, "Posix specific behavior") def test_raises_if_src_not_readable(self): self.check_posix_only() src_file = self.make_path("xyzzy") dst_file = self.make_path("xyzzy_copy") src_contents = "contents of source file" self.create_file(src_file, contents=src_contents) os.chmod(src_file, 0o000) self.assertTrue(os.path.exists(src_file)) if not is_root(): with self.assertRaises(OSError): shutil.copyfile(src_file, dst_file) else: shutil.copyfile(src_file, dst_file) self.assertTrue(os.path.exists(dst_file)) self.check_contents(dst_file, src_contents) def test_raises_if_src_is_a_directory(self): src_file = self.make_path("xyzzy") dst_file = self.make_path("xyzzy_copy") self.create_dir(src_file) self.assertTrue(os.path.exists(src_file)) with self.assertRaises(OSError): shutil.copyfile(src_file, dst_file) def test_raises_if_dest_is_a_directory(self): src_file = self.make_path("xyzzy") dst_dir = self.make_path("tmp", "foo") src_contents = "contents of source file" self.create_file(src_file, contents=src_contents) self.create_dir(dst_dir) self.assertTrue(os.path.exists(src_file)) self.assertTrue(os.path.exists(dst_dir)) with self.assertRaises(OSError): shutil.copyfile(src_file, dst_dir) def test_moving_dir_into_dir(self): # regression test for #515 source_dir = tempfile.mkdtemp() target_dir = tempfile.mkdtemp() filename = "foo.pdf" with open(os.path.join(source_dir, filename), "wb") as fp: fp.write(b"stub") shutil.move(source_dir, target_dir) shutil.rmtree(target_dir) class RealCopyFileTest(FakeCopyFileTest): def use_real_fs(self): return True if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fake_filesystem_test.py0000644000175100001660000033226114764107375022521 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unittest for fake_filesystem module.""" import contextlib import errno import os import pathlib import shutil import stat import sys import tempfile import unittest from unittest.mock import patch from pyfakefs import fake_filesystem, fake_os, fake_open from pyfakefs.fake_filesystem import ( set_uid, set_gid, is_root, reset_ids, OSType, ) from pyfakefs.helpers import IS_WIN from pyfakefs.tests.test_utils import ( TestCase, RealFsTestCase, time_mock, skip_if_symlink_not_supported, ) class FakeDirectoryUnitTest(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator="/") self.os = fake_os.FakeOsModule(self.filesystem) self.time = time_mock(10, 1) self.time.start() self.fake_file = fake_filesystem.FakeFile( "foobar", contents="dummy_file", filesystem=self.filesystem ) self.fake_dir = fake_filesystem.FakeDirectory( "somedir", filesystem=self.filesystem ) def tearDown(self): self.time.stop() def test_new_file_and_directory(self): self.assertTrue(stat.S_IFREG & self.fake_file.st_mode) self.assertTrue(stat.S_IFDIR & self.fake_dir.st_mode) self.assertEqual({}, self.fake_dir.entries) def test_add_entry(self): self.fake_dir.add_entry(self.fake_file) self.assertEqual({"foobar": self.fake_file}, self.fake_dir.entries) def test_get_entry(self): self.fake_dir.add_entry(self.fake_file) self.assertEqual(self.fake_file, self.fake_dir.get_entry("foobar")) def test_path(self): root_dir = self.filesystem.root_dir_name self.filesystem.root.add_entry(self.fake_dir) self.fake_dir.add_entry(self.fake_file) self.assertEqual(f"{root_dir}somedir/foobar", self.fake_file.path) self.assertEqual(f"{root_dir}somedir", self.fake_dir.path) def test_path_with_drive(self): self.filesystem.is_windows_fs = True dir_path = "C:/foo/bar/baz" self.filesystem.create_dir(dir_path) dir_object = self.filesystem.get_object(dir_path) self.assertEqual(dir_path, dir_object.path) def test_path_after_chdir(self): root_dir = self.filesystem.root_dir_name dir_path = "/foo/bar/baz" self.filesystem.create_dir(dir_path) self.os.chdir(dir_path) dir_object = self.filesystem.get_object(dir_path) self.assertEqual(f"{root_dir}foo/bar/baz", dir_object.path) def test_path_after_chdir_with_drive(self): self.filesystem.is_windows_fs = True dir_path = "C:/foo/bar/baz" self.filesystem.create_dir(dir_path) self.os.chdir(dir_path) dir_object = self.filesystem.get_object(dir_path) self.assertEqual(dir_path, dir_object.path) def test_remove_entry(self): self.fake_dir.add_entry(self.fake_file) self.assertEqual(self.fake_file, self.fake_dir.get_entry("foobar")) self.fake_dir.remove_entry("foobar") with self.assertRaises(KeyError): self.fake_dir.get_entry("foobar") def test_should_throw_if_set_size_is_not_integer(self): with self.raises_os_error(errno.ENOSPC): self.fake_file.size = 0.1 def test_should_throw_if_set_size_is_negative(self): with self.raises_os_error(errno.ENOSPC): self.fake_file.size = -1 def test_produce_empty_file_if_set_size_is_zero(self): self.fake_file.size = 0 self.assertEqual("", self.fake_file.contents) def test_sets_content_empty_if_set_size_is_zero(self): self.fake_file.size = 0 self.assertEqual("", self.fake_file.contents) def test_truncate_file_if_size_is_smaller_than_current_size(self): self.fake_file.size = 6 self.assertEqual("dummy_", self.fake_file.contents) def test_leave_file_unchanged_if_size_is_equal_to_current_size(self): self.fake_file.size = 10 self.assertEqual("dummy_file", self.fake_file.contents) def test_set_contents_to_dir_raises(self): # Regression test for #276 self.filesystem.is_windows_fs = True with self.raises_os_error(errno.EISDIR): self.fake_dir.set_contents("a") self.filesystem.is_windows_fs = False with self.raises_os_error(errno.EISDIR): self.fake_dir.set_contents("a") def test_pads_with_nullbytes_if_size_is_greater_than_current_size(self): self.fake_file.size = 13 self.assertEqual("dummy_file\0\0\0", self.fake_file.contents) def test_set_m_time(self): self.assertEqual(10, self.fake_file.st_mtime) self.fake_file.st_mtime = 14 self.assertEqual(14, self.fake_file.st_mtime) self.fake_file.st_mtime = 131 self.assertEqual(131, self.fake_file.st_mtime) def test_file_inode(self): filesystem = fake_filesystem.FakeFilesystem(path_separator="/") fake_os_module = fake_os.FakeOsModule(filesystem) file_path = "some_file1" filesystem.create_file(file_path, contents="contents here1") self.assertLess(0, fake_os_module.stat(file_path)[stat.ST_INO]) file_obj = filesystem.get_object(file_path) file_obj.st_ino = 43 self.assertEqual(43, fake_os_module.stat(file_path)[stat.ST_INO]) def test_directory_inode(self): filesystem = fake_filesystem.FakeFilesystem(path_separator="/") fake_os_module = fake_os.FakeOsModule(filesystem) dirpath = "testdir" filesystem.create_dir(dirpath) self.assertLess(0, fake_os_module.stat(dirpath)[stat.ST_INO]) dir_obj = filesystem.get_object(dirpath) dir_obj.st_ino = 43 self.assertEqual(43, fake_os_module.stat(dirpath)[stat.ST_INO]) def test_directory_size(self): fs = fake_filesystem.FakeFilesystem(path_separator="/") foo_dir = fs.create_dir("/foo") fs.create_file("/foo/bar.txt", st_size=20) bar_dir = fs.create_dir("/foo/bar/") fs.create_file("/foo/bar/baz1.txt", st_size=30) fs.create_file("/foo/bar/baz2.txt", st_size=40) foo1_dir = fs.create_dir("/foo1") fs.create_file("/foo1/bar.txt", st_size=50) fs.create_file("/foo1/bar/baz/file", st_size=60) self.assertEqual(90, foo_dir.size) self.assertEqual(70, bar_dir.size) self.assertEqual(110, foo1_dir.size) self.assertEqual(200, fs.root_dir.size) with self.raises_os_error(errno.EISDIR): foo1_dir.size = 100 def test_ordered_dirs(self): filesystem = fake_filesystem.FakeFilesystem(path_separator="/") filesystem.create_dir("/foo") filesystem.create_file("/foo/2") filesystem.create_file("/foo/4") filesystem.create_file("/foo/1") filesystem.create_file("/foo/3") fake_dir = filesystem.get_object("/foo") self.assertEqual(["2", "4", "1", "3"], fake_dir.ordered_dirs) class SetLargeFileSizeTest(TestCase): def setUp(self): filesystem = fake_filesystem.FakeFilesystem() self.fake_file = fake_filesystem.FakeFile("foobar", filesystem=filesystem) def test_should_throw_if_size_is_not_integer(self): with self.raises_os_error(errno.ENOSPC): self.fake_file.set_large_file_size(0.1) def test_should_throw_if_size_is_negative(self): with self.raises_os_error(errno.ENOSPC): self.fake_file.set_large_file_size(-1) def test_sets_content_none_if_size_is_non_negative_integer(self): self.fake_file.set_large_file_size(1000000000) self.assertEqual(None, self.fake_file.contents) self.assertEqual(1000000000, self.fake_file.st_size) class NormalizePathTest(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator="/") self.root_name = self.filesystem.root_dir_name def test_empty_path_should_get_normalized_to_root_path(self): self.assertEqual(self.root_name, self.filesystem.absnormpath("")) def test_root_path_remains_unchanged(self): self.assertEqual(self.root_name, self.filesystem.absnormpath(self.root_name)) def test_relative_path_forced_to_cwd(self): path = "bar" self.filesystem.cwd = "/foo" self.assertEqual("/foo/bar", self.filesystem.absnormpath(path)) def test_cwd_from_pathlib_path(self): self.filesystem.cwd = pathlib.Path("/foo/bar") self.assertEqual("/foo/bar", self.filesystem.cwd) def test_absolute_path_remains_unchanged(self): path = "foo/bar" self.assertEqual(self.root_name + path, self.filesystem.absnormpath(path)) def test_dotted_path_is_normalized(self): path = "/foo/.." self.assertEqual( self.filesystem.root_dir_name, self.filesystem.absnormpath(path) ) path = "foo/../bar" self.assertEqual( f"{self.filesystem.root_dir_name}bar", self.filesystem.absnormpath(path), ) def test_dot_path_is_normalized(self): path = "." self.assertEqual(self.root_name, self.filesystem.absnormpath(path)) class GetPathComponentsTest(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator="/") self.root_name = "/" def test_root_path_should_return_empty_list(self): self.assertEqual([], self.filesystem._path_components(self.root_name)) def test_empty_path_should_return_empty_list(self): self.assertEqual([], self.filesystem._path_components("")) def test_relative_path_with_one_component_should_return_component(self): self.assertEqual(["foo"], self.filesystem._path_components("foo")) def test_absolute_path_with_one_component_should_return_component(self): self.assertEqual(["foo"], self.filesystem._path_components("/foo")) def test_two_level_relative_path_should_return_components(self): self.assertEqual(["foo", "bar"], self.filesystem._path_components("foo/bar")) def test_two_level_absolute_path_should_return_components(self): self.assertEqual(["foo", "bar"], self.filesystem._path_components("/foo/bar")) class FakeFilesystemUnitTest(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator="/") self.root_name = self.filesystem.root_dir_name self.fake_file = fake_filesystem.FakeFile("foobar", filesystem=self.filesystem) self.fake_child = fake_filesystem.FakeDirectory( "foobaz", filesystem=self.filesystem ) self.fake_grandchild = fake_filesystem.FakeDirectory( "quux", filesystem=self.filesystem ) def test_new_filesystem(self): self.assertEqual("/", self.filesystem.path_separator) self.assertTrue(stat.S_IFDIR & self.filesystem.root.st_mode) self.assertEqual({}, self.filesystem.root_dir.entries) def test_none_raises_type_error(self): with self.assertRaises(TypeError): self.filesystem.exists(None) def test_empty_string_does_not_exist(self): self.assertFalse(self.filesystem.exists("")) def test_exists_root(self): self.assertTrue(self.filesystem.exists(self.root_name)) def test_exists_unadded_file(self): self.assertFalse(self.filesystem.exists(self.fake_file.name)) def test_not_exists_subpath_named_like_file_contents(self): # Regression test for #219 file_path = "/foo/bar" self.filesystem.create_file(file_path, contents="baz") self.assertFalse(self.filesystem.exists(file_path + "/baz")) def test_get_root_object(self): self.assertEqual( self.filesystem.root_dir, self.filesystem.get_object(self.root_name), ) def test_add_object_to_root(self): self.filesystem.add_object(self.root_name, self.fake_file) self.assertEqual({"foobar": self.fake_file}, self.filesystem.root_dir.entries) def test_windows_root_dir_name(self): self.filesystem.is_windows_fs = True self.assertEqual("C:/", self.filesystem.root_dir_name) self.filesystem.cwd = "E:/foo" self.assertEqual("E:/", self.filesystem.root_dir_name) self.filesystem.cwd = "//foo/bar" self.assertEqual("//foo/bar/", self.filesystem.root_dir_name) def test_exists_added_file(self): self.filesystem.add_object(self.root_name, self.fake_file) self.assertTrue(self.filesystem.exists(self.fake_file.name)) def test_exists_relative_path_posix(self): self.filesystem.is_windows_fs = False self.filesystem.create_file("/a/b/file_one") self.filesystem.create_file("/a/c/file_two") self.assertTrue(self.filesystem.exists("a/b/../c/file_two")) self.assertTrue(self.filesystem.exists("/a/c/../b/file_one")) self.assertTrue(self.filesystem.exists("/a/c/../../a/b/file_one")) self.assertFalse(self.filesystem.exists("a/b/../z/d")) self.assertFalse(self.filesystem.exists("a/b/../z/../c/file_two")) self.filesystem.cwd = "/a/c" self.assertTrue(self.filesystem.exists("../b/file_one")) self.assertTrue(self.filesystem.exists("../../a/b/file_one")) self.assertTrue(self.filesystem.exists("../../a/b/../../a/c/file_two")) self.assertFalse(self.filesystem.exists("../z/file_one")) self.assertFalse(self.filesystem.exists("../z/../c/file_two")) def test_exists_relative_path_windows(self): self.filesystem.is_windows_fs = True self.filesystem.is_macos = False self.filesystem.create_file("/a/b/file_one") self.filesystem.create_file("/a/c/file_two") self.assertTrue(self.filesystem.exists("a/b/../c/file_two")) self.assertTrue(self.filesystem.exists("/a/c/../b/file_one")) self.assertTrue(self.filesystem.exists("/a/c/../../a/b/file_one")) self.assertFalse(self.filesystem.exists("a/b/../z/d")) self.assertTrue(self.filesystem.exists("a/b/../z/../c/file_two")) self.filesystem.cwd = "C:/a/c" self.assertTrue(self.filesystem.exists("../b/file_one")) self.assertTrue(self.filesystem.exists("../../a/b/file_one")) self.assertTrue(self.filesystem.exists("../../a/b/../../a/c/file_two")) self.assertFalse(self.filesystem.exists("../z/file_one")) self.assertTrue(self.filesystem.exists("../z/../c/file_two")) def test_get_object_from_root(self): self.filesystem.add_object(self.root_name, self.fake_file) self.assertEqual(self.fake_file, self.filesystem.get_object("foobar")) def test_get_nonexistent_object_from_root_error(self): self.filesystem.add_object(self.root_name, self.fake_file) self.assertEqual(self.fake_file, self.filesystem.get_object("foobar")) with self.raises_os_error(errno.ENOENT): self.filesystem.get_object("some_bogus_filename") def test_remove_object_from_root(self): self.filesystem.add_object(self.root_name, self.fake_file) self.filesystem.remove_object(self.fake_file.name) with self.raises_os_error(errno.ENOENT): self.filesystem.get_object(self.fake_file.name) def test_remove_nonexisten_object_from_root_error(self): with self.raises_os_error(errno.ENOENT): self.filesystem.remove_object("some_bogus_filename") def test_exists_removed_file(self): self.filesystem.add_object(self.root_name, self.fake_file) self.filesystem.remove_object(self.fake_file.name) self.assertFalse(self.filesystem.exists(self.fake_file.name)) def test_add_object_to_child(self): self.filesystem.add_object(self.root_name, self.fake_child) self.filesystem.add_object(self.fake_child.name, self.fake_file) self.assertEqual( {self.fake_file.name: self.fake_file}, self.filesystem.root_dir.get_entry(self.fake_child.name).entries, ) def test_add_object_to_regular_file_error_posix(self): self.filesystem.is_windows_fs = False self.filesystem.add_object(self.filesystem.root_dir_name, self.fake_file) with self.raises_os_error(errno.ENOTDIR): self.filesystem.add_object(self.fake_file.name, self.fake_file) def test_add_object_to_regular_file_error_windows(self): self.filesystem.is_windows_fs = True self.filesystem.add_object(self.root_name, self.fake_file) with self.raises_os_error(errno.ENOENT): self.filesystem.add_object(self.fake_file.name, self.fake_file) def test_exists_file_added_to_child(self): self.filesystem.add_object(self.root_name, self.fake_child) self.filesystem.add_object(self.fake_child.name, self.fake_file) path = self.filesystem.joinpaths(self.fake_child.name, self.fake_file.name) self.assertTrue(self.filesystem.exists(path)) def test_get_object_from_child(self): self.filesystem.add_object(self.root_name, self.fake_child) self.filesystem.add_object(self.fake_child.name, self.fake_file) self.assertEqual( self.fake_file, self.filesystem.get_object( self.filesystem.joinpaths(self.fake_child.name, self.fake_file.name) ), ) def test_get_nonexistent_object_from_child_error(self): self.filesystem.add_object(self.root_name, self.fake_child) self.filesystem.add_object(self.fake_child.name, self.fake_file) with self.raises_os_error(errno.ENOENT): self.filesystem.get_object( self.filesystem.joinpaths(self.fake_child.name, "some_bogus_filename") ) def test_remove_object_from_child(self): self.filesystem.add_object(self.root_name, self.fake_child) self.filesystem.add_object(self.fake_child.name, self.fake_file) target_path = self.filesystem.joinpaths( self.fake_child.name, self.fake_file.name ) self.filesystem.remove_object(target_path) with self.raises_os_error(errno.ENOENT): self.filesystem.get_object(target_path) def test_remove_object_from_child_error(self): self.filesystem.add_object(self.root_name, self.fake_child) with self.raises_os_error(errno.ENOENT): self.filesystem.remove_object( self.filesystem.joinpaths(self.fake_child.name, "some_bogus_filename") ) def test_remove_object_from_non_directory_error(self): self.filesystem.add_object(self.root_name, self.fake_file) with self.raises_os_error(errno.ENOTDIR): self.filesystem.remove_object( self.filesystem.joinpaths( "%s" % self.fake_file.name, "file_does_not_matter_since_parent_not_a_directory", ) ) def test_exists_file_removed_from_child(self): self.filesystem.add_object(self.root_name, self.fake_child) self.filesystem.add_object(self.fake_child.name, self.fake_file) path = self.filesystem.joinpaths(self.fake_child.name, self.fake_file.name) self.filesystem.remove_object(path) self.assertFalse(self.filesystem.exists(path)) def test_operate_on_grandchild_directory(self): self.filesystem.add_object(self.root_name, self.fake_child) self.filesystem.add_object(self.fake_child.name, self.fake_grandchild) grandchild_directory = self.filesystem.joinpaths( self.fake_child.name, self.fake_grandchild.name ) grandchild_file = self.filesystem.joinpaths( grandchild_directory, self.fake_file.name ) with self.assertRaises(OSError): self.filesystem.get_object(grandchild_file) self.filesystem.add_object(grandchild_directory, self.fake_file) self.assertEqual(self.fake_file, self.filesystem.get_object(grandchild_file)) self.assertTrue(self.filesystem.exists(grandchild_file)) self.filesystem.remove_object(grandchild_file) with self.assertRaises(OSError): self.filesystem.get_object(grandchild_file) self.assertFalse(self.filesystem.exists(grandchild_file)) def test_create_directory_in_root_directory(self): path = "foo" self.filesystem.create_dir(path) new_dir = self.filesystem.get_object(path) self.assertEqual(os.path.basename(path), new_dir.name) self.assertTrue(stat.S_IFDIR & new_dir.st_mode) def test_create_directory_in_root_directory_already_exists_error(self): path = "foo" self.filesystem.create_dir(path) with self.raises_os_error(errno.EEXIST): self.filesystem.create_dir(path) def test_create_directory(self): path = "foo/bar/baz" self.filesystem.create_dir(path) new_dir = self.filesystem.get_object(path) self.assertEqual(os.path.basename(path), new_dir.name) self.assertTrue(stat.S_IFDIR & new_dir.st_mode) # Create second directory to make sure first is OK. path = "%s/quux" % path self.filesystem.create_dir(path) new_dir = self.filesystem.get_object(path) self.assertEqual(os.path.basename(path), new_dir.name) self.assertTrue(stat.S_IFDIR & new_dir.st_mode) def test_create_dir_umask(self): old_umask = self.filesystem.umask self.filesystem.umask = 0o22 path = "foo/bar/baz" self.filesystem.create_dir(path, perm_bits=0o777) new_dir = self.filesystem.get_object(path) self.assertEqual(stat.S_IFDIR | 0o755, new_dir.st_mode) path = "foo/bar/boo" self.filesystem.create_dir(path, perm_bits=0o777, apply_umask=False) new_dir = self.filesystem.get_object(path) self.assertEqual(stat.S_IFDIR | 0o777, new_dir.st_mode) self.filesystem.umask = old_umask def test_create_directory_already_exists_error(self): path = "foo/bar/baz" self.filesystem.create_dir(path) with self.raises_os_error(errno.EEXIST): self.filesystem.create_dir(path) def test_create_file_in_read_only_directory_raises_in_posix(self): self.filesystem.is_windows_fs = False dir_path = "/foo/bar" self.filesystem.create_dir(dir_path, perm_bits=0o555) file_path = dir_path + "/baz" if not is_root(): with self.raises_os_error(errno.EACCES): self.filesystem.create_file(file_path) else: self.filesystem.create_file(file_path) self.assertTrue(self.filesystem.exists(file_path)) def test_create_file_in_read_only_directory_possible_in_windows(self): self.filesystem.is_windows_fs = True dir_path = "C:/foo/bar" self.filesystem.create_dir(dir_path, perm_bits=0o555) file_path = dir_path + "/baz" self.filesystem.create_file(file_path) self.assertTrue(self.filesystem.exists(file_path)) def test_create_file_in_current_directory(self): path = "foo" contents = "dummy data" self.filesystem.create_file(path, contents=contents) self.assertTrue(self.filesystem.exists(path)) self.assertFalse(self.filesystem.exists(os.path.dirname(path))) path = "./%s" % path self.assertTrue(self.filesystem.exists(os.path.dirname(path))) def test_create_file_in_root_directory(self): path = "/foo" contents = "dummy data" self.filesystem.create_file(path, contents=contents) new_file = self.filesystem.get_object(path) self.assertTrue(self.filesystem.exists(path)) self.assertTrue(self.filesystem.exists(os.path.dirname(path))) self.assertEqual(os.path.basename(path), new_file.name) self.assertTrue(stat.S_IFREG & new_file.st_mode) self.assertEqual(contents, new_file.contents) def test_create_file_with_size_but_no_content_creates_large_file(self): path = "large_foo_bar" self.filesystem.create_file(path, st_size=100000000) new_file = self.filesystem.get_object(path) self.assertEqual(None, new_file.contents) self.assertEqual(100000000, new_file.st_size) def test_create_file_in_root_directory_already_exists_error(self): path = "foo" self.filesystem.create_file(path) with self.raises_os_error(errno.EEXIST): self.filesystem.create_file(path) def test_create_file(self): path = "foo/bar/baz" retval = self.filesystem.create_file(path, contents="dummy_data") self.assertTrue(self.filesystem.exists(path)) self.assertTrue(self.filesystem.exists(os.path.dirname(path))) new_file = self.filesystem.get_object(path) self.assertEqual(os.path.basename(path), new_file.name) if IS_WIN: fake_id = 0 if is_root() else 1 self.assertEqual(fake_id, new_file.st_uid) self.assertEqual(fake_id, new_file.st_gid) else: self.assertEqual(os.getuid(), new_file.st_uid) self.assertEqual(os.getgid(), new_file.st_gid) self.assertEqual(new_file, retval) def test_create_file_with_changed_ids(self): path = "foo/bar/baz" set_uid(42) set_gid(2) self.filesystem.create_file(path) self.assertTrue(self.filesystem.exists(path)) new_file = self.filesystem.get_object(path) self.assertEqual(42, new_file.st_uid) self.assertEqual(2, new_file.st_gid) reset_ids() def test_empty_file_created_for_none_contents(self): fake_open = fake_filesystem.FakeFileOpen(self.filesystem) path = "foo/bar/baz" self.filesystem.create_file(path, contents=None) with fake_open(path, encoding="utf8") as f: self.assertEqual("", f.read()) def test_create_file_with_incorrect_mode_type(self): with self.assertRaises(TypeError): self.filesystem.create_file("foo", "bar") def test_create_file_already_exists_error(self): path = "foo/bar/baz" self.filesystem.create_file(path, contents="dummy_data") with self.raises_os_error(errno.EEXIST): self.filesystem.create_file(path) def test_create_link(self): path = "foo/bar/baz" target_path = "foo/bar/quux" new_file = self.filesystem.create_symlink(path, "quux") # Neither the path nor the final target exists before we actually # write to one of them, even though the link appears in the file # system. self.assertFalse(self.filesystem.exists(path)) self.assertFalse(self.filesystem.exists(target_path)) self.assertTrue(stat.S_IFLNK & new_file.st_mode) # but once we write the linked to file, they both will exist. self.filesystem.create_file(target_path) self.assertTrue(self.filesystem.exists(path)) self.assertTrue(self.filesystem.exists(target_path)) def test_resolve_object(self): target_path = "dir/target" target_contents = "0123456789ABCDEF" link_name = "x" self.filesystem.create_dir("dir") self.filesystem.create_file("dir/target", contents=target_contents) self.filesystem.create_symlink(link_name, target_path) obj = self.filesystem.resolve(link_name) self.assertEqual("target", obj.name) self.assertEqual(target_contents, obj.contents) def check_lresolve_object(self): target_path = "dir/target" target_contents = "0123456789ABCDEF" link_name = "x" self.filesystem.create_dir("dir") self.filesystem.create_file("dir/target", contents=target_contents) self.filesystem.create_symlink(link_name, target_path) obj = self.filesystem.lresolve(link_name) self.assertEqual(link_name, obj.name) self.assertEqual(target_path, obj.contents) def test_lresolve_object_windows(self): self.filesystem.is_windows_fs = True self.check_lresolve_object() def test_lresolve_object_posix(self): self.filesystem.is_windows_fs = False self.check_lresolve_object() def check_directory_access_on_file(self, error_subtype): self.filesystem.create_file("not_a_dir") with self.raises_os_error(error_subtype): self.filesystem.resolve("not_a_dir/foo") with self.raises_os_error(error_subtype): self.filesystem.lresolve("not_a_dir/foo/bar") def test_directory_access_on_file_windows(self): self.filesystem.is_windows_fs = True self.check_directory_access_on_file(errno.ENOENT) def test_directory_access_on_file_posix(self): self.filesystem.is_windows_fs = False self.check_directory_access_on_file(errno.ENOTDIR) def test_pickle_fs(self): """Regression test for #445""" import pickle self.filesystem.open_files = [] p = pickle.dumps(self.filesystem) fs = pickle.loads(p) self.assertEqual(str(fs.root), str(self.filesystem.root)) self.assertEqual(fs.mount_points, self.filesystem.mount_points) class CaseInsensitiveFakeFilesystemTest(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator="/") self.filesystem.is_case_sensitive = False self.os = fake_os.FakeOsModule(self.filesystem) self.path = self.os.path def test_get_object(self): self.filesystem.create_dir("/foo/bar") self.filesystem.create_file("/foo/bar/baz") self.assertTrue(self.filesystem.get_object("/Foo/Bar/Baz")) def test_remove_object(self): self.filesystem.create_dir("/foo/bar") self.filesystem.create_file("/foo/bar/baz") self.filesystem.remove_object("/Foo/Bar/Baz") self.assertFalse(self.filesystem.exists("/foo/bar/baz")) def test_exists(self): self.filesystem.create_dir("/Foo/Bar") self.assertTrue(self.filesystem.exists("/Foo/Bar")) self.assertTrue(self.filesystem.exists("/foo/bar")) self.filesystem.create_file("/foo/Bar/baz") self.assertTrue(self.filesystem.exists("/Foo/bar/BAZ")) self.assertTrue(self.filesystem.exists("/foo/bar/baz")) def test_create_directory_with_different_case_root(self): self.filesystem.create_dir("/Foo/Bar") self.filesystem.create_dir("/foo/bar/baz") dir1 = self.filesystem.get_object("/Foo/Bar") dir2 = self.filesystem.get_object("/foo/bar") self.assertEqual(dir1, dir2) def test_create_file_with_different_case_dir(self): self.filesystem.create_dir("/Foo/Bar") self.filesystem.create_file("/foo/bar/baz") dir1 = self.filesystem.get_object("/Foo/Bar") dir2 = self.filesystem.get_object("/foo/bar") self.assertEqual(dir1, dir2) def test_resolve_path(self): self.filesystem.create_dir("/foo/baz") self.filesystem.create_symlink("/Foo/Bar", "./baz/bip") self.assertEqual( f"{self.filesystem.root_dir_name}foo/baz/bip", self.filesystem.resolve_path("/foo/bar"), ) def test_isdir_isfile(self): self.filesystem.create_file("foo/bar") self.assertTrue(self.path.isdir("Foo")) self.assertFalse(self.path.isfile("Foo")) self.assertTrue(self.path.isfile("Foo/Bar")) self.assertFalse(self.path.isdir("Foo/Bar")) def test_getsize(self): file_path = "foo/bar/baz" self.filesystem.create_file(file_path, contents="1234567") self.assertEqual(7, self.path.getsize("FOO/BAR/BAZ")) def test_getsize_with_looping_symlink(self): self.filesystem.is_windows_fs = False dir_path = "/foo/bar" self.filesystem.create_dir(dir_path) link_path = dir_path + "/link" link_target = link_path + "/link" self.os.symlink(link_target, link_path) with self.raises_os_error(errno.ELOOP): self.os.path.getsize(link_path) def test_get_mtime(self): test_file = self.filesystem.create_file("foo/bar1.txt") test_file.st_mtime = 24 self.assertEqual(24, self.path.getmtime("Foo/Bar1.TXT")) def test_get_object_with_file_size(self): self.filesystem.create_file("/Foo/Bar", st_size=10) self.assertTrue(self.filesystem.get_object("/foo/bar")) class CaseSensitiveFakeFilesystemTest(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator="/") self.filesystem.is_case_sensitive = True self.os = fake_os.FakeOsModule(self.filesystem) self.path = self.os.path def test_get_object(self): self.filesystem.create_dir("/foo/bar") self.filesystem.create_file("/foo/bar/baz") with self.assertRaises(OSError): self.filesystem.get_object("/Foo/Bar/Baz") def test_remove_object(self): self.filesystem.create_dir("/foo/bar") self.filesystem.create_file("/foo/bar/baz") with self.assertRaises(OSError): self.filesystem.remove_object("/Foo/Bar/Baz") self.assertTrue(self.filesystem.exists("/foo/bar/baz")) def test_exists(self): self.filesystem.create_dir("/Foo/Bar") self.assertTrue(self.filesystem.exists("/Foo/Bar")) self.assertFalse(self.filesystem.exists("/foo/bar")) self.filesystem.create_file("/foo/Bar/baz") self.assertFalse(self.filesystem.exists("/Foo/bar/BAZ")) self.assertFalse(self.filesystem.exists("/foo/bar/baz")) def test_create_directory_with_different_case_root(self): self.filesystem.create_dir("/Foo/Bar") self.filesystem.create_dir("/foo/bar/baz") dir1 = self.filesystem.get_object("/Foo/Bar") dir2 = self.filesystem.get_object("/foo/bar") self.assertNotEqual(dir1, dir2) def test_create_file_with_different_case_dir(self): self.filesystem.create_dir("/Foo/Bar") self.filesystem.create_file("/foo/bar/baz") dir1 = self.filesystem.get_object("/Foo/Bar") dir2 = self.filesystem.get_object("/foo/bar") self.assertNotEqual(dir1, dir2) def test_isdir_isfile(self): self.filesystem.create_file("foo/bar") self.assertFalse(self.path.isdir("Foo")) self.assertFalse(self.path.isfile("Foo")) self.assertFalse(self.path.isfile("Foo/Bar")) self.assertFalse(self.path.isdir("Foo/Bar")) def test_getsize(self): file_path = "foo/bar/baz" self.filesystem.create_file(file_path, contents="1234567") with self.assertRaises(os.error): self.path.getsize("FOO/BAR/BAZ") def test_get_mtime(self): test_file = self.filesystem.create_file("foo/bar1.txt") test_file.st_mtime = 24 with self.raises_os_error(errno.ENOENT): self.path.getmtime("Foo/Bar1.TXT") class OsPathInjectionRegressionTest(TestCase): """Test faking os.path before calling os.walk. Found when investigating a problem with gws/tools/labrat/rat_utils_unittest, which was faking out os.path before calling os.walk. """ def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator="/") self.os_path = os.path # The bug was that when os.path gets faked, the FakePathModule doesn't # get called in self.os.walk(). FakePathModule now insists that it is # created as part of FakeOsModule. self.os = fake_os.FakeOsModule(self.filesystem) def tearDown(self): os.path = self.os_path def test_create_top_level_directory(self): top_level_dir = "/x" self.assertFalse(self.filesystem.exists(top_level_dir)) self.filesystem.create_dir(top_level_dir) self.assertTrue(self.filesystem.exists("/")) self.assertTrue(self.filesystem.exists(top_level_dir)) self.filesystem.create_dir("%s/po" % top_level_dir) self.filesystem.create_file("%s/po/control" % top_level_dir) self.filesystem.create_file("%s/po/experiment" % top_level_dir) self.filesystem.create_dir("%s/gv" % top_level_dir) self.filesystem.create_file("%s/gv/control" % top_level_dir) expected = [ ("/", ["x"], []), ("/x", ["gv", "po"], []), ("/x/gv", [], ["control"]), ("/x/po", [], ["control", "experiment"]), ] # as the result is unsorted, we have to check against sorted results result = sorted([step for step in self.os.walk("/")], key=lambda v: v[0]) self.assertEqual(len(expected), len(result)) for entry, expected_entry in zip(result, expected): self.assertEqual(expected_entry[0], entry[0]) self.assertEqual(expected_entry[1], sorted(entry[1])) self.assertEqual(expected_entry[2], sorted(entry[2])) class FakePathModuleTest(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator="!") self.os = fake_os.FakeOsModule(self.filesystem) self.path = self.os.path def check_abspath(self, is_windows): # the implementation differs in Windows and Posix, so test both self.filesystem.is_windows_fs = is_windows filename = "foo" abspath = self.filesystem.root_dir_name + filename self.filesystem.create_file(abspath) self.assertEqual(abspath, self.path.abspath(abspath)) self.assertEqual(abspath, self.path.abspath(filename)) self.assertEqual(abspath, self.path.abspath("..!%s" % filename)) def test_abspath_windows(self): self.check_abspath(is_windows=True) def test_abspath_posix(self): """abspath should return a consistent representation of a file.""" self.check_abspath(is_windows=False) def check_abspath_bytes(self, is_windows): """abspath should return a consistent representation of a file.""" self.filesystem.is_windows_fs = is_windows filename = b"foo" abspath = self.filesystem.root_dir_name.encode() + filename self.filesystem.create_file(abspath) self.assertEqual(abspath, self.path.abspath(abspath)) self.assertEqual(abspath, self.path.abspath(filename)) self.assertEqual(abspath, self.path.abspath(b"..!" + filename)) def test_abspath_bytes_windows(self): self.check_abspath_bytes(is_windows=True) def test_abspath_bytes_posix(self): self.check_abspath_bytes(is_windows=False) def test_abspath_deals_with_relative_non_root_path(self): """abspath should correctly handle relative paths from a non-! directory. This test is distinct from the basic functionality test because fake_filesystem has historically been based in !. """ filename = "!foo!bar!baz" file_components = filename.split(self.path.sep) root_name = self.filesystem.root_dir_name basedir = f"{root_name}{file_components[0]}" self.filesystem.create_file(filename) self.os.chdir(basedir) self.assertEqual(basedir, self.path.abspath(self.path.curdir)) self.assertEqual(root_name, self.path.abspath("..")) self.assertEqual( self.path.join(basedir, file_components[1]), self.path.abspath(file_components[1]), ) def test_abs_path_with_drive_component(self): self.filesystem.is_windows_fs = True self.filesystem.cwd = "C:!foo" self.assertEqual("C:!foo!bar", self.path.abspath("bar")) self.assertEqual("C:!foo!bar", self.path.abspath("C:bar")) self.assertEqual("C:!foo!bar", self.path.abspath("!foo!bar")) def test_isabs_with_drive_component(self): self.filesystem.is_windows_fs = False self.assertFalse(self.path.isabs("C:!foo")) self.assertFalse(self.path.isabs(b"C:!foo")) self.assertTrue(self.path.isabs("!")) self.assertTrue(self.path.isabs(b"!")) self.filesystem.is_windows_fs = True self.assertTrue(self.path.isabs("C:!foo")) self.assertTrue(self.path.isabs(b"C:!foo")) if sys.version_info < (3, 13): self.assertTrue(self.path.isabs("!")) self.assertTrue(self.path.isabs(b"!")) else: self.assertFalse(self.path.isabs("!")) self.assertFalse(self.path.isabs(b"!")) def test_relpath(self): path_foo = "!path!to!foo" path_bar = "!path!to!bar" path_other = "!some!where!else" with self.assertRaises(ValueError): self.path.relpath(None) with self.assertRaises(ValueError): self.path.relpath("") self.assertEqual("path!to!foo", self.path.relpath(path_foo)) self.assertEqual("..!foo", self.path.relpath(path_foo, path_bar)) self.assertEqual( "..!..!..%s" % path_other, self.path.relpath(path_other, path_bar) ) self.assertEqual(".", self.path.relpath(path_bar, path_bar)) def test_realpath_vs_abspath(self): self.filesystem.is_windows_fs = False self.filesystem.create_file("!george!washington!bridge") self.filesystem.create_symlink("!first!president", "!george!washington") self.assertEqual( "!first!president!bridge", self.os.path.abspath("!first!president!bridge"), ) self.assertEqual( "!george!washington!bridge", self.os.path.realpath("!first!president!bridge"), ) self.os.chdir("!first!president") self.assertEqual("!george!washington!bridge", self.os.path.realpath("bridge")) @unittest.skipIf(sys.version_info < (3, 10), "'strict' new in Python 3.10") def test_realpath_strict(self): self.filesystem.create_file("!foo!bar") root_dir = self.filesystem.root_dir_name self.filesystem.cwd = f"{root_dir}foo" self.assertEqual( f"{root_dir}foo!baz", self.os.path.realpath("baz", strict=False) ) with self.raises_os_error(errno.ENOENT): self.os.path.realpath("baz", strict=True) self.assertEqual( f"{root_dir}foo!bar", self.os.path.realpath("bar", strict=True) ) def test_samefile(self): file_path1 = "!foo!bar!baz" file_path2 = "!foo!bar!boo" self.filesystem.create_file(file_path1) self.filesystem.create_file(file_path2) self.assertTrue(self.path.samefile(file_path1, file_path1)) self.assertFalse(self.path.samefile(file_path1, file_path2)) self.assertTrue(self.path.samefile(file_path1, "!foo!..!foo!bar!..!bar!baz")) self.assertTrue(self.path.samefile(file_path1, b"!foo!..!foo!bar!..!bar!baz")) def test_exists(self): file_path = "foo!bar!baz" file_path_bytes = b"foo!bar!baz" self.filesystem.create_file(file_path) self.assertTrue(self.path.exists(file_path)) self.assertTrue(self.path.exists(file_path_bytes)) self.assertFalse(self.path.exists("!some!other!bogus!path")) def test_exists_with_drive(self): self.filesystem.os = OSType.WINDOWS self.filesystem.add_mount_point("F:") self.assertTrue(self.path.exists("C:")) self.assertTrue(self.path.exists("c:\\")) self.assertTrue(self.path.exists("f:")) self.assertTrue(self.path.exists("F:\\")) self.assertFalse(self.path.exists("Z:")) self.assertFalse(self.path.exists("z:\\")) def test_lexists(self): file_path = "foo!bar!baz" file_path_bytes = b"foo!bar!baz" self.filesystem.create_dir("foo!bar") self.filesystem.create_symlink(file_path, "bogus") self.assertTrue(self.path.lexists(file_path)) self.assertTrue(self.path.lexists(file_path_bytes)) self.assertFalse(self.path.exists(file_path)) self.assertFalse(self.path.exists(file_path_bytes)) self.filesystem.create_file("foo!bar!bogus") self.assertTrue(self.path.exists(file_path)) def test_dirname_with_drive(self): self.filesystem.is_windows_fs = True self.assertEqual("c:!foo", self.path.dirname("c:!foo!bar")) self.assertEqual(b"c:!", self.path.dirname(b"c:!foo")) self.assertEqual("!foo", self.path.dirname("!foo!bar")) self.assertEqual(b"!", self.path.dirname(b"!foo")) self.assertEqual("c:foo", self.path.dirname("c:foo!bar")) self.assertEqual(b"c:", self.path.dirname(b"c:foo")) self.assertEqual("foo", self.path.dirname("foo!bar")) def test_dirname(self): dirname = "foo!bar" self.assertEqual(dirname, self.path.dirname("%s!baz" % dirname)) def test_join_strings(self): components = ["foo", "bar", "baz"] self.assertEqual("foo!bar!baz", self.path.join(*components)) def test_join_bytes(self): components = [b"foo", b"bar", b"baz"] self.assertEqual(b"foo!bar!baz", self.path.join(*components)) @unittest.skipIf( sys.platform != "win32" or sys.version_info < (3, 8), "Windows specific test" ) @patch.dict(os.environ, {"USERPROFILE": r"C:\Users\John"}) def test_expand_user_windows(self): self.assertEqual(self.path.expanduser("~"), "C:!Users!John") @unittest.skipIf(sys.platform == "win32", "Posix specific test") @patch.dict(os.environ, {"HOME": "/home/john"}) def test_expand_user(self): self.assertEqual(self.path.expanduser("~"), "!home!john") @patch.dict(os.environ, {}, clear=True) def test_expand_user_no_home_environment(self): """Make sure this also works without HOME / USERPROFILE set""" # we just check that it does not crash and has some result, # as the result is system-dependent self.assertTrue(self.path.expanduser("~")) @unittest.skipIf( TestCase.is_windows or TestCase.is_cygwin, "only tested on unix systems", ) def test_expand_root(self): if sys.platform == "darwin": roothome = "!var!root" else: roothome = "!root" self.assertEqual(self.path.expanduser("~root"), roothome) def test_getsize_path_nonexistent(self): file_path = "foo!bar!baz" with self.assertRaises(os.error): self.path.getsize(file_path) def test_getsize_file_empty(self): file_path = "foo!bar!baz" self.filesystem.create_file(file_path) self.assertEqual(0, self.path.getsize(file_path)) def test_getsize_file_non_zero_size(self): file_path = "foo!bar!baz" file_path_bytes = b"foo!bar!baz" self.filesystem.create_file(file_path, contents="1234567") self.assertEqual(7, self.path.getsize(file_path)) self.assertEqual(7, self.path.getsize(file_path_bytes)) def test_getsize_dir_empty(self): # For directories, only require that the size is non-negative. dir_path = "foo!bar" self.filesystem.create_dir(dir_path) size = self.path.getsize(dir_path) self.assertFalse(int(size) < 0, "expected non-negative size; actual: %s" % size) def test_getsize_dir_non_zero_size(self): # For directories, only require that the size is non-negative. dir_path = "foo!bar" self.filesystem.create_file(self.filesystem.joinpaths(dir_path, "baz")) size = self.path.getsize(dir_path) self.assertFalse(int(size) < 0, "expected non-negative size; actual: %s" % size) def test_isdir(self): self.filesystem.create_file("foo!bar") self.assertTrue(self.path.isdir("foo")) self.assertTrue(self.path.isdir(b"foo")) self.assertFalse(self.path.isdir("foo!bar")) self.assertFalse(self.path.isdir("it_dont_exist")) def test_isdir_with_cwd_change(self): self.filesystem.create_file("!foo!bar!baz") self.assertTrue(self.path.isdir("!foo")) self.assertTrue(self.path.isdir("!foo!bar")) self.assertTrue(self.path.isdir("foo")) self.assertTrue(self.path.isdir("foo!bar")) self.filesystem.cwd = f"{self.filesystem.root_dir_name}foo" self.assertTrue(self.path.isdir("!foo")) self.assertTrue(self.path.isdir("!foo!bar")) self.assertTrue(self.path.isdir("bar")) def test_isfile(self): self.filesystem.create_file("foo!bar") self.assertFalse(self.path.isfile("foo")) self.assertTrue(self.path.isfile("foo!bar")) self.assertTrue(self.path.isfile(b"foo!bar")) self.assertFalse(self.path.isfile("it_dont_exist")) def test_get_mtime(self): test_file = self.filesystem.create_file("foo!bar1.txt") self.assertNotEqual(24, self.path.getmtime("foo!bar1.txt")) test_file.st_mtime = 24 self.assertEqual(24, self.path.getmtime("foo!bar1.txt")) self.assertEqual(24, self.path.getmtime(b"foo!bar1.txt")) def test_get_mtime_raises_os_error(self): self.assertFalse(self.path.exists("does_not_exist")) with self.raises_os_error(errno.ENOENT): self.path.getmtime("does_not_exist") def test_islink(self): self.filesystem.create_dir("foo") self.filesystem.create_file("foo!regular_file") self.filesystem.create_symlink("foo!link_to_file", "regular_file") self.assertFalse(self.path.islink("foo")) # An object can be both a link and a file or file, according to the # comments in Python/Lib/posixpath.py. self.assertTrue(self.path.islink("foo!link_to_file")) self.assertTrue(self.path.isfile("foo!link_to_file")) self.assertTrue(self.path.islink(b"foo!link_to_file")) self.assertTrue(self.path.isfile(b"foo!link_to_file")) self.assertTrue(self.path.isfile("foo!regular_file")) self.assertFalse(self.path.islink("foo!regular_file")) self.assertFalse(self.path.islink("it_dont_exist")) def test_is_link_case_sensitive(self): # Regression test for #306 self.filesystem.is_case_sensitive = False self.filesystem.create_dir("foo") self.filesystem.create_symlink("foo!bar", "foo") self.assertTrue(self.path.islink("foo!Bar")) def test_ismount(self): self.assertFalse(self.path.ismount("")) self.assertTrue(self.path.ismount("!")) self.assertTrue(self.path.ismount(b"!")) self.assertFalse(self.path.ismount("!mount!")) self.filesystem.add_mount_point("!mount") self.assertTrue(self.path.ismount("!mount")) self.assertTrue(self.path.ismount(b"!mount")) self.assertTrue(self.path.ismount("!mount!")) def test_ismount_with_drive_letters(self): self.filesystem.is_windows_fs = True self.assertTrue(self.path.ismount("!")) self.assertTrue(self.path.ismount("c:!")) self.assertFalse(self.path.ismount("c:")) self.assertTrue(self.path.ismount("z:!")) self.filesystem.add_mount_point("!mount") self.assertTrue(self.path.ismount("!mount")) self.assertTrue(self.path.ismount("!mount!")) def test_ismount_with_unc_paths(self): self.filesystem.is_windows_fs = True self.assertTrue(self.path.ismount("!!a!")) self.assertTrue(self.path.ismount("!!a!b")) self.assertTrue(self.path.ismount("!!a!b!")) self.assertFalse(self.path.ismount("!a!b!")) self.assertFalse(self.path.ismount("!!a!b!c")) def test_ismount_with_alternate_path_separator(self): self.filesystem.alternative_path_separator = "!" self.filesystem.add_mount_point("!mount") self.assertTrue(self.path.ismount("!mount")) self.assertTrue(self.path.ismount("!mount!")) self.assertTrue(self.path.ismount("!mount!!")) self.filesystem.is_windows_fs = True self.assertTrue(self.path.ismount("Z:!")) def test_getattr_forward_to_real_os_path(self): """Forwards any non-faked calls to os.path.""" self.assertTrue(hasattr(self.path, "sep"), "Get a faked os.path function") private_path_function = None if sys.version_info < (3, 6): if self.is_windows: private_path_function = "_get_bothseps" else: private_path_function = "_join_real_path" if private_path_function: self.assertTrue( hasattr(self.path, private_path_function), "Get a real os.path function not implemented in fake os.path", ) self.assertFalse(hasattr(self.path, "nonexistent")) def test_splitroot_posix(self): self.filesystem.is_windows_fs = False self.assertEqual(("", "", "foo!bar"), self.filesystem.splitroot("foo!bar")) self.assertEqual(("", "!", "foo!bar"), self.filesystem.splitroot("!foo!bar")) self.assertEqual( ("", "!!", "foo!!bar"), self.filesystem.splitroot("!!foo!!bar") ) @unittest.skipIf(sys.version_info < (3, 13), "Introduced in Python 3.13") @unittest.skipIf(TestCase.is_windows, "Posix specific behavior") def test_is_reserved_posix(self): self.assertFalse(self.filesystem.isreserved("!dev")) self.assertFalse(self.filesystem.isreserved("!")) self.assertFalse(self.filesystem.isreserved("COM1")) self.assertFalse(self.filesystem.isreserved("nul.txt")) @unittest.skipIf(sys.version_info < (3, 13), "Introduced in Python 3.13") @unittest.skipIf(not TestCase.is_windows, "Windows specific behavior") def test_is_reserved_windows(self): self.assertFalse(self.filesystem.isreserved("!dev")) self.assertFalse(self.filesystem.isreserved("!")) self.assertTrue(self.filesystem.isreserved("COM1")) self.assertTrue(self.filesystem.isreserved("nul.txt")) class PathManipulationTestBase(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator="|") class CollapsePathPipeSeparatorTest(PathManipulationTestBase): """Tests CollapsePath (mimics os.path.normpath) using | as path separator.""" def test_empty_path_becomes_dot_path(self): self.assertEqual(".", self.filesystem.normpath("")) def test_dot_path_unchanged(self): self.assertEqual(".", self.filesystem.normpath(".")) def test_slashes_are_not_collapsed(self): """Tests that '/' is not treated specially if the path separator is '|'. In particular, multiple slashes should not be collapsed. """ self.assertEqual("/", self.filesystem.normpath("/")) self.assertEqual("/////", self.filesystem.normpath("/////")) def test_root_path(self): self.assertEqual("|", self.filesystem.normpath("|")) def test_multiple_separators_collapsed_into_root_path(self): self.assertEqual("|", self.filesystem.normpath("|||||")) def test_all_dot_paths_removed_but_one(self): self.assertEqual(".", self.filesystem.normpath(".|.|.|.")) def test_all_dot_paths_removed_if_another_path_component_exists(self): self.assertEqual("|", self.filesystem.normpath("|.|.|.|")) self.assertEqual("foo|bar", self.filesystem.normpath("foo|.|.|.|bar")) def test_ignores_up_level_references_starting_from_root(self): self.assertEqual("|", self.filesystem.normpath("|..|..|..|")) self.assertEqual("|", self.filesystem.normpath("|..|..|foo|bar|..|..|")) self.filesystem.is_windows_fs = False # not an UNC path self.assertEqual("|", self.filesystem.normpath("||..|.|..||")) def test_conserves_up_level_references_starting_from_current_dir(self): self.assertEqual("..|..", self.filesystem.normpath("..|foo|bar|..|..|..")) def test_combine_dot_and_up_level_references_in_absolute_path(self): self.assertEqual("|yes", self.filesystem.normpath("|||||.|..|||yes|no|..|.|||")) def test_dots_in_path_collapses_to_last_path(self): self.assertEqual("bar", self.filesystem.normpath("foo|..|bar")) self.assertEqual("bar", self.filesystem.normpath("foo|..|yes|..|no|..|bar")) class SplitPathTest(PathManipulationTestBase): """Tests SplitPath (which mimics os.path.split) using | as path separator.""" def test_empty_path(self): self.assertEqual(("", ""), self.filesystem.splitpath("")) def test_no_separators(self): self.assertEqual(("", "ab"), self.filesystem.splitpath("ab")) def test_slashes_do_not_split(self): """Tests that '/' is not treated specially if the path separator is '|'.""" self.assertEqual(("", "a/b"), self.filesystem.splitpath("a/b")) def test_eliminate_trailing_separators_from_head(self): self.assertEqual(("a", "b"), self.filesystem.splitpath("a|b")) self.assertEqual(("a", "b"), self.filesystem.splitpath("a|||b")) self.assertEqual(("|a", "b"), self.filesystem.splitpath("|a||b")) self.assertEqual(("a|b", "c"), self.filesystem.splitpath("a|b|c")) self.assertEqual(("|a|b", "c"), self.filesystem.splitpath("|a|b|c")) def test_root_separator_is_not_stripped(self): self.assertEqual(("|||", ""), self.filesystem.splitpath("|||")) self.assertEqual(("|", "a"), self.filesystem.splitpath("|a")) self.assertEqual(("|||", "a"), self.filesystem.splitpath("|||a")) def test_empty_tail_if_path_ends_in_separator(self): self.assertEqual(("a|b", ""), self.filesystem.splitpath("a|b|")) def test_empty_path_components_are_preserved_in_head(self): self.assertEqual(("|a||b", "c"), self.filesystem.splitpath("|a||b||c")) class JoinPathTest(PathManipulationTestBase): """Tests JoinPath (which mimics os.path.join) using | as path separator.""" def test_one_empty_component(self): self.assertEqual("", self.filesystem.joinpaths("")) def test_multiple_empty_components(self): self.assertEqual("", self.filesystem.joinpaths("", "", "")) def test_separators_not_stripped_from_single_component(self): self.assertEqual("||a||", self.filesystem.joinpaths("||a||")) def test_one_separator_added_between_components(self): self.assertEqual("a|b|c|d", self.filesystem.joinpaths("a", "b", "c", "d")) def test_no_separator_added_for_components_ending_in_separator(self): self.assertEqual("a|b|c", self.filesystem.joinpaths("a|", "b|", "c")) self.assertEqual("a|||b|||c", self.filesystem.joinpaths("a|||", "b|||", "c")) def test_components_preceding_absolute_component_are_ignored(self): self.assertEqual("|c|d", self.filesystem.joinpaths("a", "|b", "|c", "d")) def test_one_separator_added_for_trailing_empty_components(self): self.assertEqual("a|", self.filesystem.joinpaths("a", "")) self.assertEqual("a|", self.filesystem.joinpaths("a", "", "")) def test_no_separator_added_for_leading_empty_components(self): self.assertEqual("a", self.filesystem.joinpaths("", "a")) def test_internal_empty_components_ignored(self): self.assertEqual("a|b", self.filesystem.joinpaths("a", "", "b")) self.assertEqual("a|b|", self.filesystem.joinpaths("a|", "", "b|")) class PathSeparatorTest(TestCase): def test_os_path_sep_matches_fake_filesystem_separator(self): filesystem = fake_filesystem.FakeFilesystem(path_separator="!") fake_os_module = fake_os.FakeOsModule(filesystem) self.assertEqual("!", fake_os_module.sep) self.assertEqual("!", fake_os_module.path.sep) class NormalizeCaseTest(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator="/") self.filesystem.is_case_sensitive = False def test_normalize_case(self): self.filesystem.create_file("/Foo/Bar") self.assertEqual( f"{self.filesystem.root_dir_name}Foo/Bar", self.filesystem._original_path("/foo/bar"), ) self.assertEqual( f"{self.filesystem.root_dir_name}Foo/Bar", self.filesystem._original_path("/FOO/BAR"), ) def test_normalize_case_for_drive(self): self.filesystem.is_windows_fs = True self.filesystem.create_file("C:/Foo/Bar") self.assertEqual("C:/Foo/Bar", self.filesystem._original_path("c:/foo/bar")) self.assertEqual("C:/Foo/Bar", self.filesystem._original_path("C:/FOO/BAR")) def test_normalize_case_for_non_existing_file(self): self.filesystem.create_dir("/Foo/Bar") self.assertEqual( f"{self.filesystem.root_dir_name}Foo/Bar/baz", self.filesystem._original_path("/foo/bar/baz"), ) self.assertEqual( f"{self.filesystem.root_dir_name}Foo/Bar/BAZ", self.filesystem._original_path("/FOO/BAR/BAZ"), ) @unittest.skipIf( not TestCase.is_windows, "Regression test for Windows problem only" ) def test_normalize_case_for_lazily_added_empty_file(self): # regression test for specific issue with added empty real files filesystem = fake_filesystem.FakeFilesystem() real_dir_path = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0] filesystem.add_real_directory(real_dir_path) initPyPath = os.path.join(real_dir_path, "__init__.py") self.assertEqual(initPyPath, filesystem._original_path(initPyPath.upper())) class AlternativePathSeparatorTest(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator="!") self.filesystem.alternative_path_separator = "?" def test_initial_value(self): filesystem = fake_filesystem.FakeFilesystem() if self.is_windows: self.assertEqual("/", filesystem.alternative_path_separator) else: self.assertIsNone(filesystem.alternative_path_separator) filesystem = fake_filesystem.FakeFilesystem(path_separator="/") self.assertIsNone(filesystem.alternative_path_separator) def test_alt_sep(self): fake_os_module = fake_os.FakeOsModule(self.filesystem) self.assertEqual("?", fake_os_module.altsep) self.assertEqual("?", fake_os_module.path.altsep) def test_collapse_path_with_mixed_separators(self): self.assertEqual("!foo!bar", self.filesystem.normpath("!foo??bar")) def test_normalize_path_with_mixed_separators(self): path = "foo?..?bar" self.assertEqual( f"{self.filesystem.root_dir_name}bar", self.filesystem.absnormpath(path), ) def test_exists_with_mixed_separators(self): self.filesystem.create_file("?foo?bar?baz") self.filesystem.create_file("!foo!bar!xyzzy!plugh") self.assertTrue(self.filesystem.exists("!foo!bar!baz")) self.assertTrue(self.filesystem.exists("?foo?bar?xyzzy?plugh")) class DriveLetterSupportTest(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator="!") self.filesystem.alternative_path_separator = "^" self.filesystem.is_windows_fs = True def test_initial_value(self): filesystem = fake_filesystem.FakeFilesystem() if self.is_windows: self.assertTrue(filesystem.is_windows_fs) else: self.assertFalse(filesystem.is_windows_fs) def test_collapse_path(self): self.assertEqual("c:!foo!bar", self.filesystem.normpath("c:!!foo!!bar")) def test_collapse_unc_path(self): self.assertEqual("!!foo!bar!baz", self.filesystem.normpath("!!foo!bar!!baz!!")) def test_normalize_path_str(self): self.filesystem.cwd = "" self.assertEqual("c:!foo!bar", self.filesystem.absnormpath("c:!foo!!bar")) self.filesystem.cwd = "c:!foo" self.assertEqual("c:!foo!bar", self.filesystem.absnormpath("bar")) def test_normalize_path_bytes(self): self.filesystem.cwd = b"" self.assertEqual(b"c:!foo!bar", self.filesystem.absnormpath(b"c:!foo!!bar")) self.filesystem.cwd = b"c:!foo" self.assertEqual(b"c:!foo!bar", self.filesystem.absnormpath(b"bar")) def test_split_path_str(self): self.assertEqual(("c:!foo", "bar"), self.filesystem.splitpath("c:!foo!bar")) self.assertEqual(("c:!", "foo"), self.filesystem.splitpath("c:!foo")) self.assertEqual(("!foo", "bar"), self.filesystem.splitpath("!foo!bar")) self.assertEqual(("!", "foo"), self.filesystem.splitpath("!foo")) self.assertEqual(("c:foo", "bar"), self.filesystem.splitpath("c:foo!bar")) self.assertEqual(("c:", "foo"), self.filesystem.splitpath("c:foo")) self.assertEqual(("foo", "bar"), self.filesystem.splitpath("foo!bar")) def test_split_with_alt_separator(self): self.assertEqual(("a^b", "c"), self.filesystem.splitpath("a^b^c")) self.assertEqual(("a^b!c", "d"), self.filesystem.splitpath("a^b!c^d")) self.assertEqual(("a^b!c", "d"), self.filesystem.splitpath("a^b!c!d")) self.assertEqual((b"a^b", b"c"), self.filesystem.splitpath(b"a^b^c")) self.assertEqual((b"a^b!c", b"d"), self.filesystem.splitpath(b"a^b!c^d")) self.assertEqual((b"a^b!c", b"d"), self.filesystem.splitpath(b"a^b!c!d")) def test_split_path_bytes(self): self.assertEqual((b"c:!foo", b"bar"), self.filesystem.splitpath(b"c:!foo!bar")) self.assertEqual((b"c:!", b"foo"), self.filesystem.splitpath(b"c:!foo")) self.assertEqual((b"!foo", b"bar"), self.filesystem.splitpath(b"!foo!bar")) self.assertEqual((b"!", b"foo"), self.filesystem.splitpath(b"!foo")) self.assertEqual((b"c:foo", b"bar"), self.filesystem.splitpath(b"c:foo!bar")) self.assertEqual((b"c:", b"foo"), self.filesystem.splitpath(b"c:foo")) self.assertEqual((b"foo", b"bar"), self.filesystem.splitpath(b"foo!bar")) def test_characters_before_root_ignored_in_join_paths(self): self.assertEqual("c:d", self.filesystem.joinpaths("b", "c:", "d")) def test_resolve_path(self): self.assertEqual("C:!foo!bar", self.filesystem.resolve_path("C:!foo!bar")) def test_get_path_components(self): self.assertEqual( ["c:", "foo", "bar"], self.filesystem._path_components("c:!foo!bar"), ) self.assertEqual(["c:"], self.filesystem._path_components("c:")) def test_split_drive_str(self): self.assertEqual(("c:", "!foo!bar"), self.filesystem.splitdrive("c:!foo!bar")) self.assertEqual(("", "!foo!bar"), self.filesystem.splitdrive("!foo!bar")) self.assertEqual(("c:", "foo!bar"), self.filesystem.splitdrive("c:foo!bar")) self.assertEqual(("", "foo!bar"), self.filesystem.splitdrive("foo!bar")) def test_split_drive_bytes(self): self.assertEqual( (b"c:", b"!foo!bar"), self.filesystem.splitdrive(b"c:!foo!bar") ) self.assertEqual((b"", b"!foo!bar"), self.filesystem.splitdrive(b"!foo!bar")) def test_split_drive_alt_sep(self): self.assertEqual(("c:", "^foo^bar"), self.filesystem.splitdrive("c:^foo^bar")) self.assertEqual(("", "foo^bar"), self.filesystem.splitdrive("foo^bar")) self.assertEqual(("", "foo^bar!baz"), self.filesystem.splitdrive("foo^bar!baz")) self.assertEqual( (b"c:", b"^foo^bar"), self.filesystem.splitdrive(b"c:^foo^bar") ) self.assertEqual((b"", b"^foo^bar"), self.filesystem.splitdrive(b"^foo^bar")) self.assertEqual( (b"", b"^foo^bar!baz"), self.filesystem.splitdrive(b"^foo^bar!baz") ) def test_split_drive_with_unc_path(self): self.assertEqual( ("!!foo!bar", "!baz"), self.filesystem.splitdrive("!!foo!bar!baz") ) self.assertEqual(("", "!!foo"), self.filesystem.splitdrive("!!foo")) self.assertEqual(("", "!!foo!!bar"), self.filesystem.splitdrive("!!foo!!bar")) self.assertEqual(("!!foo!bar", "!!"), self.filesystem.splitdrive("!!foo!bar!!")) def test_split_drive_with_unc_path_alt_sep(self): self.assertEqual( ("^^foo^bar", "!baz"), self.filesystem.splitdrive("^^foo^bar!baz") ) self.assertEqual(("", "^^foo"), self.filesystem.splitdrive("^^foo")) self.assertEqual(("", "^^foo^^bar"), self.filesystem.splitdrive("^^foo^^bar")) self.assertEqual(("^^foo^bar", "^^"), self.filesystem.splitdrive("^^foo^bar^^")) def test_split_path_with_drive(self): self.assertEqual(("d:!foo", "baz"), self.filesystem.splitpath("d:!foo!baz")) self.assertEqual(("d:!foo!baz", ""), self.filesystem.splitpath("d:!foo!baz!")) self.assertEqual(("c:", ""), self.filesystem.splitpath("c:")) self.assertEqual(("c:!", ""), self.filesystem.splitpath("c:!")) self.assertEqual(("c:!!", ""), self.filesystem.splitpath("c:!!")) def test_split_path_with_drive_alt_sep(self): self.assertEqual(("d:^foo", "baz"), self.filesystem.splitpath("d:^foo^baz")) self.assertEqual(("d:^foo^baz", ""), self.filesystem.splitpath("d:^foo^baz^")) self.assertEqual(("c:", ""), self.filesystem.splitpath("c:")) self.assertEqual(("c:^", ""), self.filesystem.splitpath("c:^")) self.assertEqual(("c:^^", ""), self.filesystem.splitpath("c:^^")) def test_split_path_with_unc_path(self): self.assertEqual( ("!!foo!bar!", "baz"), self.filesystem.splitpath("!!foo!bar!baz") ) self.assertEqual(("!!foo!bar", ""), self.filesystem.splitpath("!!foo!bar")) self.assertEqual(("!!foo!bar!!", ""), self.filesystem.splitpath("!!foo!bar!!")) def test_split_path_with_unc_path_alt_sep(self): self.assertEqual( ("^^foo^bar^", "baz"), self.filesystem.splitpath("^^foo^bar^baz") ) self.assertEqual(("^^foo^bar", ""), self.filesystem.splitpath("^^foo^bar")) self.assertEqual(("^^foo^bar^^", ""), self.filesystem.splitpath("^^foo^bar^^")) def test_splitroot_with_drive(self): self.assertEqual( ("E:", "!", "foo!bar"), self.filesystem.splitroot("E:!foo!bar") ) self.assertEqual( ("E:", "!", "!foo!!!bar"), self.filesystem.splitroot("E:!!foo!!!bar") ) self.assertEqual( (b"E:", b"!", b"!foo!!!bar"), self.filesystem.splitroot(b"E:!!foo!!!bar") ) self.assertEqual( ("C:", "^", "foo^bar"), self.filesystem.splitroot("C:^foo^bar") ) def test_splitroot_with_unc_path(self): self.assertEqual( ("!!foo!bar", "!", "baz"), self.filesystem.splitroot("!!foo!bar!baz") ) self.assertEqual( ("!!?!UNC", "!", "foo!bar"), self.filesystem.splitroot("!!?!UNC!foo!bar") ) self.assertEqual( ("^^foo^bar", "^", "baz"), self.filesystem.splitroot("^^foo^bar^baz") ) self.assertEqual( (b"!!foo!bar", b"!", b"baz"), self.filesystem.splitroot(b"!!foo!bar!baz") ) def test_splitroot_with_empty_parts(self): self.assertEqual(("", "", ""), self.filesystem.splitroot("")) self.assertEqual(("", "!", "foo"), self.filesystem.splitroot("!foo")) self.assertEqual(("!!foo!bar", "", ""), self.filesystem.splitroot("!!foo!bar")) self.assertEqual(("!!foo", "", ""), self.filesystem.splitroot("!!foo")) self.assertEqual( ("!!foo!bar", "!", ""), self.filesystem.splitroot("!!foo!bar!") ) self.assertEqual(("C:", "", "foo!bar"), self.filesystem.splitroot("C:foo!bar")) class DiskSpaceTest(TestCase): def setUp(self): self.fs = fake_filesystem.FakeFilesystem(path_separator="!", total_size=100) self.os = fake_os.FakeOsModule(self.fs) self.open = fake_open.FakeFileOpen(self.fs) def test_disk_usage_on_file_creation(self): total_size = 100 self.fs.add_mount_point("!mount", total_size) def create_too_large_file(): with self.open("!mount!file", "w", encoding="utf8") as dest: dest.write("a" * (total_size + 1)) with self.assertRaises(OSError): create_too_large_file() self.assertEqual(0, self.fs.get_disk_usage("!mount").used) with self.open("!mount!file", "w", encoding="utf8") as dest: dest.write("a" * total_size) self.assertEqual(total_size, self.fs.get_disk_usage("!mount").used) def test_disk_usage_on_automounted_drive(self): self.fs.is_windows_fs = True self.fs.reset(total_size=100) self.fs.create_file("!foo!bar", st_size=50) self.assertEqual(0, self.fs.get_disk_usage("D:!").used) self.fs.cwd = "E:!foo" self.assertEqual(0, self.fs.get_disk_usage("!foo").used) def test_disk_usage_on_mounted_paths(self): self.fs.add_mount_point("!foo", total_size=200) self.fs.add_mount_point("!foo!bar", total_size=400) self.fs.create_file("!baz", st_size=50) self.fs.create_file("!foo!baz", st_size=60) self.fs.create_file("!foo!bar!baz", st_size=100) self.assertEqual(50, self.fs.get_disk_usage("!").used) self.assertEqual(60, self.fs.get_disk_usage("!foo").used) self.assertEqual(100, self.fs.get_disk_usage("!foo!bar").used) self.assertEqual(400, self.fs.get_disk_usage("!foo!bar").total) def test_file_system_size_after_large_file_creation(self): filesystem = fake_filesystem.FakeFilesystem( path_separator="!", total_size=1024 * 1024 * 1024 * 100 ) filesystem.create_file("!foo!baz", st_size=1024 * 1024 * 1024 * 10) self.assertEqual( ( 1024 * 1024 * 1024 * 100, 1024 * 1024 * 1024 * 10, 1024 * 1024 * 1024 * 90, ), filesystem.get_disk_usage(), ) def test_file_system_size_after_binary_file_creation(self): self.fs.create_file("!foo!bar", contents=b"xyzzy") self.assertEqual((100, 5, 95), self.fs.get_disk_usage()) def test_file_system_size_after_ascii_string_file_creation(self): self.fs.create_file("!foo!bar", contents="complicated") self.assertEqual((100, 11, 89), self.fs.get_disk_usage()) def test_filesystem_size_after_2byte_unicode_file_creation(self): self.fs.create_file("!foo!bar", contents="сложно", encoding="utf-8") self.assertEqual((100, 12, 88), self.fs.get_disk_usage()) def test_filesystem_size_after_3byte_unicode_file_creation(self): self.fs.create_file("!foo!bar", contents="複雑", encoding="utf-8") self.assertEqual((100, 6, 94), self.fs.get_disk_usage()) def test_file_system_size_after_file_deletion(self): self.fs.create_file("!foo!bar", contents=b"xyzzy") self.fs.create_file("!foo!baz", st_size=20) self.fs.remove_object("!foo!bar") self.assertEqual((100, 20, 80), self.fs.get_disk_usage()) def test_file_system_size_after_directory_removal(self): self.fs.create_file("!foo!bar", st_size=10) self.fs.create_file("!foo!baz", st_size=20) self.fs.create_file("!foo1!bar", st_size=40) self.fs.remove_object("!foo") self.assertEqual((100, 40, 60), self.fs.get_disk_usage()) def test_creating_file_with_fitting_content(self): initial_usage = self.fs.get_disk_usage() try: self.fs.create_file("!foo!bar", contents=b"a" * 100) except OSError: self.fail( "File with contents fitting into disk space could not be written." ) self.assertEqual(initial_usage.used + 100, self.fs.get_disk_usage().used) def test_creating_file_with_content_too_large(self): def create_large_file(): self.fs.create_file("!foo!bar", contents=b"a" * 101) initial_usage = self.fs.get_disk_usage() with self.assertRaises(OSError): create_large_file() self.assertEqual(initial_usage, self.fs.get_disk_usage()) def test_creating_file_with_fitting_size(self): initial_usage = self.fs.get_disk_usage() try: self.fs.create_file("!foo!bar", st_size=100) except OSError: self.fail("File with size fitting into disk space could not be written.") self.assertEqual(initial_usage.used + 100, self.fs.get_disk_usage().used) def test_creating_file_with_size_too_large(self): initial_usage = self.fs.get_disk_usage() def create_large_file(): self.fs.create_file("!foo!bar", st_size=101) with self.assertRaises(OSError): create_large_file() self.assertEqual(initial_usage, self.fs.get_disk_usage()) def test_resize_file_with_fitting_size(self): file_object = self.fs.create_file("!foo!bar", st_size=50) try: file_object.set_large_file_size(100) file_object.set_contents(b"a" * 100) except OSError: self.fail("Resizing file failed although disk space was sufficient.") def test_resize_file_with_size_too_large(self): file_object = self.fs.create_file("!foo!bar", st_size=50) with self.raises_os_error(errno.ENOSPC): file_object.set_large_file_size(200) with self.raises_os_error(errno.ENOSPC): file_object.set_contents("a" * 150) def test_file_system_size_after_directory_rename(self): self.fs.create_file("!foo!bar", st_size=20) self.os.rename("!foo", "!baz") self.assertEqual(20, self.fs.get_disk_usage().used) def test_file_system_size_after_file_rename(self): self.fs.create_file("!foo!bar", st_size=20) self.os.rename("!foo!bar", "!foo!baz") self.assertEqual(20, self.fs.get_disk_usage().used) def test_that_hard_link_does_not_change_used_size(self): file1_path = "test_file1" file2_path = "test_file2" self.fs.create_file(file1_path, st_size=20) self.assertEqual(20, self.fs.get_disk_usage().used) # creating a hard link shall not increase used space self.os.link(file1_path, file2_path) self.assertEqual(20, self.fs.get_disk_usage().used) # removing a file shall not decrease used space # if a hard link still exists self.os.unlink(file1_path) self.assertEqual(20, self.fs.get_disk_usage().used) self.os.unlink(file2_path) self.assertEqual(0, self.fs.get_disk_usage().used) def test_that_the_size_of_correct_mount_point_is_used(self): self.fs.add_mount_point("!mount_limited", total_size=50) self.fs.add_mount_point("!mount_unlimited") with self.raises_os_error(errno.ENOSPC): self.fs.create_file("!mount_limited!foo", st_size=60) with self.raises_os_error(errno.ENOSPC): self.fs.create_file("!bar", st_size=110) try: self.fs.create_file("!foo", st_size=60) self.fs.create_file("!mount_limited!foo", st_size=40) self.fs.create_file("!mount_unlimited!foo", st_size=1000000) except OSError: self.fail( "File with contents fitting into disk space could not be written." ) def test_that_disk_usage_of_correct_mount_point_is_used(self): self.fs.add_mount_point("!mount1", total_size=20) self.fs.add_mount_point("!mount1!bar!mount2", total_size=50) self.fs.create_file("!foo!bar", st_size=10) self.fs.create_file("!mount1!foo!bar", st_size=10) self.fs.create_file("!mount1!bar!mount2!foo!bar", st_size=10) self.assertEqual(90, self.fs.get_disk_usage("!foo").free) self.assertEqual(10, self.fs.get_disk_usage("!mount1!foo").free) self.assertEqual(40, self.fs.get_disk_usage("!mount1!bar!mount2").free) def test_set_larger_disk_size(self): self.fs.add_mount_point("!mount1", total_size=20) with self.raises_os_error(errno.ENOSPC): self.fs.create_file("!mount1!foo", st_size=100) self.fs.set_disk_usage(total_size=200, path="!mount1") self.fs.create_file("!mount1!foo", st_size=100) self.assertEqual(100, self.fs.get_disk_usage("!mount1!foo").free) def test_set_smaller_disk_size(self): self.fs.add_mount_point("!mount1", total_size=200) self.fs.create_file("!mount1!foo", st_size=100) with self.raises_os_error(errno.ENOSPC): self.fs.set_disk_usage(total_size=50, path="!mount1") self.fs.set_disk_usage(total_size=150, path="!mount1") self.assertEqual(50, self.fs.get_disk_usage("!mount1!foo").free) def test_disk_size_on_unlimited_disk(self): self.fs.add_mount_point("!mount1") self.fs.create_file("!mount1!foo", st_size=100) self.fs.set_disk_usage(total_size=1000, path="!mount1") self.assertEqual(900, self.fs.get_disk_usage("!mount1!foo").free) def test_disk_size_on_auto_mounted_drive_on_file_creation(self): self.fs.is_windows_fs = True # drive d: shall be auto-mounted and the used size adapted self.fs.create_file("d:!foo!bar", st_size=100) self.fs.set_disk_usage(total_size=1000, path="d:") self.assertEqual(self.fs.get_disk_usage("d:!foo").free, 900) def test_disk_size_on_auto_mounted_drive_on_directory_creation(self): self.fs.is_windows_fs = True self.fs.create_dir("d:!foo!bar") self.fs.create_file("d:!foo!bar!baz", st_size=100) self.fs.create_file("d:!foo!baz", st_size=100) self.fs.set_disk_usage(total_size=1000, path="d:") self.assertEqual(800, self.fs.get_disk_usage("d:!foo").free) def test_copying_preserves_byte_contents(self): source_file = self.fs.create_file("foo", contents=b"somebytes") dest_file = self.fs.create_file("bar") dest_file.set_contents(source_file.contents) self.assertEqual(dest_file.contents, source_file.contents) def test_diskusage_after_open_write(self): with self.open("bar.txt", "w", encoding="utf8") as f: f.write("a" * 60) f.flush() self.assertEqual(60, self.fs.get_disk_usage()[1]) def test_disk_full_after_reopened(self): with self.open("bar.txt", "w", encoding="utf8") as f: f.write("a" * 60) with self.open("bar.txt", encoding="utf8") as f: self.assertEqual("a" * 60, f.read()) with self.raises_os_error(errno.ENOSPC): with self.open("bar.txt", "w", encoding="utf8") as f: f.write("b" * 110) with self.raises_os_error(errno.ENOSPC): f.flush() with self.open("bar.txt", encoding="utf8") as f: self.assertEqual("", f.read()) def test_disk_full_append(self): file_path = "bar.txt" with self.open(file_path, "w", encoding="utf8") as f: f.write("a" * 60) with self.open(file_path, encoding="utf8") as f: self.assertEqual("a" * 60, f.read()) with self.raises_os_error(errno.ENOSPC): with self.open(file_path, "a", encoding="utf8") as f: f.write("b" * 41) with self.raises_os_error(errno.ENOSPC): f.flush() with self.open("bar.txt", encoding="utf8") as f: self.assertEqual(f.read(), "a" * 60) def test_disk_full_after_reopened_rplus_seek(self): with self.open("bar.txt", "w", encoding="utf8") as f: f.write("a" * 60) with self.open("bar.txt", encoding="utf8") as f: self.assertEqual(f.read(), "a" * 60) with self.raises_os_error(errno.ENOSPC): with self.open("bar.txt", "r+", encoding="utf8") as f: f.seek(50) f.write("b" * 60) with self.raises_os_error(errno.ENOSPC): f.flush() with self.open("bar.txt", encoding="utf8") as f: self.assertEqual(f.read(), "a" * 60) class MountPointTest(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem( path_separator="!", total_size=100 ) def add_mount_points(self): self.filesystem.add_mount_point("!foo") self.filesystem.add_mount_point("!bar") self.filesystem.add_mount_point("!foo!baz") def test_that_new_mount_points_get_new_device_number(self): self.add_mount_points() self.assertEqual(1, self.filesystem.get_object("!").st_dev) self.assertEqual(2, self.filesystem.get_object("!foo").st_dev) self.assertEqual(3, self.filesystem.get_object("!bar").st_dev) self.assertEqual(4, self.filesystem.get_object("!foo!baz").st_dev) def test_that_new_directories_get_correct_device_number(self): self.add_mount_points() self.assertEqual(1, self.filesystem.create_dir("!foo1!bar").st_dev) self.assertEqual(2, self.filesystem.create_dir("!foo!bar").st_dev) self.assertEqual(4, self.filesystem.create_dir("!foo!baz!foo!bar").st_dev) def test_that_new_files_get_correct_device_number(self): self.add_mount_points() self.assertEqual(1, self.filesystem.create_file("!foo1!bar").st_dev) self.assertEqual(2, self.filesystem.create_file("!foo!bar").st_dev) self.assertEqual(4, self.filesystem.create_file("!foo!baz!foo!bar").st_dev) def test_that_mount_point_cannot_be_added_twice(self): self.add_mount_points() with self.raises_os_error(errno.EEXIST): self.filesystem.add_mount_point("!foo") with self.raises_os_error(errno.EEXIST): self.filesystem.add_mount_point("!foo!") def test_that_drives_are_auto_mounted(self): self.filesystem.is_windows_fs = True self.add_mount_points() self.filesystem.create_dir("d:!foo!bar") self.filesystem.create_file("d:!foo!baz") self.filesystem.create_file("z:!foo!baz") self.assertEqual(5, self.filesystem.get_object("d:").st_dev) self.assertEqual(5, self.filesystem.get_object("d:!foo!bar").st_dev) self.assertEqual(5, self.filesystem.get_object("d:!foo!baz").st_dev) self.assertEqual(6, self.filesystem.get_object("z:!foo!baz").st_dev) def test_that_drives_are_auto_mounted_case_insensitive(self): self.filesystem.is_windows_fs = True self.add_mount_points() self.filesystem.is_case_sensitive = False self.filesystem.create_dir("D:!foo!bar") self.filesystem.create_file("e:!foo!baz") self.assertEqual(5, self.filesystem.get_object("D:").st_dev) self.assertEqual(5, self.filesystem.get_object("d:!foo!bar").st_dev) self.assertEqual(6, self.filesystem.get_object("e:!foo").st_dev) self.assertEqual(6, self.filesystem.get_object("E:!Foo!Baz").st_dev) def test_that_unc_paths_are_auto_mounted(self): self.filesystem.is_windows_fs = True self.add_mount_points() self.filesystem.create_dir("!!foo!bar!baz") self.filesystem.create_file("!!foo!bar!bip!bop") self.assertEqual(5, self.filesystem.get_object("!!foo!bar").st_dev) self.assertEqual(5, self.filesystem.get_object("!!foo!bar!bip!bop").st_dev) class ConvenienceMethodTest(RealFsTestCase): def test_create_link_with_non_existent_parent(self): skip_if_symlink_not_supported() file1_path = self.make_path("test_file1") link_path = self.make_path("nonexistent", "test_file2") self.filesystem.create_file(file1_path, contents="link test") self.assertEqual(self.os.stat(file1_path).st_nlink, 1) self.filesystem.create_link(file1_path, link_path) self.assertEqual(self.os.stat(file1_path).st_nlink, 2) self.assertTrue(self.filesystem.exists(link_path)) def test_create_symlink_with_non_existent_parent(self): skip_if_symlink_not_supported() file1_path = self.make_path("test_file1") link_path = self.make_path("nonexistent", "test_file2") self.filesystem.create_file(file1_path, contents="symlink test") self.filesystem.create_symlink(link_path, file1_path) self.assertTrue(self.filesystem.exists(link_path)) self.assertTrue(self.filesystem.islink(link_path)) class RealFileSystemAccessTest(RealFsTestCase): def setUp(self): # use the real path separator to work with the real file system self.filesystem = fake_filesystem.FakeFilesystem() self.fake_open = fake_filesystem.FakeFileOpen(self.filesystem) self.pyfakefs_path = os.path.split(os.path.dirname(os.path.abspath(__file__)))[ 0 ] self.root_path = os.path.split(self.pyfakefs_path)[0] def test_add_non_existing_real_file_raises(self): nonexisting_path = os.path.join("nonexisting", "test.txt") with self.assertRaises(OSError): self.filesystem.add_real_file(nonexisting_path) self.assertFalse(self.filesystem.exists(nonexisting_path)) def test_add_non_existing_real_directory_raises(self): nonexisting_path = "/nonexisting" with self.raises_os_error(errno.ENOENT): self.filesystem.add_real_directory(nonexisting_path) self.assertFalse(self.filesystem.exists(nonexisting_path)) def test_existing_fake_file_raises(self): real_file_path = __file__ self.filesystem.create_file(real_file_path) with self.raises_os_error(errno.EEXIST): self.filesystem.add_real_file(real_file_path) @contextlib.contextmanager def create_real_paths(self): temp_directory = tempfile.mkdtemp() real_dir_root = os.path.join(temp_directory, "root") try: for dir_name in ("foo", "bar"): real_dir = os.path.join(real_dir_root, dir_name) os.makedirs(real_dir, exist_ok=True) with open( os.path.join(real_dir, "test.txt"), "w", encoding="utf8" ) as f: f.write("test") sub_dir = os.path.join(real_dir, "sub") os.makedirs(sub_dir, exist_ok=True) with open(os.path.join(sub_dir, "sub.txt"), "w", encoding="utf8") as f: f.write("sub") yield real_dir_root finally: shutil.rmtree(temp_directory, ignore_errors=True) def test_existing_fake_directory_is_merged_lazily(self): self.filesystem.create_file(os.path.join("/", "root", "foo", "test1.txt")) self.filesystem.create_dir(os.path.join("root", "baz")) with self.create_real_paths() as root_dir: self.filesystem.add_real_directory(root_dir, target_path="/root") self.assertTrue( self.filesystem.exists(os.path.join("root", "foo", "test.txt")) ) self.assertTrue( self.filesystem.exists(os.path.join("root", "foo", "test1.txt")) ) self.assertTrue( self.filesystem.exists(os.path.join("root", "bar", "sub", "sub.txt")) ) self.assertTrue(self.filesystem.exists(os.path.join("root", "baz"))) def test_existing_fake_directory_is_merged(self): self.filesystem.create_file(os.path.join("/", "root", "foo", "test1.txt")) self.filesystem.create_dir(os.path.join("root", "baz")) with self.create_real_paths() as root_dir: self.filesystem.add_real_directory( root_dir, target_path="/root", lazy_read=False ) self.assertTrue( self.filesystem.exists(os.path.join("root", "foo", "test.txt")) ) self.assertTrue( self.filesystem.exists(os.path.join("root", "foo", "test1.txt")) ) self.assertTrue( self.filesystem.exists(os.path.join("root", "bar", "sub", "sub.txt")) ) self.assertTrue(self.filesystem.exists(os.path.join("root", "baz"))) def test_fake_files_cannot_be_overwritten(self): self.filesystem.create_file(os.path.join("/", "root", "foo", "test.txt")) with self.create_real_paths() as root_dir: with self.raises_os_error(errno.EEXIST): self.filesystem.add_real_directory(root_dir, target_path="/root") def test_cannot_overwrite_file_with_dir(self): self.filesystem.create_file(os.path.join("/", "root", "foo")) with self.create_real_paths() as root_dir: with self.raises_os_error(errno.ENOTDIR): self.filesystem.add_real_directory(root_dir, target_path="/root/") def test_cannot_overwrite_symlink_with_dir(self): self.filesystem.create_symlink( os.path.join("/", "root", "foo"), os.path.join("/", "root", "link") ) with self.create_real_paths() as root_dir: with self.raises_os_error(errno.EEXIST): self.filesystem.add_real_directory(root_dir, target_path="/root/") def test_symlink_is_merged(self): skip_if_symlink_not_supported() self.filesystem.create_dir(os.path.join("/", "root", "foo")) with self.create_real_paths() as root_dir: link_path = os.path.join(root_dir, "link.txt") target_path = os.path.join("foo", "sub", "sub.txt") os.symlink(target_path, link_path) self.filesystem.add_real_directory(root_dir, target_path="/root") fake_link_path = os.path.join("/", "root", "link.txt") self.assertTrue(self.filesystem.exists(fake_link_path)) self.assertTrue(self.filesystem.islink(fake_link_path)) def check_fake_file_stat(self, fake_file, real_file_path, target_path=None): if target_path is None or target_path == real_file_path: self.assertTrue(self.filesystem.exists(real_file_path)) else: self.assertFalse(self.filesystem.exists(real_file_path)) self.assertTrue(self.filesystem.exists(target_path)) real_stat = os.stat(real_file_path) self.assertIsNone(fake_file._byte_contents) self.assertEqual(fake_file.st_size, real_stat.st_size) self.assertAlmostEqual(fake_file.st_ctime, real_stat.st_ctime, places=5) self.assertAlmostEqual(fake_file.st_atime, real_stat.st_atime, places=5) self.assertAlmostEqual(fake_file.st_mtime, real_stat.st_mtime, places=5) self.assertEqual(fake_file.st_uid, real_stat.st_uid) self.assertEqual(fake_file.st_gid, real_stat.st_gid) def check_read_only_file(self, fake_file, real_file_path): with open(real_file_path, "rb") as f: real_contents = f.read() self.assertEqual(fake_file.byte_contents, real_contents) if not is_root(): with self.raises_os_error(errno.EACCES): self.fake_open(real_file_path, "w") else: with self.fake_open(real_file_path, "w"): pass def check_writable_file(self, fake_file, real_file_path): with open(real_file_path, "rb") as f: real_contents = f.read() self.assertEqual(fake_file.byte_contents, real_contents) with self.fake_open(real_file_path, "wb") as f: f.write(b"test") with open(real_file_path, "rb") as f: real_contents1 = f.read() self.assertEqual(real_contents1, real_contents) with self.fake_open(real_file_path, "rb") as f: fake_contents = f.read() self.assertEqual(fake_contents, b"test") def test_add_existing_real_file_read_only(self): real_file_path = os.path.abspath(__file__) fake_file = self.filesystem.add_real_file(real_file_path) self.check_fake_file_stat(fake_file, real_file_path) self.assertEqual(fake_file.st_mode & 0o333, 0) self.check_read_only_file(fake_file, real_file_path) def test_add_existing_real_file_read_write(self): real_file_path = os.path.realpath(__file__) fake_file = self.filesystem.add_real_file(real_file_path, read_only=False) self.check_fake_file_stat(fake_file, real_file_path) self.assertEqual(fake_file.st_mode, os.stat(real_file_path).st_mode) self.check_writable_file(fake_file, real_file_path) def test_add_real_file_to_existing_path(self): real_file_path = os.path.abspath(__file__) self.filesystem.create_file("/foo/bar") with self.raises_os_error(errno.EEXIST): self.filesystem.add_real_file(real_file_path, target_path="/foo/bar") def test_add_real_file_to_non_existing_path(self): real_file_path = os.path.abspath(__file__) fake_file = self.filesystem.add_real_file( real_file_path, target_path="/foo/bar" ) self.check_fake_file_stat(fake_file, real_file_path, target_path="/foo/bar") def test_write_to_real_file(self): # regression test for #470 real_file_path = os.path.abspath(__file__) self.filesystem.add_real_file(real_file_path, read_only=False) with self.fake_open(real_file_path, "w", encoding="utf8") as f: f.write("foo") with self.fake_open(real_file_path, "rb") as f: self.assertEqual(b"foo", f.read()) def test_add_existing_real_directory_read_only(self): self.filesystem.add_real_directory(self.pyfakefs_path) self.assertTrue(self.filesystem.exists(self.pyfakefs_path)) self.assertTrue( self.filesystem.exists( os.path.join(self.pyfakefs_path, "fake_filesystem.py") ) ) self.assertTrue( self.filesystem.exists(os.path.join(self.pyfakefs_path, "fake_pathlib.py")) ) file_path = os.path.join(self.pyfakefs_path, "fake_filesystem_shutil.py") fake_file = self.filesystem.resolve(file_path) self.check_fake_file_stat(fake_file, file_path) self.check_read_only_file(fake_file, file_path) def test_add_existing_real_directory_tree(self): self.filesystem.add_real_directory(self.root_path) self.assertTrue( self.filesystem.exists( os.path.join( self.root_path, "pyfakefs", "tests", "fake_filesystem_test.py", ) ) ) self.assertTrue( self.filesystem.exists( os.path.join(self.root_path, "pyfakefs", "fake_filesystem.py") ) ) self.assertTrue( self.filesystem.exists( os.path.join(self.root_path, "pyfakefs", "__init__.py") ) ) @contextlib.contextmanager def create_symlinks(self, symlinks): for link in symlinks: os.symlink(link[0], link[1]) try: yield finally: for link in symlinks: os.unlink(link[1]) @staticmethod def _setup_temp_directory(): real_directory = tempfile.mkdtemp() os.mkdir(os.path.join(real_directory, "fixtures")) with open(os.path.join(real_directory, "all_tests.py"), "w"): pass return real_directory def test_add_existing_real_directory_symlink(self): fake_open = fake_filesystem.FakeFileOpen(self.filesystem) real_directory = self._setup_temp_directory() symlinks = [ ( "..", os.path.join(real_directory, "fixtures", "symlink_dir_relative"), ), ( "../all_tests.py", os.path.join(real_directory, "fixtures", "symlink_file_relative"), ), ( real_directory, os.path.join(real_directory, "fixtures", "symlink_dir_absolute"), ), ( os.path.join(real_directory, "all_tests.py"), os.path.join(real_directory, "fixtures", "symlink_file_absolute"), ), ( "/etc/something", os.path.join( real_directory, "fixtures", "symlink_file_absolute_outside" ), ), ] self.filesystem.create_file("/etc/something") with fake_open("/etc/something", "w", encoding="utf8") as f: f.write("good morning") try: with self.create_symlinks(symlinks): self.filesystem.add_real_directory(real_directory, lazy_read=False) except OSError: if self.is_windows: raise unittest.SkipTest("Symlinks under Windows need admin privileges") raise for link in symlinks: self.assertTrue(self.filesystem.islink(link[1])) # relative self.assertTrue( self.filesystem.exists( os.path.join( real_directory, "fixtures/symlink_dir_relative", ) ) ) self.assertTrue( self.filesystem.exists( os.path.join( real_directory, "fixtures/symlink_dir_relative/all_tests.py", ) ) ) self.assertTrue( self.filesystem.exists( os.path.join( real_directory, "fixtures/symlink_file_relative", ) ) ) # absolute self.assertTrue( self.filesystem.exists( os.path.join( real_directory, "fixtures/symlink_dir_absolute", ) ) ) self.assertTrue( self.filesystem.exists( os.path.join( real_directory, "fixtures/symlink_dir_absolute/all_tests.py", ) ) ) self.assertTrue( self.filesystem.exists( os.path.join( real_directory, "fixtures/symlink_file_absolute", ) ) ) # outside self.assertTrue( self.filesystem.exists( os.path.join( real_directory, "fixtures/symlink_file_absolute_outside", ) ) ) self.assertEqual( fake_open( os.path.join( real_directory, "fixtures/symlink_file_absolute_outside", ), encoding="utf8", ).read(), "good morning", ) def test_add_existing_real_directory_symlink_target_path(self): skip_if_symlink_not_supported() real_directory = self._setup_temp_directory() symlinks = [ ( "..", os.path.join(real_directory, "fixtures", "symlink_dir_relative"), ), ( "../all_tests.py", os.path.join(real_directory, "fixtures", "symlink_file_relative"), ), ] with self.create_symlinks(symlinks): self.filesystem.add_real_directory( real_directory, target_path="/path", lazy_read=False ) self.assertTrue(self.filesystem.exists("/path/fixtures/symlink_dir_relative")) self.assertTrue( self.filesystem.exists("/path/fixtures/symlink_dir_relative/all_tests.py") ) self.assertTrue(self.filesystem.exists("/path/fixtures/symlink_file_relative")) def test_add_existing_real_directory_symlink_lazy_read(self): skip_if_symlink_not_supported() real_directory = self._setup_temp_directory() symlinks = [ ( "..", os.path.join(real_directory, "fixtures", "symlink_dir_relative"), ), ( "../all_tests.py", os.path.join(real_directory, "fixtures", "symlink_file_relative"), ), ] with self.create_symlinks(symlinks): self.filesystem.add_real_directory( real_directory, target_path="/path", lazy_read=True ) self.assertTrue( self.filesystem.exists("/path/fixtures/symlink_dir_relative") ) self.assertTrue( self.filesystem.exists( "/path/fixtures/symlink_dir_relative/all_tests.py" ) ) self.assertTrue( self.filesystem.exists("/path/fixtures/symlink_file_relative") ) def test_add_existing_real_directory_tree_to_other_path(self): self.filesystem.add_real_directory(self.root_path, target_path="/foo/bar") self.assertFalse( self.filesystem.exists( os.path.join(self.pyfakefs_path, "tests", "fake_filesystem_test.py") ) ) self.assertTrue( self.filesystem.exists( os.path.join( "foo", "bar", "pyfakefs", "tests", "fake_filesystem_test.py", ) ) ) self.assertFalse( self.filesystem.exists( os.path.join(self.root_path, "pyfakefs", "fake_filesystem.py") ) ) self.assertTrue( self.filesystem.exists( os.path.join("foo", "bar", "pyfakefs", "__init__.py") ) ) def test_get_object_from_lazily_added_real_directory(self): self.filesystem.is_case_sensitive = True self.filesystem.add_real_directory(self.root_path) self.assertTrue( self.filesystem.get_object( os.path.join(self.root_path, "pyfakefs", "fake_filesystem.py") ) ) self.assertTrue( self.filesystem.get_object( os.path.join(self.root_path, "pyfakefs", "__init__.py") ) ) def test_add_existing_real_directory_lazily(self): disk_size = 1024 * 1024 * 1024 real_dir_path = os.path.join(self.root_path, "pyfakefs") self.filesystem.set_disk_usage(disk_size, real_dir_path) self.filesystem.add_real_directory(real_dir_path) # the directory contents have not been read, the disk usage # has not changed self.assertEqual(disk_size, self.filesystem.get_disk_usage(real_dir_path).free) # checking for existence shall read the directory contents self.assertTrue( self.filesystem.get_object( os.path.join(real_dir_path, "fake_filesystem.py") ) ) # so now the free disk space shall have decreased self.assertGreater( disk_size, self.filesystem.get_disk_usage(real_dir_path).free ) def test_add_existing_real_directory_not_lazily(self): disk_size = 1024 * 1024 * 1024 self.filesystem.set_disk_usage(disk_size, self.pyfakefs_path) self.filesystem.add_real_directory(self.pyfakefs_path, lazy_read=False) # the directory has been read, so the file sizes have # been subtracted from the free space self.assertGreater( disk_size, self.filesystem.get_disk_usage(self.pyfakefs_path).free ) def test_add_existing_real_directory_read_write(self): self.filesystem.add_real_directory(self.pyfakefs_path, read_only=False) self.assertTrue(self.filesystem.exists(self.pyfakefs_path)) self.assertTrue( self.filesystem.exists( os.path.join(self.pyfakefs_path, "fake_filesystem.py") ) ) self.assertTrue( self.filesystem.exists(os.path.join(self.pyfakefs_path, "fake_pathlib.py")) ) file_path = os.path.join(self.pyfakefs_path, "pytest_plugin.py") fake_file = self.filesystem.resolve(file_path) self.check_fake_file_stat(fake_file, file_path) self.check_writable_file(fake_file, file_path) def test_add_existing_real_paths_read_only(self): real_file_path = os.path.realpath(__file__) fixture_path = os.path.join(self.pyfakefs_path, "tests", "fixtures") self.filesystem.add_real_paths([real_file_path, fixture_path]) fake_file = self.filesystem.resolve(real_file_path) self.check_fake_file_stat(fake_file, real_file_path) self.check_read_only_file(fake_file, real_file_path) real_file_path = os.path.join(fixture_path, "module_with_attributes.py") fake_file = self.filesystem.resolve(real_file_path) self.check_fake_file_stat(fake_file, real_file_path) self.check_read_only_file(fake_file, real_file_path) def test_add_existing_real_paths_read_write(self): real_file_path = os.path.realpath(__file__) fixture_path = os.path.join(self.pyfakefs_path, "tests", "fixtures") self.filesystem.add_real_paths([real_file_path, fixture_path], read_only=False) fake_file = self.filesystem.resolve(real_file_path) self.check_fake_file_stat(fake_file, real_file_path) self.check_writable_file(fake_file, real_file_path) real_file_path = os.path.join(fixture_path, "module_with_attributes.py") fake_file = self.filesystem.resolve(real_file_path) self.check_fake_file_stat(fake_file, real_file_path) self.check_writable_file(fake_file, real_file_path) class FileSideEffectTests(TestCase): def side_effect(self): test_case = self test_case.side_effect_called = False def __side_effect(file_object): test_case.side_effect_called = True test_case.side_effect_file_object_content = file_object.contents return __side_effect def setUp(self): # use the real path separator to work with the real file system self.filesystem = fake_filesystem.FakeFilesystem() self.filesystem.create_file("/a/b/file_one", side_effect=self.side_effect()) def test_side_effect_called(self): fake_open = fake_filesystem.FakeFileOpen(self.filesystem) self.side_effect_called = False with fake_open("/a/b/file_one", "w", encoding="utf8") as handle: handle.write("foo") self.assertTrue(self.side_effect_called) def test_side_effect_file_object(self): fake_open = fake_filesystem.FakeFileOpen(self.filesystem) self.side_effect_called = False with fake_open("/a/b/file_one", "w", encoding="utf8") as handle: handle.write("foo") self.assertEqual(self.side_effect_file_object_content, "foo") if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fake_filesystem_unittest_test.py0000644000175100001660000011260414764107375024455 0ustar00runnerdocker# Copyright 2014 Altera Corporation. All Rights Reserved. # Copyright 2015-2017 John McGehee # Author: John McGehee # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Test the :py:class`pyfakefs.fake_filesystem_unittest.TestCase` base class. """ import glob import importlib.util import io import multiprocessing import os import pathlib import runpy import shutil import sys import tempfile import unittest import warnings from contextlib import redirect_stdout from io import StringIO from pathlib import Path from unittest import TestCase, mock import pyfakefs.tests.import_as_example import pyfakefs.tests.logsio from pyfakefs import fake_filesystem_unittest, fake_filesystem from pyfakefs.fake_filesystem import OSType from pyfakefs.fake_filesystem_unittest import ( Patcher, Pause, patchfs, PatchMode, ) from pyfakefs.tests.fixtures import module_with_attributes if sys.version_info < (3, 12): # distutils removed in Python 3.12 from distutils.dir_util import copy_tree, remove_tree # Work around pyupgrade auto-rewriting `io.open()` to `open()`. io_open = io.open class TestPatcher(TestCase): def test_context_manager(self): with Patcher() as patcher: patcher.fs.create_file("/foo/bar", contents="test") with open("/foo/bar", encoding="utf8") as f: contents = f.read() self.assertEqual("test", contents) @patchfs def test_context_decorator(self, fake_fs): fake_fs.create_file("/foo/bar", contents="test") with open("/foo/bar", encoding="utf8") as f: contents = f.read() self.assertEqual("test", contents) class TestPatchfsArgumentOrder(TestCase): @patchfs @mock.patch("os.system") def test_argument_order1(self, fake_fs, patched_system): fake_fs.create_file("/foo/bar", contents="test") with open("/foo/bar", encoding="utf8") as f: contents = f.read() self.assertEqual("test", contents) os.system("foo") patched_system.assert_called_with("foo") @mock.patch("os.system") @patchfs def test_argument_order2(self, patched_system, fake_fs): fake_fs.create_file("/foo/bar", contents="test") with open("/foo/bar", encoding="utf8") as f: contents = f.read() self.assertEqual("test", contents) os.system("foo") patched_system.assert_called_with("foo") class TestPyfakefsUnittestBase(fake_filesystem_unittest.TestCase): def setUp(self): """Set up the fake file system""" self.setUpPyfakefs() class TestPyfakefsUnittest(TestPyfakefsUnittestBase): # pylint: disable=R0904 """Test the `pyfakefs.fake_filesystem_unittest.TestCase` base class.""" def test_open(self): """Fake `open()` function is bound""" self.assertFalse(os.path.exists("/fake_file.txt")) with open("/fake_file.txt", "w", encoding="utf8") as f: f.write("This test file was created using the open() function.\n") self.assertTrue(self.fs.exists("/fake_file.txt")) with open("/fake_file.txt", encoding="utf8") as f: content = f.read() self.assertEqual( "This test file was created using the open() function.\n", content, ) def test_io_open(self): """Fake io module is bound""" self.assertFalse(os.path.exists("/fake_file.txt")) with io_open("/fake_file.txt", "w", encoding="utf8") as f: f.write("This test file was created using the io.open() function.\n") self.assertTrue(self.fs.exists("/fake_file.txt")) with open("/fake_file.txt", encoding="utf8") as f: content = f.read() self.assertEqual( "This test file was created using the io.open() function.\n", content, ) def test_os(self): """Fake os module is bound""" self.assertFalse(self.fs.exists("/test/dir1/dir2")) os.makedirs("/test/dir1/dir2") self.assertTrue(self.fs.exists("/test/dir1/dir2")) def test_glob(self): """Fake glob module is bound""" is_windows = sys.platform.startswith("win") self.assertEqual([], glob.glob("/test/dir1/dir*")) self.fs.create_dir("/test/dir1/dir2a") matching_paths = glob.glob("/test/dir1/dir*") if is_windows: self.assertEqual([r"/test/dir1\dir2a"], matching_paths) else: self.assertEqual(["/test/dir1/dir2a"], matching_paths) self.fs.create_dir("/test/dir1/dir2b") matching_paths = sorted(glob.glob("/test/dir1/dir*")) if is_windows: self.assertEqual([r"/test/dir1\dir2a", r"/test/dir1\dir2b"], matching_paths) else: self.assertEqual(["/test/dir1/dir2a", "/test/dir1/dir2b"], matching_paths) def test_shutil(self): """Fake shutil module is bound""" self.fs.create_dir("/test/dir1/dir2a") self.fs.create_dir("/test/dir1/dir2b") self.assertTrue(self.fs.exists("/test/dir1/dir2b")) self.assertTrue(self.fs.exists("/test/dir1/dir2a")) shutil.rmtree("/test/dir1") self.assertFalse(self.fs.exists("/test/dir1")) def test_fakepathlib(self): p = pathlib.Path("/fake_file.txt") with p.open("w", encoding="utf8") as f: f.write("text") is_windows = sys.platform.startswith("win") if is_windows: self.assertTrue(self.fs.exists(r"\fake_file.txt")) else: self.assertTrue(self.fs.exists("/fake_file.txt")) class TestPatchingImports(TestPyfakefsUnittestBase): def test_import_as_other_name(self): file_path = "/foo/bar/baz" self.fs.create_file(file_path) self.assertTrue(self.fs.exists(file_path)) self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists1(file_path)) def test_import_path_from_os(self): """Make sure `from os import path` patches `path`.""" file_path = "/foo/bar/baz" self.fs.create_file(file_path) self.assertTrue(self.fs.exists(file_path)) self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists2(file_path)) def test_import_path_from_pathlib(self): file_path = "/foo/bar" self.fs.create_dir(file_path) self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists3(file_path)) def test_import_exists_from_os_path(self): file_path = "/foo/bar" self.fs.create_dir(file_path) self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists5(file_path)) def test_import_isfile_from_os_path(self): file_path = "/foo/bar" self.fs.create_file(file_path) self.assertTrue(pyfakefs.tests.import_as_example.check_if_isfile(file_path)) def test_import_isdir_from_os_path(self): file_path = "/foo/bar" self.fs.create_dir(file_path) self.assertTrue(pyfakefs.tests.import_as_example.check_if_isdir(file_path)) def test_import_islink_from_os_path(self): file_path = "/foo/bar" link_path = "/foo/link" self.fs.create_file(file_path) self.fs.create_symlink(link_path, file_path) self.assertTrue(pyfakefs.tests.import_as_example.check_if_islink(link_path)) def test_import_function_from_os_path_as_other_name(self): file_path = "/foo/bar" self.fs.create_dir(file_path) self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists6(file_path)) def test_import_pathlib_path(self): file_path = "/foo/bar" self.fs.create_dir(file_path) self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists7(file_path)) def test_import_function_from_os(self): file_path = "/foo/bar" self.fs.create_file(file_path, contents=b"abc") stat_result = pyfakefs.tests.import_as_example.file_stat1(file_path) self.assertEqual(3, stat_result.st_size) def test_import_function_from_os_as_other_name(self): file_path = "/foo/bar" self.fs.create_file(file_path, contents=b"abc") stat_result = pyfakefs.tests.import_as_example.file_stat2(file_path) self.assertEqual(3, stat_result.st_size) def test_import_open_as_other_name(self): file_path = "/foo/bar" self.fs.create_file(file_path, contents=b"abc") contents = pyfakefs.tests.import_as_example.file_contents1(file_path) self.assertEqual("abc", contents) def test_import_io_open_as_other_name(self): file_path = "/foo/bar" self.fs.create_file(file_path, contents=b"abc") contents = pyfakefs.tests.import_as_example.file_contents2(file_path) self.assertEqual("abc", contents) class TestPatchingDefaultArgs(fake_filesystem_unittest.TestCase): def setUp(self): self.setUpPyfakefs(patch_default_args=True) def test_path_exists_as_default_arg_in_function(self): file_path = "/foo/bar" self.fs.create_dir(file_path) self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists4(file_path)) def test_path_exists_as_default_arg_in_method(self): file_path = "/foo/bar" self.fs.create_dir(file_path) sut = pyfakefs.tests.import_as_example.TestDefaultArg() self.assertTrue(sut.check_if_exists(file_path)) def test_fake_path_exists4(self): self.fs.create_file("foo") self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists4("foo")) class TestAttributesWithFakeModuleNames(TestPyfakefsUnittestBase): """Test that module attributes with names like `path` or `io` are not stubbed out. """ def test_attributes(self): """Attributes of module under test are not patched""" self.assertEqual(module_with_attributes.os, "os attribute value") self.assertEqual(module_with_attributes.path, "path attribute value") self.assertEqual(module_with_attributes.pathlib, "pathlib attribute value") self.assertEqual(module_with_attributes.shutil, "shutil attribute value") self.assertEqual(module_with_attributes.io, "io attribute value") import math as path # noqa: E402 wanted import not at top class TestPathNotPatchedIfNotOsPath(TestPyfakefsUnittestBase): """Tests that `path` is not patched if it is not `os.path`. An own path module (in this case an alias to math) can be imported and used. """ def test_own_path_module(self): self.assertEqual(2, path.floor(2.5)) class FailedPatchingTest(TestPyfakefsUnittestBase): """Negative tests: make sure the tests for `modules_to_reload` and `modules_to_patch` fail if not providing the arguments. """ @unittest.expectedFailure def test_system_stat(self): file_path = "/foo/bar" self.fs.create_file(file_path, contents=b"test") self.assertEqual( 4, pyfakefs.tests.import_as_example.system_stat(file_path).st_size ) class ReloadModuleTest(fake_filesystem_unittest.TestCase): """Make sure that reloading a module allows patching of classes not patched automatically. """ def setUp(self): """Set up the fake file system""" self.setUpPyfakefs(modules_to_reload=[pyfakefs.tests.import_as_example]) class NoSkipNamesTest(fake_filesystem_unittest.TestCase): """Reference test for additional_skip_names tests: make sure that the module is patched by default.""" def setUp(self): self.setUpPyfakefs() def test_path_exists(self): self.assertFalse(pyfakefs.tests.import_as_example.exists_this_file()) def test_fake_path_exists1(self): self.fs.create_file("foo") self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists1("foo")) def test_fake_path_exists2(self): self.fs.create_file("foo") self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists2("foo")) def test_fake_path_exists3(self): self.fs.create_file("foo") self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists3("foo")) def test_fake_path_exists5(self): self.fs.create_file("foo") self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists5("foo")) def test_fake_path_exists6(self): self.fs.create_file("foo") self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists6("foo")) def test_fake_path_exists7(self): self.fs.create_file("foo") self.assertTrue(pyfakefs.tests.import_as_example.check_if_exists7("foo")) def test_open_fails(self): with self.assertRaises(OSError): pyfakefs.tests.import_as_example.open_this_file() def test_open_patched_in_module_ending_with_io(self): # regression test for #569 file_path = "/foo/bar" self.fs.create_file(file_path, contents=b"abc") contents = pyfakefs.tests.logsio.file_contents(file_path) self.assertEqual(b"abc", contents) class AdditionalSkipNamesTest(fake_filesystem_unittest.TestCase): """Make sure that modules in additional_skip_names are not patched. Passes module name to `additional_skip_names`.""" def setUp(self): self.setUpPyfakefs(additional_skip_names=["pyfakefs.tests.import_as_example"]) def test_path_exists(self): self.assertTrue(pyfakefs.tests.import_as_example.exists_this_file()) def test_fake_path_does_not_exist1(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists1("foo")) def test_fake_path_does_not_exist2(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists2("foo")) def test_fake_path_does_not_exist3(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists3("foo")) def test_fake_path_does_not_exist4(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists4("foo")) def test_fake_path_does_not_exist5(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists5("foo")) def test_fake_path_does_not_exist6(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists6("foo")) def test_fake_path_does_not_exist7(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists7("foo")) def test_open_succeeds(self): pyfakefs.tests.import_as_example.open_this_file() def test_path_succeeds(self): pyfakefs.tests.import_as_example.return_this_file_path() class AdditionalSkipNamesModuleTest(fake_filesystem_unittest.TestCase): """Make sure that modules in additional_skip_names are not patched. Passes module to `additional_skip_names`.""" def setUp(self): self.setUpPyfakefs(additional_skip_names=[pyfakefs.tests.import_as_example]) def test_path_exists(self): self.assertTrue(pyfakefs.tests.import_as_example.exists_this_file()) def test_fake_path_does_not_exist1(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists1("foo")) def test_fake_path_does_not_exist2(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists2("foo")) def test_fake_path_does_not_exist3(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists3("foo")) def test_fake_path_does_not_exist4(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists4("foo")) def test_fake_path_does_not_exist5(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists5("foo")) def test_fake_path_does_not_exist6(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists6("foo")) def test_fake_path_does_not_exist7(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists7("foo")) def test_open_succeeds(self): pyfakefs.tests.import_as_example.open_this_file() def test_path_succeeds(self): pyfakefs.tests.import_as_example.return_this_file_path() class RuntimeSkipModuleTest(fake_filesystem_unittest.TestCase): """Emulates skipping a module using RUNTIME_SKIPMODULES. Not all functionality implemented for skip modules will work here.""" def setUp(self): Patcher.RUNTIME_SKIPMODULES.update( {"pyfakefs.tests.import_as_example": ["pyfakefs.tests.import_"]} ) self.setUpPyfakefs() def tearDown(self): del self.patcher.RUNTIME_SKIPMODULES["pyfakefs.tests.import_as_example"] def test_fake_path_does_not_exist1(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists1("foo")) def test_fake_path_does_not_exist2(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists2("foo")) def test_fake_path_does_not_exist3(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists3("foo")) def test_fake_path_does_not_exist4(self): self.fs.create_file("foo") self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists4("foo")) class FakeExampleModule: """Used to patch a function that uses system-specific functions that cannot be patched automatically.""" _orig_module = pyfakefs.tests.import_as_example def __init__(self, fs): pass def system_stat(self, filepath): return os.stat(filepath) def __getattr__(self, name): """Forwards any non-faked calls to the standard module.""" return getattr(self._orig_module, name) class PatchModuleTest(fake_filesystem_unittest.TestCase): """Make sure that reloading a module allows patching of classes not patched automatically. """ def setUp(self): """Set up the fake file system""" self.setUpPyfakefs( modules_to_patch={"pyfakefs.tests.import_as_example": FakeExampleModule} ) def test_system_stat(self): file_path = "/foo/bar" self.fs.create_file(file_path, contents=b"test") self.assertEqual( 4, pyfakefs.tests.import_as_example.system_stat(file_path).st_size ) class PatchModuleTestUsingDecorator(unittest.TestCase): """Make sure that reloading a module allows patching of classes not patched automatically - use patchfs decorator with parameter. """ @patchfs @unittest.expectedFailure def test_system_stat_failing(self, fake_fs): file_path = "/foo/bar" fake_fs.create_file(file_path, contents=b"test") self.assertEqual( 4, pyfakefs.tests.import_as_example.system_stat(file_path).st_size ) @patchfs(modules_to_patch={"pyfakefs.tests.import_as_example": FakeExampleModule}) def test_system_stat(self, fake_fs): file_path = "/foo/bar" fake_fs.create_file(file_path, contents=b"test") self.assertEqual( 4, pyfakefs.tests.import_as_example.system_stat(file_path).st_size ) class NoRootUserTest(fake_filesystem_unittest.TestCase): """Test allow_root_user argument to setUpPyfakefs.""" def setUp(self): self.setUpPyfakefs(allow_root_user=False) def test_non_root_behavior(self): """Check that fs behaves as non-root user regardless of actual user rights. """ self.fs.is_windows_fs = False dir_path = "/foo/bar" self.fs.create_dir(dir_path, perm_bits=0o555) file_path = dir_path + "baz" with self.assertRaises(OSError): self.fs.create_file(file_path) file_path = "/baz" self.fs.create_file(file_path) os.chmod(file_path, 0o400) with self.assertRaises(OSError): open(file_path, "w", encoding="utf8") class PauseResumeTest(fake_filesystem_unittest.TestCase): def setUp(self): self.setUpPyfakefs() def test_pause_resume(self): fake_temp_file = tempfile.NamedTemporaryFile() self.assertTrue(self.fs.exists(fake_temp_file.name)) self.assertTrue(os.path.exists(fake_temp_file.name)) self.pause() self.assertTrue(self.fs.exists(fake_temp_file.name)) self.assertFalse(os.path.exists(fake_temp_file.name)) real_temp_file = tempfile.NamedTemporaryFile() self.assertFalse(self.fs.exists(real_temp_file.name)) self.assertTrue(os.path.exists(real_temp_file.name)) self.resume() self.assertFalse(os.path.exists(real_temp_file.name)) self.assertTrue(os.path.exists(fake_temp_file.name)) def test_pause_resume_fs(self): fake_temp_file = tempfile.NamedTemporaryFile() self.assertTrue(self.fs.exists(fake_temp_file.name)) self.assertTrue(os.path.exists(fake_temp_file.name)) # resume does nothing if not paused self.fs.resume() self.assertTrue(os.path.exists(fake_temp_file.name)) self.fs.pause() self.assertTrue(self.fs.exists(fake_temp_file.name)) self.assertFalse(os.path.exists(fake_temp_file.name)) real_temp_file = tempfile.NamedTemporaryFile() self.assertFalse(self.fs.exists(real_temp_file.name)) self.assertTrue(os.path.exists(real_temp_file.name)) # pause does nothing if already paused self.fs.pause() self.assertFalse(self.fs.exists(real_temp_file.name)) self.assertTrue(os.path.exists(real_temp_file.name)) self.fs.resume() self.assertFalse(os.path.exists(real_temp_file.name)) self.assertTrue(os.path.exists(fake_temp_file.name)) def test_pause_resume_contextmanager(self): fake_temp_file = tempfile.NamedTemporaryFile() self.assertTrue(self.fs.exists(fake_temp_file.name)) self.assertTrue(os.path.exists(fake_temp_file.name)) with Pause(self): self.assertTrue(self.fs.exists(fake_temp_file.name)) self.assertFalse(os.path.exists(fake_temp_file.name)) real_temp_file = tempfile.NamedTemporaryFile() self.assertFalse(self.fs.exists(real_temp_file.name)) self.assertTrue(os.path.exists(real_temp_file.name)) self.assertFalse(os.path.exists(real_temp_file.name)) self.assertTrue(os.path.exists(fake_temp_file.name)) def test_pause_resume_fs_contextmanager(self): fake_temp_file = tempfile.NamedTemporaryFile() self.assertTrue(self.fs.exists(fake_temp_file.name)) self.assertTrue(os.path.exists(fake_temp_file.name)) with Pause(self.fs): self.assertTrue(self.fs.exists(fake_temp_file.name)) self.assertFalse(os.path.exists(fake_temp_file.name)) real_temp_file = tempfile.NamedTemporaryFile() self.assertFalse(self.fs.exists(real_temp_file.name)) self.assertTrue(os.path.exists(real_temp_file.name)) self.assertFalse(os.path.exists(real_temp_file.name)) self.assertTrue(os.path.exists(fake_temp_file.name)) def test_pause_resume_without_patcher(self): fs = fake_filesystem.FakeFilesystem() with self.assertRaises(RuntimeError): fs.resume() def test_that_tempfile_is_patched_after_resume(fs): """Regression test for #1098""" fs.pause() fs.resume() with tempfile.NamedTemporaryFile(): pass class PauseResumePatcherTest(fake_filesystem_unittest.TestCase): def test_pause_resume(self): with Patcher() as p: fake_temp_file = tempfile.NamedTemporaryFile() self.assertTrue(p.fs.exists(fake_temp_file.name)) self.assertTrue(os.path.exists(fake_temp_file.name)) p.pause() self.assertTrue(p.fs.exists(fake_temp_file.name)) self.assertFalse(os.path.exists(fake_temp_file.name)) real_temp_file = tempfile.NamedTemporaryFile() self.assertFalse(p.fs.exists(real_temp_file.name)) self.assertTrue(os.path.exists(real_temp_file.name)) p.resume() self.assertFalse(os.path.exists(real_temp_file.name)) self.assertTrue(os.path.exists(fake_temp_file.name)) real_temp_file.close() def test_pause_resume_contextmanager(self): with Patcher() as p: fake_temp_file = tempfile.NamedTemporaryFile() self.assertTrue(p.fs.exists(fake_temp_file.name)) self.assertTrue(os.path.exists(fake_temp_file.name)) with Pause(p): self.assertTrue(p.fs.exists(fake_temp_file.name)) self.assertFalse(os.path.exists(fake_temp_file.name)) real_temp_file = tempfile.NamedTemporaryFile() self.assertFalse(p.fs.exists(real_temp_file.name)) self.assertTrue(os.path.exists(real_temp_file.name)) self.assertFalse(os.path.exists(real_temp_file.name)) self.assertTrue(os.path.exists(fake_temp_file.name)) real_temp_file.close() class TestPyfakefsTestCase(unittest.TestCase): def setUp(self): class TestTestCase(fake_filesystem_unittest.TestCase): def runTest(self): pass self.test_case = TestTestCase("runTest") def test_test_case_type(self): self.assertIsInstance(self.test_case, unittest.TestCase) self.assertIsInstance(self.test_case, fake_filesystem_unittest.TestCaseMixin) class TestTempDirCreation(fake_filesystem_unittest.TestCase): """Test that the temp directory exists at test start.""" def setUp(self): self.setUpPyfakefs() def test_tempdir_exists(self): self.assertTrue(os.path.exists(tempfile.gettempdir())) @unittest.skipIf(sys.platform == "win32", "POSIX only test") def test_tmp_exists(self): # directory or link under Linux, link under macOS self.assertTrue(os.path.exists("/tmp")) class TestTempFileReload(unittest.TestCase): """Regression test for #356 to make sure that reloading the tempfile does not affect other tests.""" def test_fakefs(self): with Patcher() as patcher: patcher.fs.create_file("/mytempfile", contents="abcd") def test_value(self): v = multiprocessing.Value("I", 0) self.assertEqual(v.value, 0) class TestPyfakefsTestCaseMixin( unittest.TestCase, fake_filesystem_unittest.TestCaseMixin ): def test_set_up_pyfakefs(self): self.setUpPyfakefs() self.assertTrue(hasattr(self, "fs")) self.assertIsInstance(self.fs, fake_filesystem.FakeFilesystem) class TestShutilWithZipfile(fake_filesystem_unittest.TestCase): """Regression test for #427.""" def setUp(self): self.setUpPyfakefs() self.fs.create_file("foo/bar") def test_a(self): shutil.make_archive("archive", "zip", root_dir="foo") def test_b(self): # used to fail because 'bar' could not be found shutil.make_archive("archive", "zip", root_dir="foo") if sys.version_info < (3, 12): class TestDistutilsCopyTree(fake_filesystem_unittest.TestCase): """Regression test for #501.""" def setUp(self): self.setUpPyfakefs() self.fs.create_dir("./test/subdir/") self.fs.create_dir("./test/subdir2/") self.fs.create_file("./test2/subdir/1.txt") def test_file_copied(self): copy_tree("./test2/", "./test/") remove_tree("./test2/") self.assertTrue(os.path.isfile("./test/subdir/1.txt")) self.assertFalse(os.path.isdir("./test2/")) def test_file_copied_again(self): # used to fail because 'test2' could not be found self.assertTrue(os.path.isfile("./test2/subdir/1.txt")) copy_tree("./test2/", "./test/") remove_tree("./test2/") self.assertTrue(os.path.isfile("./test/subdir/1.txt")) self.assertFalse(os.path.isdir("./test2/")) class PathlibTest(TestCase): """Regression test for #527""" @patchfs def test_cwd(self, fs): """Make sure fake file system is used for os in pathlib""" is_windows = sys.platform.startswith("win") root_dir = "C:" + os.path.sep if is_windows else os.path.sep self.assertEqual(root_dir, str(pathlib.Path.cwd())) dot_abs = pathlib.Path(".").absolute() self.assertEqual(root_dir, str(dot_abs)) self.assertTrue(dot_abs.exists()) class TestDeprecationSuppression(fake_filesystem_unittest.TestCase): def test_no_deprecation_warning(self): """Ensures that deprecation warnings are suppressed during module lookup, see #542. """ from pyfakefs.tests.fixtures.deprecated_property import ( # noqa: F401 DeprecationTest, ) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("error", DeprecationWarning) self.setUpPyfakefs() self.assertEqual(0, len(w)) def load_configs(configs): """Helper code for patching open_code in auto mode, see issue #554.""" retval = [] for config in configs: if len(config) > 3 and config[-3:] == ".py": retval += runpy.run_path(config) else: retval += runpy.run_module(config) return retval @unittest.skipIf(sys.version_info < (3, 8), "open_code new in Python 3.8") class AutoPatchOpenCodeTestCase(fake_filesystem_unittest.TestCase): """Test patching open_code in auto mode, see issue #554.""" def setUp(self): self.setUpPyfakefs(patch_open_code=PatchMode.AUTO) self.configpy = "configpy.py" self.fs.create_file(self.configpy, contents="configurable_value='yup'") self.config_module = "pyfakefs.tests.fixtures.config_module" def test_both(self): load_configs([self.configpy, self.config_module]) def test_run_path(self): load_configs([self.configpy]) def test_run_module(self): load_configs([self.config_module]) def import_foo(self): spec = importlib.util.spec_from_file_location("bar", "/foo/bar.py") mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) def test_exec_module_in_fake_fs(self): self.fs.create_file("/foo/bar.py", contents="print('hello')") with redirect_stdout(StringIO()) as stdout: self.import_foo() assert stdout.getvalue() == "hello\n" class TestOtherFS(fake_filesystem_unittest.TestCase): def setUp(self): self.setUpPyfakefs() @mock.patch.dict(os.environ, {"HOME": "/home/john"}) def test_real_file_with_home(self): """Regression test for #558""" self.fs.is_windows_fs = os.name != "nt" if self.fs.is_windows_fs: self.fs.is_macos = False self.fs.add_real_file(__file__) with open(__file__, encoding="utf8") as f: self.assertTrue(f.read()) home = Path.home() os.chdir(home) with open(__file__, encoding="utf8") as f: self.assertTrue(f.read()) def test_windows(self): self.fs.os = OSType.WINDOWS path = r"C:\foo\bar" self.assertEqual(path, os.path.join("C:\\", "foo", "bar")) self.assertEqual(("C:", r"\foo\bar"), os.path.splitdrive(path)) self.fs.create_file(path) self.assertTrue(os.path.exists(path)) self.assertTrue(os.path.exists(path.upper())) self.assertTrue(os.path.ismount(r"\\share\foo")) self.assertTrue(os.path.ismount(r"C:")) self.assertEqual("\\", os.sep) self.assertEqual("\\", os.path.sep) self.assertEqual("/", os.altsep) self.assertEqual(";", os.pathsep) self.assertEqual("\r\n", os.linesep) self.assertEqual("NUL", os.devnull) def test_linux(self): self.fs.os = OSType.LINUX path = "/foo/bar" self.assertEqual(path, os.path.join("/", "foo", "bar")) self.assertEqual(("", "C:/foo/bar"), os.path.splitdrive("C:/foo/bar")) self.fs.create_file(path) self.assertTrue(os.path.exists(path)) self.assertFalse(os.path.exists(path.upper())) self.assertTrue(os.path.ismount("/")) self.assertFalse(os.path.ismount("//share/foo")) self.assertEqual("/", os.sep) self.assertEqual("/", os.path.sep) self.assertEqual(None, os.altsep) self.assertEqual(":", os.pathsep) self.assertEqual("\n", os.linesep) self.assertEqual("/dev/null", os.devnull) def test_macos(self): self.fs.os = OSType.MACOS path = "/foo/bar" self.assertEqual(path, os.path.join("/", "foo", "bar")) self.assertEqual(("", "C:/foo/bar"), os.path.splitdrive("C:/foo/bar")) self.fs.create_file(path) self.assertTrue(os.path.exists(path)) self.assertTrue(os.path.exists(path.upper())) self.assertTrue(os.path.ismount("/")) self.assertFalse(os.path.ismount("//share/foo")) self.assertEqual("/", os.sep) self.assertEqual("/", os.path.sep) self.assertEqual(None, os.altsep) self.assertEqual(":", os.pathsep) self.assertEqual("\n", os.linesep) self.assertEqual("/dev/null", os.devnull) def test_drivelike_path(self): self.fs.os = OSType.LINUX folder = Path("/test") file_path = folder / "C:/testfile" file_path.parent.mkdir(parents=True) file_path.touch() os.chdir(folder) self.assertTrue(os.path.exists(str(file_path.relative_to(folder)))) @unittest.skipIf(sys.platform != "win32", "Windows-specific test") def test_tempfile_access(self): # regression test for #912 self.fs.os = OSType.LINUX tmp_file = tempfile.TemporaryFile() assert tmp_file @unittest.skipIf(sys.platform != "win32", "Windows-specific behavior") class TestAbsolutePathOnWindows(fake_filesystem_unittest.TestCase): @patchfs def test_is_absolute(self, fs): # regression test for #673 self.assertTrue(pathlib.Path(".").absolute().is_absolute()) @unittest.skipIf(sys.version_info < (3, 8), "Not available before Python 3.8") class TestClassSetup(fake_filesystem_unittest.TestCase): @classmethod def setUpClass(cls): cls.setUpClassPyfakefs() cls.fake_fs().create_file("foo/bar", contents="test") def test_using_fs_functions(self): self.assertTrue(os.path.exists("foo/bar")) with open("foo/bar", encoding="utf8") as f: contents = f.read() self.assertEqual("test", contents) def test_using_fakefs(self): self.assertTrue(self.fs.exists("foo/bar")) f = self.fs.get_object("foo/bar") self.assertEqual("test", f.contents) class TestTempPathCreation(fake_filesystem_unittest.TestCase): """Regression test for #965. Checks that the temp file system is properly created with a root-owned root path. """ def setUp(self): self.setUpPyfakefs() def check_write_tmp_after_reset(self, os_type): self.fs.os = os_type # Mark '/' to be modifiable by only root os.chown("/", 0, 0) os.chmod("/", 0b111_101_101) with tempfile.TemporaryFile("wb") as f: assert f.write(b"foo") == 3 def test_write_tmp_linux(self): self.check_write_tmp_after_reset(OSType.LINUX) def test_write_tmp_macos(self): self.check_write_tmp_after_reset(OSType.MACOS) def test_write_tmp_windows(self): self.check_write_tmp_after_reset(OSType.WINDOWS) @unittest.skipIf(sys.version_info < (3, 8), "Not available before Python 3.8") class FakeImportTest(fake_filesystem_unittest.TestCase): """Checks that a fake module can be imported in AUTO patch mode.""" def setUp(self): self.setUpPyfakefs(patch_open_code=PatchMode.AUTO) def test_simple_fake_import(self): fake_module_path = Path("/") / "site-packages" / "fake_module.py" self.fs.create_file(fake_module_path, contents="number = 42") sys.path.insert(0, str(fake_module_path.parent)) module = importlib.import_module("fake_module") del sys.path[0] assert module.__name__ == "fake_module" assert module.number == 42 def test_fake_import_dotted_module(self): fake_pkg_path = Path("/") / "site-packages" self.fs.create_file(fake_pkg_path / "fakepkg" / "__init__.py") fake_module_path = fake_pkg_path / "fakepkg" / "fake_module.py" self.fs.create_file(fake_module_path, contents="number = 42") sys.path.insert(0, str(fake_pkg_path)) module = importlib.import_module("fakepkg.fake_module") del sys.path[0] assert module.__name__ == "fakepkg.fake_module" assert module.number == 42 def test_fake_relative_import(self): fake_module_path = Path("site-packages") / "fake_module.py" self.fs.create_file(fake_module_path, contents="number = 42") sys.path.insert(0, str(fake_module_path.parent)) module = importlib.import_module("fake_module") del sys.path[0] assert module.__name__ == "fake_module" assert module.number == 42 if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fake_filesystem_vs_real_test.py0000644000175100001660000007370614764107375024242 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Test that FakeFilesystem calls work identically to a real filesystem.""" # pylint: disable-all import os import shutil import sys import tempfile import time import unittest from pyfakefs import fake_filesystem, fake_os, fake_open from pyfakefs.tests.test_utils import skip_if_symlink_not_supported def sep(path): """Converts slashes in the path to the architecture's path separator.""" if isinstance(path, str): return path.replace("/", os.sep) return path def _get_errno(raised_error): if raised_error is not None: try: return raised_error.errno except AttributeError: pass class FakeFilesystemVsRealTest(unittest.TestCase): is_windows = sys.platform.startswith("win") fake_base = "C:\\fakefs" if is_windows else "/fakefs" def _paths(self, path): """For a given path, return paths in the real and fake filesystems.""" if not path: return None, None return ( os.path.join(self.real_base, path), os.path.join(self.fake_base, path), ) def _create_test_file(self, file_type, path, contents=None): """Create a dir, file, or link in both the real fs and the fake.""" path = sep(path) self._created_files.append([file_type, path, contents]) real_path, fake_path = self._paths(path) if file_type == "d": os.mkdir(real_path) self.fake_os.mkdir(fake_path) if file_type == "f": fh = open(real_path, "w", encoding="utf8") fh.write(contents or "") fh.close() fh = self.fake_open(fake_path, "w", encoding="utf8") fh.write(contents or "") fh.close() # b for binary file if file_type == "b": fh = open(real_path, "wb") fh.write(contents or "") fh.close() fh = self.fake_open(fake_path, "wb") fh.write(contents or "") fh.close() # l for symlink, h for hard link if file_type in ("l", "h"): contents = sep(contents) real_target, fake_target = (contents, contents) # If it begins with '/', make it relative to the base. You can't go # creating files in / for the real file system. if contents.startswith(os.sep): real_target, fake_target = self._paths(contents[1:]) if file_type == "l": os.symlink(real_target, real_path) self.fake_os.symlink(fake_target, fake_path) elif file_type == "h": os.link(real_target, real_path) self.fake_os.link(fake_target, fake_path) def setUp(self): # Base paths in the real and test file systems. We keep them different # so that missing features in the fake don't fall through to the base # operations and magically succeed. tsname = "fakefs.%s" % time.time() self.cwd = os.getcwd() # Fully expand the base_path - required on OS X. self.real_base = os.path.realpath(os.path.join(tempfile.gettempdir(), tsname)) os.chdir(tempfile.gettempdir()) if os.path.isdir(self.real_base): shutil.rmtree(self.real_base) os.mkdir(self.real_base) # Make sure we can write to the physical testing temp directory. self.assertTrue(os.access(self.real_base, os.W_OK)) self.fake_filesystem = fake_filesystem.FakeFilesystem() self.fake_filesystem.create_dir(self.fake_base) self.fake_os = fake_os.FakeOsModule(self.fake_filesystem) self.fake_open = fake_open.FakeFileOpen(self.fake_filesystem) self._created_files = [] os.chdir(self.real_base) self.fake_os.chdir(self.fake_base) def tearDown(self): # We have to remove all the files from the real FS. Doing the same for # the fake FS is optional, but doing it is an extra sanity check. os.chdir(tempfile.gettempdir()) try: rev_files = self._created_files[:] rev_files.reverse() for info in rev_files: real_path, fake_path = self._paths(info[1]) if info[0] == "d": try: os.rmdir(real_path) except OSError as e: if "Directory not empty" in e: self.fail( "Real path %s not empty: %s : %s" % (real_path, e, os.listdir(real_path)) ) else: raise self.fake_os.rmdir(fake_path) if info[0] == "f" or info[0] == "l": os.remove(real_path) self.fake_os.remove(fake_path) finally: shutil.rmtree(self.real_base) os.chdir(self.cwd) def _compare_behaviors( self, method_name, path, real, fake, method_returns_path=False ): """Invoke an os method in both real and fake contexts and compare results. Invoke a real filesystem method with a path to a real file and invoke a fake filesystem method with a path to a fake file and compare the results. We expect some calls to throw Exceptions, so we catch those and compare them. Args: method_name: Name of method being tested, for use in error messages. path: potential path to a file in the real and fake file systems, passing an empty tuple indicates that no arguments to pass to method. real: built-in system library or method from the built-in system library which takes a path as an arg and returns some value. fake: fake_filesystem object or method from a fake_filesystem class which takes a path as an arg and returns some value. method_returns_path: True if the method returns a path, and thus we must compensate for expected difference between real and fake. Returns: A description of the difference in behavior, or None. """ # pylint: disable=C6403 def _error_class(exc): if exc: if hasattr(exc, "errno"): return f"{exc.__class__.__name__}({exc.errno})" return exc.__class__.__name__ return "None" real_err, real_value = self._get_real_value(method_name, path, real) fake_err, fake_value = self._get_fake_value(method_name, path, fake) method_call = f"{method_name}" method_call += "()" if path == () else "({path})" # We only compare on the error class because the actual error contents # is almost always different because of the file paths. if _error_class(real_err) != _error_class(fake_err): if real_err is None: return "{}: real version returned {}, fake raised {}".format( method_call, real_value, _error_class(fake_err), ) if fake_err is None: return "{}: real version raised {}, fake returned {}".format( method_call, _error_class(real_err), fake_value, ) return "{}: real version raised {}, fake raised {}".format( method_call, _error_class(real_err), _error_class(fake_err), ) real_errno = _get_errno(real_err) fake_errno = _get_errno(fake_err) if real_errno != fake_errno: return "{}({}): both raised {}, real errno {}, fake errno {}".format( method_name, path, _error_class(real_err), real_errno, fake_errno, ) # If the method is supposed to return a full path AND both values # begin with the expected full path, then trim it off. if method_returns_path: if ( real_value and fake_value and real_value.startswith(self.real_base) and fake_value.startswith(self.fake_base) ): real_value = real_value[len(self.real_base) :] fake_value = fake_value[len(self.fake_base) :] if real_value != fake_value: return "{}: real return {}, fake returned {}".format( method_call, real_value, fake_value, ) return None @staticmethod def _get_fake_value(method_name, path, fake): fake_value = None fake_err = None try: fake_method = fake if not callable(fake): fake_method = getattr(fake, method_name) args = [] if path == () else [path] result = fake_method(*args) if isinstance(result, bytes): fake_value = result.decode() else: fake_value = str(result) except Exception as e: # pylint: disable-msg=W0703 fake_err = e return fake_err, fake_value @staticmethod def _get_real_value(method_name, path, real): real_value = None real_err = None # Catching Exception below gives a lint warning, but it's what we need. try: args = [] if path == () else [path] real_method = real if not callable(real): real_method = getattr(real, method_name) result = real_method(*args) if isinstance(result, bytes): real_value = result.decode() else: real_value = str(result) except Exception as e: # pylint: disable-msg=W0703 real_err = e return real_err, real_value def assertOsMethodBehaviorMatches( self, method_name, path, method_returns_path=False ): """Invoke an os method in both real and fake contexts and compare. For a given method name (from the os module) and a path, compare the behavior of the system provided module against the fake_filesystem module. We expect results and/or Exceptions raised to be identical. Args: method_name: Name of method being tested. path: potential path to a file in the real and fake file systems. method_returns_path: True if the method returns a path, and thus we must compensate for expected difference between real and fake. Returns: A description of the difference in behavior, or None. """ path = sep(path) return self._compare_behaviors( method_name, path, os, self.fake_os, method_returns_path ) def diff_open_method_behavior( self, method_name, path, mode, data, method_returns_data=True ): """Invoke an open method in both real and fake contexts and compare. Args: method_name: Name of method being tested. path: potential path to a file in the real and fake file systems. mode: how to open the file. data: any data to pass to the method. method_returns_data: True if a method returns some sort of data. For a given method name (from builtin open) and a path, compare the behavior of the system provided module against the fake_filesystem module. We expect results and/or Exceptions raised to be identical. Returns: A description of the difference in behavior, or None. """ kwargs = {} if "b" not in mode: kwargs["encoding"] = "utf8" with open(path, mode, **kwargs) as real_fh: with self.fake_open(path, mode, **kwargs) as fake_fh: return self._compare_behaviors( method_name, data, real_fh, fake_fh, method_returns_data ) def diff_os_path_method_behavior( self, method_name, path, method_returns_path=False ): """Invoke an os.path method in both real and fake contexts and compare. For a given method name (from the os.path module) and a path, compare the behavior of the system provided module against the fake_filesystem module. We expect results and/or Exceptions raised to be identical. Args: method_name: Name of method being tested. path: potential path to a file in the real and fake file systems. method_returns_path: True if the method returns a path, and thus we must compensate for expected difference between real and fake. Returns: A description of the difference in behavior, or None. """ return self._compare_behaviors( method_name, path, os.path, self.fake_os.path, method_returns_path ) def assertOsPathMethodBehaviorMatches( self, method_name, path, method_returns_path=False ): """Assert that an os.path behaves the same in both real and fake contexts. Wraps DiffOsPathMethodBehavior, raising AssertionError if any differences are reported. Args: method_name: Name of method being tested. path: potential path to a file in the real and fake file systems. method_returns_path: True if the method returns a path, and thus we must compensate for expected difference between real and fake. Raises: AssertionError if there is any difference in behavior. """ path = sep(path) diff = self.diff_os_path_method_behavior(method_name, path, method_returns_path) if diff: self.fail(diff) def assertAllOsBehaviorsMatch(self, path, excludes=None): excludes = excludes or [] path = sep(path) os_method_names = ["readlink"] os_method_names_no_args = ["getcwd"] os_path_method_names = { "isabs", "isdir", "islink", "lexists", "isfile", "exists", } - set(excludes) wrapped_methods = [ ["access", self._access_real, self._access_fake], [ "stat.size", lambda p: self._stat_result_real(p, "st_size", False), lambda p: self._stat_result_fake(p, "st_size", False), ], [ "stat.mode", lambda p: self._stat_result_real(p, "st_mode"), lambda p: self._stat_result_fake(p, "st_mode"), ], ["lstat.size", self._lstat_size_real, self._lstat_size_fake], ] wrapped_methods = [m for m in wrapped_methods if m[0] not in excludes] differences = [] for method_name in os_method_names: diff = self.assertOsMethodBehaviorMatches(method_name, path) if diff: differences.append(diff) for method_name in os_method_names_no_args: diff = self.assertOsMethodBehaviorMatches( method_name, (), method_returns_path=True ) if diff: differences.append(diff) for method_name in os_path_method_names: diff = self.diff_os_path_method_behavior(method_name, path) if diff: differences.append(diff) for m in wrapped_methods: diff = self._compare_behaviors(m[0], path, m[1], m[2]) if diff: differences.append(diff) if differences: self.fail( "Behaviors do not match for %s:\n %s" % (path, "\n ".join(differences)) ) def assertFileHandleBehaviorsMatch(self, path, mode, data): path = sep(path) write_method_names = ["write", "writelines"] read_method_names = ["read", "readlines"] other_method_names = ["truncate", "flush", "close"] differences = [] for method_name in write_method_names: diff = self.diff_open_method_behavior(method_name, path, mode, data) if diff: differences.append(diff) for method_name in read_method_names + other_method_names: diff = self.diff_open_method_behavior(method_name, path, mode, ()) if diff: differences.append(diff) if differences: self.fail( "Behaviors do not match for %s:\n %s" % (path, "\n ".join(differences)) ) def assertFileHandleOpenBehaviorsMatch(self, *args, **kwargs): """Compare open() function invocation between real and fake. Runs open(*args, **kwargs) on both real and fake. Args: *args: args to pass through to open() **kwargs: kwargs to pass through to open(). Returns: None. Raises: AssertionError if underlying open() behavior differs from fake. """ real_err = None fake_err = None try: with open(*args, **kwargs): pass except Exception as e: # pylint: disable-msg=W0703 real_err = e try: with self.fake_open(*args, **kwargs): pass except Exception as e: # pylint: disable-msg=W0703 fake_err = e # default equal in case one is None and other is not. is_exception_equal = real_err == fake_err if real_err and fake_err: # exception __eq__ doesn't evaluate equal ever, thus manual check. is_exception_equal = ( type(real_err) is type(fake_err) and real_err.args == fake_err.args ) if not is_exception_equal: msg = "Behaviors don't match on open with args {} & kwargs {}.\n".format( args, kwargs, ) real_err_msg = "Real open results in: %s\n" % repr(real_err) fake_err_msg = "Fake open results in: %s\n" % repr(fake_err) self.fail(msg + real_err_msg + fake_err_msg) # Helpers for checks which are not straight method calls. @staticmethod def _access_real(path): return os.access(path, os.R_OK) def _access_fake(self, path): return self.fake_os.access(path, os.R_OK) def _stat_result_real(self, path, prop, support_dir=True): real_path, unused_fake_path = self._paths(path) # fake_filesystem.py does not implement stat().st_size for directories if not support_dir and os.path.isdir(real_path): return None return getattr(os.stat(real_path), prop) def _stat_result_fake(self, path, prop, support_dir=True): _, fake_path = self._paths(path) if not support_dir and self.fake_os.path.isdir(fake_path): return None return getattr(self.fake_os.stat(fake_path), prop) def _lstat_size_real(self, path): real_path, unused_fake_path = self._paths(path) if os.path.isdir(real_path): return None size = os.lstat(real_path).st_size # Account for the difference in the lengths of the absolute paths. if os.path.islink(real_path): if os.readlink(real_path).startswith(os.sep): size -= len(self.real_base) return size def _lstat_size_fake(self, path): unused_real_path, fake_path = self._paths(path) # size = 0 if self.fake_os.path.isdir(fake_path): return None size = self.fake_os.lstat(fake_path).st_size # Account for the difference in the lengths of the absolute paths. if self.fake_os.path.islink(fake_path): if self.fake_os.readlink(fake_path).startswith(os.sep): size -= len(self.fake_base) return size def test_isabs(self): # We do not have to create any files for isabs. self.assertOsPathMethodBehaviorMatches("isabs", None) self.assertOsPathMethodBehaviorMatches("isabs", "") self.assertOsPathMethodBehaviorMatches("isabs", "/") self.assertOsPathMethodBehaviorMatches("isabs", "/a") self.assertOsPathMethodBehaviorMatches("isabs", "a") def test_none_path(self): self.assertAllOsBehaviorsMatch(None) def test_empty_path(self): self.assertAllOsBehaviorsMatch("") def test_root_path(self): self.assertAllOsBehaviorsMatch("/", excludes=["stat.mode"]) def test_non_existent_file(self): self.assertAllOsBehaviorsMatch("foo") def test_empty_file(self): self._create_test_file("f", "aFile") self.assertAllOsBehaviorsMatch("aFile") def test_file_with_contents(self): self._create_test_file("f", "aFile", "some contents") self.assertAllOsBehaviorsMatch("aFile") def test_file_with_binary_contents(self): self._create_test_file("b", "aFile", b"some contents") self.assertAllOsBehaviorsMatch("aFile") def test_sym_link_to_empty_file(self): skip_if_symlink_not_supported() self._create_test_file("f", "aFile") self._create_test_file("l", "link_to_empty", "aFile") self.assertAllOsBehaviorsMatch("link_to_empty") def test_hard_link_to_empty_file(self): skip_if_symlink_not_supported() self._create_test_file("f", "aFile") self._create_test_file("h", "link_to_empty", "aFile") self.assertAllOsBehaviorsMatch("link_to_empty") def test_sym_link_to_real_file(self): skip_if_symlink_not_supported() self._create_test_file("f", "aFile", "some contents") self._create_test_file("l", "link_to_file", "aFile") self.assertAllOsBehaviorsMatch("link_to_file") def test_hard_link_to_real_file(self): skip_if_symlink_not_supported() self._create_test_file("f", "aFile", "some contents") self._create_test_file("h", "link_to_file", "aFile") self.assertAllOsBehaviorsMatch("link_to_file") def test_broken_sym_link(self): skip_if_symlink_not_supported() self._create_test_file("l", "broken_link", "broken") self.assertAllOsBehaviorsMatch("broken_link") def test_file_in_a_folder(self): self._create_test_file("d", "a") self._create_test_file("d", "a/b") self._create_test_file("f", "a/b/file", "contents") self.assertAllOsBehaviorsMatch("a/b/file") def test_absolute_sym_link_to_folder(self): skip_if_symlink_not_supported() self._create_test_file("d", "a") self._create_test_file("d", "a/b") self._create_test_file("f", "a/b/file", "contents") self._create_test_file("l", "a/link", "/a/b") self.assertAllOsBehaviorsMatch("a/link/file") def test_link_to_folder_after_chdir(self): skip_if_symlink_not_supported() self._create_test_file("d", "a") self._create_test_file("d", "a/b") self._create_test_file("f", "a/b/file", "contents") self._create_test_file("l", "a/link", "/a/b") real_dir, fake_dir = self._paths("a/b") os.chdir(real_dir) self.fake_os.chdir(fake_dir) self.assertAllOsBehaviorsMatch("file") def test_relative_sym_link_to_folder(self): skip_if_symlink_not_supported() self._create_test_file("d", "a") self._create_test_file("d", "a/b") self._create_test_file("f", "a/b/file", "contents") self._create_test_file("l", "a/link", "b") self.assertAllOsBehaviorsMatch("a/link/file") def test_sym_link_to_parent(self): skip_if_symlink_not_supported() # Soft links on HFS+ / OS X behave differently. if sys.platform != "darwin": self._create_test_file("d", "a") self._create_test_file("d", "a/b") self._create_test_file("l", "a/b/c", "..") self.assertAllOsBehaviorsMatch("a/b/c") def test_path_through_sym_link_to_parent(self): skip_if_symlink_not_supported() self._create_test_file("d", "a") self._create_test_file("f", "a/target", "contents") self._create_test_file("d", "a/b") self._create_test_file("l", "a/b/c", "..") self.assertAllOsBehaviorsMatch("a/b/c/target") def test_sym_link_to_sibling_directory(self): skip_if_symlink_not_supported() self._create_test_file("d", "a") self._create_test_file("d", "a/b") self._create_test_file("d", "a/sibling_of_b") self._create_test_file("f", "a/sibling_of_b/target", "contents") self._create_test_file("l", "a/b/c", "../sibling_of_b") self.assertAllOsBehaviorsMatch("a/b/c/target") def test_sym_link_to_sibling_directory_non_existent_file(self): skip_if_symlink_not_supported() self._create_test_file("d", "a") self._create_test_file("d", "a/b") self._create_test_file("d", "a/sibling_of_b") self._create_test_file("f", "a/sibling_of_b/target", "contents") self._create_test_file("l", "a/b/c", "../sibling_of_b") self.assertAllOsBehaviorsMatch("a/b/c/file_does_not_exist") def test_broken_sym_link_to_sibling_directory(self): skip_if_symlink_not_supported() self._create_test_file("d", "a") self._create_test_file("d", "a/b") self._create_test_file("d", "a/sibling_of_b") self._create_test_file("f", "a/sibling_of_b/target", "contents") self._create_test_file("l", "a/b/c", "../broken_sibling_of_b") self.assertAllOsBehaviorsMatch("a/b/c/target") def test_relative_path(self): self._create_test_file("d", "a") self._create_test_file("d", "a/b") self._create_test_file("d", "a/sibling_of_b") self._create_test_file("f", "a/sibling_of_b/target", "contents") self.assertAllOsBehaviorsMatch("a/b/../sibling_of_b/target") def test_broken_relative_path(self): self._create_test_file("d", "a") self._create_test_file("d", "a/b") self._create_test_file("d", "a/sibling_of_b") self._create_test_file("f", "a/sibling_of_b/target", "contents") self.assertAllOsBehaviorsMatch("a/b/../broken/target") def test_bad_relative_path(self): self._create_test_file("d", "a") self._create_test_file("f", "a/target", "contents") self._create_test_file("d", "a/b") self._create_test_file("d", "a/sibling_of_b") self._create_test_file("f", "a/sibling_of_b/target", "contents") self.assertAllOsBehaviorsMatch("a/b/../broken/../target") def test_getmtime_nonexistent_path(self): self.assertOsPathMethodBehaviorMatches("getmtime", "no/such/path") def test_builtin_open_modes(self): self._create_test_file("f", "read", "some contents") self._create_test_file("f", "write", "some contents") self._create_test_file("f", "append", "some contents") self.assertFileHandleBehaviorsMatch("read", "r", "other contents") self.assertFileHandleBehaviorsMatch("write", "w", "other contents") self.assertFileHandleBehaviorsMatch("append", "a", "other contents") self._create_test_file("f", "readplus", "some contents") self._create_test_file("f", "writeplus", "some contents") self.assertFileHandleBehaviorsMatch("readplus", "r+", "other contents") self.assertFileHandleBehaviorsMatch("writeplus", "w+", "other contents") self._create_test_file("b", "binaryread", b"some contents") self._create_test_file("b", "binarywrite", b"some contents") self._create_test_file("b", "binaryappend", b"some contents") self.assertFileHandleBehaviorsMatch("binaryread", "rb", b"other contents") self.assertFileHandleBehaviorsMatch("binarywrite", "wb", b"other contents") self.assertFileHandleBehaviorsMatch("binaryappend", "ab", b"other contents") self.assertFileHandleBehaviorsMatch("read", "rb", "other contents") self.assertFileHandleBehaviorsMatch("write", "wb", "other contents") self.assertFileHandleBehaviorsMatch("append", "ab", "other contents") # binary cannot have encoding self.assertFileHandleOpenBehaviorsMatch("read", "rb", encoding="enc") self.assertFileHandleOpenBehaviorsMatch("write", mode="wb", encoding="enc") self.assertFileHandleOpenBehaviorsMatch("append", "ab", encoding="enc") # text can have encoding self.assertFileHandleOpenBehaviorsMatch("read", "r", encoding="utf-8") self.assertFileHandleOpenBehaviorsMatch("write", "w", encoding="utf-8") self.assertFileHandleOpenBehaviorsMatch("append", "a", encoding="utf-8") def test_directory_permissions(self): self._create_test_file("d", "a") self._create_test_file("f", "a/write") self._create_test_file("d", "b") self._create_test_file("f", "b/write") self._create_test_file("d", "b/stat_dir") for path, mode in [("b", 0o555), ("b/stat_dir", 0o111)]: real_path, fake_path = self._paths(path) os.chmod(real_path, mode) self.fake_os.chmod(fake_path, mode) try: self.assertFileHandleOpenBehaviorsMatch("a/write", "w") self.assertFileHandleOpenBehaviorsMatch("b/write", "w") self.assertAllOsBehaviorsMatch("b/stat_dir") finally: for path in ["b", "b/stat_dir"]: real_path, fake_path = self._paths(path) os.chmod(real_path, 0o777) self.fake_os.chmod(fake_path, 0o777) def main(_): unittest.main() if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fake_legacy_modules_test.py0000644000175100001660000000724014764107375023325 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import unittest from pyfakefs.fake_filesystem_unittest import TestCase from pyfakefs.fake_legacy_modules import FakeScanDirModule, FakePathlib2Module from pyfakefs.legacy_packages import pathlib2, scandir from pyfakefs.tests.fake_os_test import FakeScandirTest from pyfakefs.tests.fake_pathlib_test import ( FakePathlibInitializationTest, FakePathlibPathFileOperationTest, FakePathlibFileObjectPropertyTest, FakePathlibUsageInOsFunctionsTest, ) class DeprecationWarningTest(TestCase): def setUp(self): self.setUpPyfakefs() @unittest.skipIf(scandir is None, "The scandir package is not installed") def test_scandir_warning(self): FakeScanDirModule.has_warned = False with self.assertWarns(DeprecationWarning): scandir.scandir("/") @unittest.skipIf(pathlib2 is None, "The pathlib2 package is not installed") def test_pathlib2_warning(self): FakePathlib2Module.has_warned = False with self.assertWarns(DeprecationWarning): pathlib2.Path("/foo") @unittest.skipIf(scandir is None, "The scandir package is not installed") class FakeScandirPackageTest(FakeScandirTest): def used_scandir(self): import pyfakefs.fake_legacy_modules def fake_scan_dir(p): return pyfakefs.fake_legacy_modules.FakeScanDirModule( self.filesystem ).scandir(p) return fake_scan_dir def test_path_like(self): unittest.skip("Path-like objects not available in scandir package") class RealScandirPackageTest(FakeScandirPackageTest): def used_scandir(self): from scandir import scandir return scandir def use_real_fs(self): return True @unittest.skipIf(pathlib2 is None, "The pathlib2 package is not installed") class FakePathlib2InitializationTest(FakePathlibInitializationTest): def used_pathlib(self): return pathlib2 class RealPathlib2InitializationTest(FakePathlib2InitializationTest): def use_real_fs(self): return True @unittest.skipIf(pathlib2 is None, "The pathlib2 package is not installed") class FakePathlib2FileObjectPropertyTest(FakePathlibFileObjectPropertyTest): def used_pathlib(self): return pathlib2 class RealPathlib2FileObjectPropertyTest(FakePathlib2FileObjectPropertyTest): def use_real_fs(self): return True @unittest.skipIf(pathlib2 is None, "The pathlib2 package is not installed") class FakePathlib2PathFileOperationTest(FakePathlibPathFileOperationTest): def used_pathlib(self): return pathlib2 def test_is_junction(self): unittest.skip("is_junction not available in pathlib2") class RealPathlibPath2FileOperationTest(FakePathlib2PathFileOperationTest): def use_real_fs(self): return True @unittest.skipIf(pathlib2 is None, "The pathlib2 package is not installed") class FakePathlib2UsageInOsFunctionsTest(FakePathlibUsageInOsFunctionsTest): def used_pathlib(self): return pathlib2 class RealPathlib2UsageInOsFunctionsTest(FakePathlib2UsageInOsFunctionsTest): def use_real_fs(self): return True if __name__ == "__main__": unittest.main(verbosity=2) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fake_open_test.py0000644000175100001660000025425414764107375021303 0ustar00runnerdocker# # Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unit tests for fake_open.FakeOsModule.""" import errno import io import os import stat import sys import time import unittest from pyfakefs import fake_filesystem, helpers from pyfakefs.helpers import is_root, IS_PYPY, get_locale_encoding from pyfakefs.fake_io import FakeIoModule from pyfakefs.fake_filesystem_unittest import PatchMode, Patcher from pyfakefs.tests.skipped_pathlib import read_open from pyfakefs.tests.test_utils import RealFsTestCase, skip_if_symlink_not_supported class FakeFileOpenTestBase(RealFsTestCase): def setUp(self): super().setUp() if self.use_real_fs(): self.open = io.open else: self.fake_io_module = FakeIoModule(self.filesystem) self.open = self.fake_io_module.open def path_separator(self): return "!" class FakeFileOpenTest(FakeFileOpenTestBase): def setUp(self): super().setUp() self.orig_time = time.time def tearDown(self): super().tearDown() time.time = self.orig_time def test_open_no_parent_dir(self): """Expect raise when opening a file in a missing directory.""" file_path = self.make_path("foo", "bar.txt") self.assert_raises_os_error(errno.ENOENT, self.open, file_path, "w") def test_delete_on_close(self): self.skip_real_fs() file_dir = "boo" file_path = "boo!far" self.os.mkdir(file_dir) self.open = fake_filesystem.FakeFileOpen(self.filesystem, delete_on_close=True) with self.open(file_path, "w", encoding="utf8"): self.assertTrue(self.filesystem.exists(file_path)) self.assertFalse(self.filesystem.exists(file_path)) def test_no_delete_on_close_by_default(self): file_path = self.make_path("czar") with self.open(file_path, "w", encoding="utf8"): self.assertTrue(self.os.path.exists(file_path)) self.assertTrue(self.os.path.exists(file_path)) def test_compatibility_of_with_statement(self): self.skip_real_fs() self.open = fake_filesystem.FakeFileOpen(self.filesystem, delete_on_close=True) file_path = "foo" self.assertFalse(self.os.path.exists(file_path)) with self.open(file_path, "w", encoding="utf8"): self.assertTrue(self.os.path.exists(file_path)) # After the 'with' statement, the close() method should have been # called. self.assertFalse(self.os.path.exists(file_path)) def test_unicode_contents(self): file_path = self.make_path("foo") # note that this will work only if the string can be represented # by the locale preferred encoding - which under Windows is # usually not UTF-8, but something like Latin1, depending on the locale text_fractions = "Ümläüts" try: with self.open(file_path, "w", encoding=get_locale_encoding()) as f: f.write(text_fractions) except UnicodeEncodeError: # see https://github.com/pytest-dev/pyfakefs/issues/623 self.skipTest("This test does not work with an ASCII locale") with self.open(file_path, encoding=get_locale_encoding()) as f: contents = f.read() self.assertEqual(contents, text_fractions) def test_byte_contents(self): file_path = self.make_path("foo") byte_fractions = b"\xe2\x85\x93 \xe2\x85\x94 \xe2\x85\x95 \xe2\x85\x96" with self.open(file_path, "wb") as f: f.write(byte_fractions) # the encoding has to be specified, otherwise the locale default # is used which can be different on different systems with self.open(file_path, encoding="utf-8") as f: contents = f.read() self.assertEqual(contents, byte_fractions.decode("utf-8")) def test_write_str_read_bytes(self): file_path = self.make_path("foo") str_contents = "Äsgül" try: with self.open(file_path, "w", encoding=get_locale_encoding()) as f: f.write(str_contents) with self.open(file_path, "rb") as f: contents = f.read() self.assertEqual(str_contents, contents.decode(get_locale_encoding())) except UnicodeError: # see https://github.com/pytest-dev/pyfakefs/issues/623 self.skipTest("This test does not work with an ASCII locale") def test_open_valid_file(self): contents = [ "I am he as\n", "you are he as\n", "you are me and\n", "we are all together\n", ] file_path = self.make_path("bar.txt") self.create_file(file_path, contents="".join(contents)) with self.open(file_path, encoding="utf8") as fake_file: self.assertEqual(contents, fake_file.readlines()) def test_open_valid_args(self): contents = [ "Bang bang Maxwell's silver hammer\n", "Came down on her head", ] file_path = self.make_path("abbey_road", "maxwell") self.create_file(file_path, contents="".join(contents)) with self.open(file_path, encoding="utf8", buffering=1) as f: self.assertEqual(contents, f.readlines()) with self.open( file_path, encoding="utf8", buffering=1, errors="strict", newline="\n", opener=None, ) as f: expected_contents = [ contents[0][:-1] + self.os.linesep, contents[1], ] self.assertEqual(expected_contents, f.readlines()) def test_open_valid_file_with_cwd(self): contents = [ "I am he as\n", "you are he as\n", "you are me and\n", "we are all together\n", ] file_path = self.make_path("bar.txt") self.create_file(file_path, contents="".join(contents)) self.os.chdir(self.base_path) with self.open(file_path, encoding="utf8") as f: self.assertEqual(contents, f.readlines()) def test_iterate_over_file(self): contents = [ "Bang bang Maxwell's silver hammer", "Came down on her head", ] file_path = self.make_path("abbey_road", "maxwell") self.create_file(file_path, contents="\n".join(contents)) with self.open(file_path, encoding="utf8") as fake_file: result = [line.rstrip() for line in fake_file] self.assertEqual(contents, result) def test_next_over_file(self): contents = ["Live long\n", "and prosper\n"] result = [] file_path = self.make_path("foo.txt") self.create_file(file_path, contents="".join(contents)) with self.open(file_path, encoding="utf8") as fake_file: result.append(next(fake_file)) result.append(next(fake_file)) self.assertEqual(contents, result) def test_open_directory_error(self): directory_path = self.make_path("foo") self.os.mkdir(directory_path) if self.is_windows: self.assert_raises_os_error( errno.EACCES, self.open.__call__, directory_path ) else: self.assert_raises_os_error( errno.EISDIR, self.open.__call__, directory_path ) def test_create_file_with_write(self): contents = [ "Here comes the sun, little darlin'", "Here comes the sun, and I say,", "It's alright", ] file_dir = self.make_path("abbey_road") file_path = self.os.path.join(file_dir, "here_comes_the_sun") self.os.mkdir(file_dir) with self.open(file_path, "w", encoding="utf8") as fake_file: for line in contents: fake_file.write(line + "\n") with self.open(file_path, encoding="utf8") as fake_file: result = [line.rstrip() for line in fake_file] self.assertEqual(contents, result) def test_create_file_with_append(self): contents = [ "Here comes the sun, little darlin'", "Here comes the sun, and I say,", "It's alright", ] file_dir = self.make_path("abbey_road") file_path = self.os.path.join(file_dir, "here_comes_the_sun") self.os.mkdir(file_dir) with self.open(file_path, "a", encoding="utf8") as fake_file: for line in contents: fake_file.write(line + "\n") with self.open(file_path, encoding="utf8") as fake_file: result = [line.rstrip() for line in fake_file] self.assertEqual(contents, result) def test_exclusive_create_file_failure(self): skip_if_symlink_not_supported() file_path = self.make_path("bar") self.create_file(file_path) self.assert_raises_os_error(errno.EEXIST, self.open, file_path, "x") self.assert_raises_os_error(errno.EEXIST, self.open, file_path, "xb") def test_exclusive_create_file(self): file_dir = self.make_path("foo") file_path = self.os.path.join(file_dir, "bar") self.os.mkdir(file_dir) contents = "String contents" with self.open(file_path, "x", encoding="utf8") as fake_file: fake_file.write(contents) with self.open(file_path, encoding="utf8") as fake_file: self.assertEqual(contents, fake_file.read()) def test_exclusive_create_binary_file(self): file_dir = self.make_path("foo") file_path = self.os.path.join(file_dir, "bar") self.os.mkdir(file_dir) contents = b"Binary contents" with self.open(file_path, "xb") as fake_file: fake_file.write(contents) with self.open(file_path, "rb") as fake_file: self.assertEqual(contents, fake_file.read()) def test_overwrite_existing_file(self): file_path = self.make_path("overwrite") self.create_file(file_path, contents="To disappear") new_contents = [ "Only these lines", "should be in the file.", ] with self.open(file_path, "w", encoding="utf8") as fake_file: for line in new_contents: fake_file.write(line + "\n") with self.open(file_path, encoding="utf8") as fake_file: result = [line.rstrip() for line in fake_file] self.assertEqual(new_contents, result) def test_append_existing_file(self): file_path = self.make_path("appendfile") contents = [ "Contents of original fileAppended contents", ] self.create_file(file_path, contents=contents[0]) with self.open(file_path, "a", encoding="utf8") as fake_file: for line in contents[1:]: fake_file.write(line + "\n") with self.open(file_path, encoding="utf8") as fake_file: result = [line.rstrip() for line in fake_file] self.assertEqual(contents, result) def test_open_with_wplus(self): # set up file_path = self.make_path("wplus_file") self.create_file(file_path, contents="old contents") self.assertTrue(self.os.path.exists(file_path)) with self.open(file_path, "r", encoding="utf8") as fake_file: self.assertEqual("old contents", fake_file.read()) # actual tests with self.open(file_path, "w+", encoding="utf8") as fake_file: fake_file.write("new contents") fake_file.seek(0) self.assertTrue("new contents", fake_file.read()) def test_open_with_wplus_truncation(self): # set up file_path = self.make_path("wplus_file") self.create_file(file_path, contents="old contents") self.assertTrue(self.os.path.exists(file_path)) with self.open(file_path, "r", encoding="utf8") as fake_file: self.assertEqual("old contents", fake_file.read()) # actual tests with self.open(file_path, "w+", encoding="utf8") as fake_file: fake_file.seek(0) self.assertEqual("", fake_file.read()) def test_open_with_append_flag(self): contents = [ "I am he as\n", "you are he as\n", "you are me and\n", "we are all together\n", ] additional_contents = ["These new lines\n", "like you a lot.\n"] file_path = self.make_path("appendfile") self.create_file(file_path, contents="".join(contents)) with self.open(file_path, "a", encoding="utf8") as fake_file: with self.assertRaises(io.UnsupportedOperation): fake_file.read(0) with self.assertRaises(io.UnsupportedOperation): fake_file.readline() expected_len = len("".join(contents)) expected_len += len(contents) * (len(self.os.linesep) - 1) self.assertEqual(expected_len, fake_file.tell()) fake_file.seek(0) self.assertEqual(0, fake_file.tell()) fake_file.writelines(additional_contents) with self.open(file_path, encoding="utf8") as fake_file: self.assertEqual(contents + additional_contents, fake_file.readlines()) def check_append_with_aplus(self): file_path = self.make_path("aplus_file") self.create_file(file_path, contents="old contents") self.assertTrue(self.os.path.exists(file_path)) with self.open(file_path, "r", encoding="utf8") as fake_file: self.assertEqual("old contents", fake_file.read()) if self.filesystem: # need to recreate FakeFileOpen for OS specific initialization self.open = fake_filesystem.FakeFileOpen( self.filesystem, delete_on_close=True ) with self.open(file_path, "a+", encoding="utf8") as fake_file: self.assertEqual(12, fake_file.tell()) fake_file.write("new contents") self.assertEqual(24, fake_file.tell()) fake_file.seek(0) self.assertEqual("old contentsnew contents", fake_file.read()) def test_append_with_aplus_mac_os(self): self.check_macos_only() self.check_append_with_aplus() def test_append_with_aplus_linux_windows(self): self.check_linux_and_windows() self.check_append_with_aplus() def test_append_with_aplus_read_with_loop(self): # set up file_path = self.make_path("aplus_file") self.create_file(file_path, contents="old contents") self.assertTrue(self.os.path.exists(file_path)) with self.open(file_path, "r", encoding="utf8") as fake_file: self.assertEqual("old contents", fake_file.read()) # actual tests with self.open(file_path, "a+", encoding="utf8") as fake_file: fake_file.seek(0) fake_file.write("new contents") fake_file.seek(0) for line in fake_file: self.assertEqual("old contentsnew contents", line) def test_read_empty_file_with_aplus(self): file_path = self.make_path("aplus_file") with self.open(file_path, "a+", encoding="utf8") as fake_file: self.assertEqual("", fake_file.read()) def test_read_with_rplus(self): # set up file_path = self.make_path("rplus_file") self.create_file(file_path, contents="old contents here") self.assertTrue(self.os.path.exists(file_path)) with self.open(file_path, "r", encoding="utf8") as fake_file: self.assertEqual("old contents here", fake_file.read()) # actual tests with self.open(file_path, "r+", encoding="utf8") as fake_file: self.assertEqual("old contents here", fake_file.read()) fake_file.seek(0) fake_file.write("new contents") fake_file.seek(0) self.assertEqual("new contents here", fake_file.read()) def create_with_permission(self, file_path, perm_bits): self.create_file(file_path) self.os.chmod(file_path, perm_bits) if perm_bits & helpers.PERM_READ: st = self.os.stat(file_path) self.assert_mode_equal(perm_bits, st.st_mode) self.assertTrue(st.st_mode & stat.S_IFREG) self.assertFalse(st.st_mode & stat.S_IFDIR) def test_open_flags700(self): # set up self.check_posix_only() file_path = self.make_path("target_file") self.create_with_permission(file_path, 0o700) # actual tests self.open(file_path, "r", encoding="utf8").close() self.open(file_path, "w", encoding="utf8").close() self.open(file_path, "w+", encoding="utf8").close() with self.assertRaises(ValueError): self.open(file_path, "INV", encoding="utf8") def test_open_flags400(self): # set up self.check_posix_only() file_path = self.make_path("target_file") self.create_with_permission(file_path, 0o400) # actual tests self.open(file_path, "r", encoding="utf8").close() if not is_root(): self.assert_raises_os_error(errno.EACCES, self.open, file_path, "w") self.assert_raises_os_error(errno.EACCES, self.open, file_path, "w+") else: self.open(file_path, "w", encoding="utf8").close() self.open(file_path, "w+", encoding="utf8").close() def test_open_flags200(self): # set up self.check_posix_only() file_path = self.make_path("target_file") self.create_with_permission(file_path, 0o200) # actual tests self.open(file_path, "w", encoding="utf8").close() if not is_root(): with self.assertRaises(OSError): self.open(file_path, "r", encoding="utf8") with self.assertRaises(OSError): self.open(file_path, "w+", encoding="utf8") else: self.open(file_path, "r", encoding="utf8").close() self.open(file_path, "w+", encoding="utf8").close() def test_open_flags100(self): # set up self.check_posix_only() file_path = self.make_path("target_file") self.create_with_permission(file_path, 0o100) # actual tests if not is_root(): with self.assertRaises(OSError): self.open(file_path, "r", encoding="utf8") with self.assertRaises(OSError): self.open(file_path, "w", encoding="utf8") with self.assertRaises(OSError): self.open(file_path, "w+", encoding="utf8") else: self.open(file_path, "r", encoding="utf8").close() self.open(file_path, "w", encoding="utf8").close() self.open(file_path, "w+", encoding="utf8").close() def test_follow_link_read(self): skip_if_symlink_not_supported() link_path = self.make_path("foo", "bar", "baz") target = self.make_path("tarJAY") target_contents = "real baz contents" self.create_file(target, contents=target_contents) self.create_symlink(link_path, target) self.assert_equal_paths(target, self.os.readlink(link_path)) fh = self.open(link_path, "r", encoding="utf8") got_contents = fh.read() fh.close() self.assertEqual(target_contents, got_contents) def test_follow_link_write(self): skip_if_symlink_not_supported() link_path = self.make_path("foo", "bar", "TBD") target = self.make_path("tarJAY") target_contents = "real baz contents" self.create_symlink(link_path, target) self.assertFalse(self.os.path.exists(target)) with self.open(link_path, "w", encoding="utf8") as fh: fh.write(target_contents) with self.open(target, "r", encoding="utf8") as fh: got_contents = fh.read() self.assertEqual(target_contents, got_contents) def test_follow_intra_path_link_write(self): # Test a link in the middle of of a file path. skip_if_symlink_not_supported() link_path = self.os.path.join( self.base_path, "foo", "build", "local_machine", "output", "1" ) target = self.make_path("tmp", "output", "1") self.create_dir(self.make_path("tmp", "output")) self.create_symlink( self.os.path.join(self.base_path, "foo", "build", "local_machine"), self.make_path("tmp"), ) self.assertFalse(self.os.path.exists(link_path)) self.assertFalse(self.os.path.exists(target)) target_contents = "real baz contents" with self.open(link_path, "w", encoding="utf8") as fh: fh.write(target_contents) with self.open(target, "r", encoding="utf8") as fh: got_contents = fh.read() self.assertEqual(target_contents, got_contents) def test_open_raises_on_symlink_loop(self): # Regression test for #274 self.check_posix_only() file_dir = self.make_path("foo") self.os.mkdir(file_dir) file_path = self.os.path.join(file_dir, "baz") self.os.symlink(file_path, file_path) self.assert_raises_os_error(errno.ELOOP, self.open, file_path) def test_file_descriptors_for_different_files(self): first_path = self.make_path("some_file1") self.create_file(first_path, contents="contents here1") second_path = self.make_path("some_file2") self.create_file(second_path, contents="contents here2") third_path = self.make_path("some_file3") self.create_file(third_path, contents="contents here3") with self.open(first_path, encoding="utf8") as fake_file1: with self.open(second_path, encoding="utf8") as fake_file2: with self.open(third_path, encoding="utf8") as fake_file3: fileno2 = fake_file2.fileno() self.assertGreater(fileno2, fake_file1.fileno()) self.assertGreater(fake_file3.fileno(), fileno2) def test_file_descriptors_for_the_same_file_are_different(self): first_path = self.make_path("some_file1") self.create_file(first_path, contents="contents here1") second_path = self.make_path("some_file2") self.create_file(second_path, contents="contents here2") with self.open(first_path, encoding="utf8") as fake_file1: with self.open(second_path, encoding="utf8") as fake_file2: with self.open(first_path, encoding="utf8") as fake_file1a: fileno2 = fake_file2.fileno() self.assertNotEqual(fileno2, fake_file1.fileno()) self.assertNotEqual(fake_file1a.fileno(), fileno2) def test_reused_file_descriptors_do_not_affect_others(self): first_path = self.make_path("some_file1") self.create_file(first_path, contents="contents here1") second_path = self.make_path("some_file2") self.create_file(second_path, contents="contents here2") third_path = self.make_path("some_file3") self.create_file(third_path, contents="contents here3") with self.open(first_path, "r", encoding="utf8") as fake_file1: with self.open(second_path, "r", encoding="utf8") as fake_file2: fake_file3 = self.open(third_path, "r", encoding="utf8") fake_file1a = self.open(first_path, "r", encoding="utf8") fileno1 = fake_file1.fileno() fileno2 = fake_file2.fileno() fileno3 = fake_file3.fileno() fileno4 = fake_file1a.fileno() with self.open(second_path, "r", encoding="utf8") as fake_file2: with self.open(first_path, "r", encoding="utf8") as fake_file1b: self.assertEqual(fileno1, fake_file2.fileno()) self.assertEqual(fileno2, fake_file1b.fileno()) self.assertEqual(fileno3, fake_file3.fileno()) self.assertEqual(fileno4, fake_file1a.fileno()) fake_file3.close() fake_file1a.close() def test_intertwined_read_write(self): file_path = self.make_path("some_file") self.create_file(file_path) with self.open(file_path, "a", encoding="utf8") as writer: with self.open(file_path, "r", encoding="utf8") as reader: writes = [ "hello", "world\n", "somewhere\nover", "the\n", "rainbow", ] reads = [] # when writes are flushes, they are piped to the reader for write in writes: writer.write(write) writer.flush() reads.append(reader.read()) reader.flush() self.assertEqual(writes, reads) writes = ["nothing", "to\nsee", "here"] reads = [] # when writes are not flushed, the reader doesn't read # anything new for write in writes: writer.write(write) reads.append(reader.read()) self.assertEqual(["" for _ in writes], reads) def test_intertwined_read_write_python3_str(self): file_path = self.make_path("some_file") self.create_file(file_path) with self.open(file_path, "a", encoding="utf-8") as writer: with self.open(file_path, "r", encoding="utf-8") as reader: writes = ["привет", "мир\n", "где-то\nза", "радугой"] reads = [] # when writes are flushes, they are piped to the reader for write in writes: writer.write(write) writer.flush() reads.append(reader.read()) reader.flush() self.assertEqual(writes, reads) writes = ["ничего", "не\nвидно"] reads = [] # when writes are not flushed, the reader doesn't # read anything new for write in writes: writer.write(write) reads.append(reader.read()) self.assertEqual(["" for _ in writes], reads) def test_open_io_errors(self): file_path = self.make_path("some_file") self.create_file(file_path) with self.open(file_path, "a", encoding="utf8") as fh: with self.assertRaises(OSError): fh.read() with self.assertRaises(OSError): fh.readlines() with self.open(file_path, "w", encoding="utf8") as fh: with self.assertRaises(OSError): fh.read() with self.assertRaises(OSError): fh.readlines() with self.open(file_path, "r", encoding="utf8") as fh: with self.assertRaises(OSError): fh.truncate() with self.assertRaises(OSError): fh.write("contents") with self.assertRaises(OSError): fh.writelines(["con", "tents"]) def _iterator_open(mode): with self.open(file_path, mode, encoding="utf8") as f: for _ in f: pass with self.assertRaises(OSError): _iterator_open("w") with self.assertRaises(OSError): _iterator_open("a") def test_open_raises_io_error_if_parent_is_file_posix(self): self.check_posix_only() file_path = self.make_path("bar") self.create_file(file_path) file_path = self.os.path.join(file_path, "baz") self.assert_raises_os_error(errno.ENOTDIR, self.open, file_path, "w") def test_open_raises_io_error_if_parent_is_file_windows(self): self.check_windows_only() file_path = self.make_path("bar") self.create_file(file_path) file_path = self.os.path.join(file_path, "baz") self.assert_raises_os_error(errno.ENOENT, self.open, file_path, "w") def check_open_with_trailing_sep(self, error_nr): # regression test for #362 path = self.make_path("foo") + self.os.path.sep self.assert_raises_os_error(error_nr, self.open, path, "w") def test_open_with_trailing_sep_linux(self): self.check_linux_only() self.check_open_with_trailing_sep(errno.EISDIR) def test_open_with_trailing_sep_macos(self): self.check_macos_only() self.check_open_with_trailing_sep(errno.ENOENT) def test_open_with_trailing_sep_windows(self): self.check_windows_only() self.check_open_with_trailing_sep(errno.EINVAL) def test_can_read_from_block_device(self): self.skip_real_fs() device_path = "device" self.filesystem.create_file(device_path, stat.S_IFBLK | helpers.PERM_ALL) with self.open(device_path, "r", encoding="utf8") as fh: self.assertEqual("", fh.read()) def test_truncate_flushes_contents(self): # Regression test for #285 file_path = self.make_path("baz") self.create_file(file_path) with self.open(file_path, "w", encoding="utf8") as f0: f0.write("test") f0.truncate() self.assertEqual(4, self.os.path.getsize(file_path)) def test_update_other_instances_of_same_file_on_flush(self): # Regression test for #302 file_path = self.make_path("baz") with self.open(file_path, "w", encoding="utf8") as f0: with self.open(file_path, "w", encoding="utf8") as f1: f0.write("test") f0.truncate() f1.flush() self.assertEqual(4, self.os.path.getsize(file_path)) def test_getsize_after_truncate(self): # Regression test for #412 file_path = self.make_path("foo") with self.open(file_path, "a", encoding="utf8") as f: f.write("a") f.seek(0) f.truncate() f.write("b") f.truncate() self.assertEqual(1, self.os.path.getsize(file_path)) self.assertEqual(1, self.os.stat(file_path).st_size) def test_st_size_after_truncate(self): # Regression test for #412 file_path = self.make_path("foo") with self.open(file_path, "a", encoding="utf8") as f: f.write("a") f.truncate() f.write("b") f.truncate() self.assertEqual(2, self.os.stat(file_path).st_size) def test_that_read_over_end_does_not_reset_position(self): # Regression test for #286 file_path = self.make_path("baz") self.create_file(file_path) with self.open(file_path, encoding="utf8") as f0: f0.seek(2) f0.read() self.assertEqual(2, f0.tell()) def test_accessing_closed_file_raises(self): # Regression test for #275, #280 if self.is_pypy: raise unittest.SkipTest("Different exceptions with PyPy") file_path = self.make_path("foo") self.create_file(file_path, contents=b"test") fake_file = self.open(file_path, "r", encoding="utf8") fake_file.close() with self.assertRaises(ValueError): fake_file.read(1) with self.assertRaises(ValueError): fake_file.write("a") with self.assertRaises(ValueError): fake_file.readline() with self.assertRaises(ValueError): fake_file.truncate() with self.assertRaises(ValueError): fake_file.tell() with self.assertRaises(ValueError): fake_file.seek(1) with self.assertRaises(ValueError): fake_file.flush() def test_accessing_open_file_with_another_handle_raises(self): # Regression test for #282 if self.is_pypy: raise unittest.SkipTest("Different exceptions with PyPy") file_path = self.make_path("foo") f0 = self.os.open(file_path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC) fake_file = self.open(file_path, "r", encoding="utf8") fake_file.close() with self.assertRaises(ValueError): fake_file.read(1) with self.assertRaises(ValueError): fake_file.write("a") self.os.close(f0) def test_tell_flushes_under_mac_os(self): # Regression test for #288 self.check_macos_only() file_path = self.make_path("foo") with self.open(file_path, "w", encoding="utf8") as f0: f0.write("test") self.assertEqual(4, f0.tell()) self.assertEqual(4, self.os.path.getsize(file_path)) def test_tell_flushes_in_python3(self): # Regression test for #288 self.check_linux_and_windows() file_path = self.make_path("foo") with self.open(file_path, "w", encoding="utf8") as f0: f0.write("test") self.assertEqual(4, f0.tell()) self.assertEqual(4, self.os.path.getsize(file_path)) def test_read_flushes_under_posix(self): # Regression test for #278 self.check_posix_only() file_path = self.make_path("foo") with self.open(file_path, "a+", encoding="utf8") as f0: f0.write("test") self.assertEqual("", f0.read()) self.assertEqual(4, self.os.path.getsize(file_path)) def test_read_flushes_under_windows_in_python3(self): # Regression test for #278 self.check_windows_only() file_path = self.make_path("foo") with self.open(file_path, "w+", encoding="utf8") as f0: f0.write("test") f0.read() self.assertEqual(4, self.os.path.getsize(file_path)) def test_seek_flushes(self): # Regression test for #290 file_path = self.make_path("foo") with self.open(file_path, "w", encoding="utf8") as f0: f0.write("test") self.assertEqual(0, self.os.path.getsize(file_path)) f0.seek(3) self.assertEqual(4, self.os.path.getsize(file_path)) def test_truncate_flushes(self): # Regression test for #291 file_path = self.make_path("foo") with self.open(file_path, "a", encoding="utf8") as f0: f0.write("test") self.assertEqual(0, self.os.path.getsize(file_path)) f0.truncate() self.assertEqual(4, self.os.path.getsize(file_path)) def check_seek_outside_and_truncate_sets_size(self, mode): # Regression test for #294 and #296 file_path = self.make_path("baz") with self.open(file_path, mode, encoding="utf8") as f0: f0.seek(1) f0.truncate() self.assertEqual(1, f0.tell()) self.assertEqual(1, self.os.path.getsize(file_path)) f0.seek(1) self.assertEqual(1, self.os.path.getsize(file_path)) self.assertEqual(1, self.os.path.getsize(file_path)) def test_seek_outside_and_truncate_sets_size_in_write_mode(self): # Regression test for #294 self.check_seek_outside_and_truncate_sets_size("w") def test_seek_outside_and_truncate_sets_size_in_append_mode(self): # Regression test for #295 self.check_seek_outside_and_truncate_sets_size("a") def test_closed(self): file_path = self.make_path("foo") f = self.open(file_path, "w", encoding="utf8") self.assertFalse(f.closed) f.close() self.assertTrue(f.closed) f = self.open(file_path, encoding="utf8") self.assertFalse(f.closed) f.close() self.assertTrue(f.closed) def test_closing_closed_file_does_nothing(self): # Regression test for #299 file_path = self.make_path("baz") f0 = self.open(file_path, "w", encoding="utf8") f0.close() with self.open(file_path, encoding="utf8") as f1: # would close f1 if not handled f0.close() self.assertEqual("", f1.read()) def test_closing_file_with_different_close_mode(self): self.skip_real_fs() filename = self.make_path("test.txt") fd = self.os.open(filename, os.O_CREAT | os.O_RDWR) file_obj = self.filesystem.get_object(filename) with self.open(fd, "wb", closefd=False) as fp: fp.write(b"test") self.assertTrue(self.filesystem.has_open_file(file_obj)) self.os.close(fd) self.assertFalse(self.filesystem.has_open_file(file_obj)) def test_truncate_flushes_zeros(self): # Regression test for #301 file_path = self.make_path("baz") with self.open(file_path, "w", encoding="utf8") as f0: with self.open(file_path, encoding="utf8") as f1: f0.seek(1) f0.truncate() self.assertEqual("\0", f1.read()) def test_byte_filename(self): file_path = self.make_path(b"test") with self.open(file_path, "wb") as f: f.write(b"test") with self.open(file_path, "rb") as f: self.assertEqual(b"test", f.read()) def test_unicode_filename(self): file_path = self.make_path("тест") with self.open(file_path, "wb") as f: f.write(b"test") with self.open(file_path, "rb") as f: self.assertEqual(b"test", f.read()) def test_write_devnull(self): for mode in ("r+", "w", "w+", "a", "a+"): with self.open(self.os.devnull, mode, encoding="utf8") as f: f.write("test") with self.open(self.os.devnull, encoding="utf8") as f: self.assertEqual("", f.read()) def test_utf16_text(self): # regression test for #574 file_path = self.make_path("foo") with self.open(file_path, "w", encoding="utf-16") as f: assert f.write("1") == 1 with self.open(file_path, "a", encoding="utf-16") as f: assert f.write("2") == 1 with self.open(file_path, "r", encoding="utf-16") as f: text = f.read() assert text == "12" class RealFileOpenTest(FakeFileOpenTest): def use_real_fs(self): return True class FakeFileOpenWithOpenerTest(FakeFileOpenTestBase): def opener(self, path, flags): return self.os.open(path, flags) def test_use_opener_with_read(self): file_path = self.make_path("foo") self.create_file(file_path, contents="test") with self.open(file_path, encoding="utf8", opener=self.opener) as f: assert f.read() == "test" with self.assertRaises(OSError): f.write("foo") def test_no_opener_with_read(self): file_path = self.make_path("foo") self.create_file(file_path, contents="test") with self.open(file_path, encoding="utf8") as f: assert f.read() == "test" with self.assertRaises(OSError): f.write("foo") def test_use_opener_with_read_plus(self): file_path = self.make_path("foo") self.create_file(file_path, contents="test") with self.open(file_path, "r+", encoding="utf8", opener=self.opener) as f: assert f.read() == "test" assert f.write("bar") == 3 with self.open(file_path, encoding="utf8") as f: assert f.read() == "testbar" def test_use_opener_with_write(self): file_path = self.make_path("foo") self.create_file(file_path, contents="foo") with self.open(file_path, "w", encoding="utf8", opener=self.opener) as f: with self.assertRaises(OSError): f.read() assert f.write("bar") == 3 with self.open(file_path, encoding="utf8") as f: assert f.read() == "bar" def test_use_opener_with_write_plus(self): file_path = self.make_path("foo") self.create_file(file_path, contents="test") with self.open(file_path, "w+", encoding="utf8", opener=self.opener) as f: assert f.read() == "" assert f.write("bar") == 3 with self.open(file_path, encoding="utf8") as f: assert f.read() == "bar" def test_use_opener_with_append(self): file_path = self.make_path("foo") self.create_file(file_path, contents="foo") with self.open(file_path, "a", encoding="utf8", opener=self.opener) as f: assert f.write("bar") == 3 with self.assertRaises(OSError): f.read() with self.open(file_path, encoding="utf8") as f: assert f.read() == "foobar" def test_use_opener_with_append_plus(self): file_path = self.make_path("foo") self.create_file(file_path, contents="foo") with self.open(file_path, "a+", encoding="utf8", opener=self.opener) as f: assert f.read() == "" assert f.write("bar") == 3 with self.open(file_path, encoding="utf8") as f: assert f.read() == "foobar" def test_use_opener_with_exclusive_write(self): file_path = self.make_path("foo") self.create_file(file_path, contents="test") with self.assertRaises(OSError): self.open(file_path, "x", encoding="utf8", opener=self.opener) file_path = self.make_path("bar") with self.open(file_path, "x", encoding="utf8", opener=self.opener) as f: assert f.write("bar") == 3 with self.assertRaises(OSError): f.read() with self.open(file_path, encoding="utf8") as f: assert f.read() == "bar" def test_use_opener_with_exclusive_plus(self): file_path = self.make_path("foo") self.create_file(file_path, contents="test") with self.assertRaises(OSError): self.open(file_path, "x+", encoding="utf8", opener=self.opener) file_path = self.make_path("bar") with self.open(file_path, "x+", encoding="utf8", opener=self.opener) as f: assert f.write("bar") == 3 assert f.read() == "" with self.open(file_path, encoding="utf8") as f: assert f.read() == "bar" class RealFileOpenWithOpenerTest(FakeFileOpenWithOpenerTest): def use_real_fs(self): return True @unittest.skipIf(sys.version_info < (3, 8), "open_code only present since Python 3.8") class FakeFilePatchedOpenCodeTest(FakeFileOpenTestBase): def setUp(self): super().setUp() if self.use_real_fs(): self.open_code = io.open_code else: self.filesystem.patch_open_code = PatchMode.ON self.open_code = self.fake_io_module.open_code def tearDown(self): if not self.use_real_fs(): self.filesystem.patch_open_code = False super().tearDown() @unittest.skipIf(IS_PYPY, "Different behavior in PyPy") def test_invalid_path(self): with self.assertRaises(TypeError): self.open_code(4) @unittest.skipIf(not IS_PYPY, "Different behavior in PyPy") def test_open_code_fd_pypy(self): file_path = self.make_path("foo") self.create_file(file_path, contents="test") fd = self.os.open(file_path, os.O_RDONLY) with self.open_code(fd) as f: assert f.read() == b"test" def test_byte_contents_open_code(self): byte_fractions = b"\xe2\x85\x93 \xe2\x85\x94 \xe2\x85\x95 \xe2\x85\x96" file_path = self.make_path("foo") self.create_file(file_path, contents=byte_fractions) with self.open_code(file_path) as f: contents = f.read() self.assertEqual(contents, byte_fractions) def test_open_code_in_real_fs(self): self.skip_real_fs() file_path = __file__ with self.assertRaises(OSError): self.open_code(file_path) class RealPatchedFileOpenCodeTest(FakeFilePatchedOpenCodeTest): def use_real_fs(self): return True @unittest.skipIf(sys.version_info < (3, 8), "open_code only present since Python 3.8") class FakeFileUnpatchedOpenCodeTest(FakeFileOpenTestBase): def setUp(self): super().setUp() if self.use_real_fs(): self.open_code = io.open_code else: self.open_code = self.fake_io_module.open_code @unittest.skipIf(IS_PYPY, "Different behavior in PyPy") def test_invalid_path(self): with self.assertRaises(TypeError): self.open_code(4) def test_open_code_in_real_fs(self): file_path = __file__ with self.open_code(file_path) as f: contents = f.read() self.assertTrue(len(contents) > 100) class RealUnpatchedFileOpenCodeTest(FakeFileUnpatchedOpenCodeTest): def use_real_fs(self): return True def test_byte_contents_open_code(self): byte_fractions = b"\xe2\x85\x93 \xe2\x85\x94 \xe2\x85\x95 \xe2\x85\x96" file_path = self.make_path("foo") self.create_file(file_path, contents=byte_fractions) with self.open_code(file_path) as f: contents = f.read() self.assertEqual(contents, byte_fractions) class BufferingModeTest(FakeFileOpenTestBase): def test_no_buffering(self): file_path = self.make_path("buffertest.bin") with self.open(file_path, "wb", buffering=0) as f: f.write(b"a" * 128) with self.open(file_path, "rb") as r: x = r.read() self.assertEqual(b"a" * 128, x) def test_no_buffering_not_allowed_in_textmode(self): file_path = self.make_path("buffertest.txt") with self.assertRaises(ValueError): self.open(file_path, "w", encoding="utf8", buffering=0) def test_default_buffering_no_flush(self): file_path = self.make_path("buffertest.bin") with self.open(file_path, "wb") as f: f.write(b"a" * 2048) with self.open(file_path, "rb") as r: x = r.read() self.assertEqual(b"", x) with self.open(file_path, "rb") as r: x = r.read() self.assertEqual(b"a" * 2048, x) def test_default_buffering_flush(self): file_path = self.make_path("buffertest.bin") with self.open(file_path, "wb") as f: f.write(b"a" * 2048) f.flush() with self.open(file_path, "rb") as r: x = r.read() self.assertEqual(b"a" * 2048, x) def test_writing_with_specific_buffer(self): file_path = self.make_path("buffertest.bin") with self.open(file_path, "wb", buffering=512) as f: f.write(b"a" * 500) with self.open(file_path, "rb") as r: x = r.read() # buffer not filled - not written self.assertEqual(0, len(x)) f.write(b"a" * 400) with self.open(file_path, "rb") as r: x = r.read() # buffer exceeded, but new buffer (400) not - previous written self.assertEqual(500, len(x)) f.write(b"a" * 100) with self.open(file_path, "rb") as r: x = r.read() # buffer not full (500) not written self.assertEqual(500, len(x)) f.write(b"a" * 100) with self.open(file_path, "rb") as r: x = r.read() # buffer exceeded (600) -> write previous # new buffer not full (100) - not written self.assertEqual(1000, len(x)) f.write(b"a" * 600) with self.open(file_path, "rb") as r: x = r.read() # new buffer exceeded (600) -> all written self.assertEqual(1700, len(x)) def test_writing_text_with_line_buffer(self): file_path = self.make_path("buffertest.bin") with self.open(file_path, "w", encoding="utf8", buffering=1) as f: f.write("test" * 100) with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # no new line - not written self.assertEqual(0, len(x)) f.write("\ntest") with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # new line - buffer written self.assertEqual(405, len(x)) f.write("test" * 10) with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # buffer not filled - not written self.assertEqual(405, len(x)) f.write("\ntest") with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # new line - buffer written self.assertEqual(450, len(x)) def test_writing_large_text_with_line_buffer(self): file_path = self.make_path("buffertest.bin") with self.open(file_path, "w", encoding="utf8", buffering=1) as f: f.write("test" * 4000) with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # buffer larger than default - written self.assertEqual(16000, len(x)) f.write("test") with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # buffer not filled - not written self.assertEqual(16000, len(x)) f.write("\ntest") with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # new line - buffer written self.assertEqual(16009, len(x)) f.write("\ntest") with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # another new line - buffer written self.assertEqual(16014, len(x)) def test_writing_text_with_default_buffer(self): file_path = self.make_path("buffertest.txt") with self.open(file_path, "w", encoding="utf8") as f: f.write("test" * 5) with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # buffer not filled - not written self.assertEqual(0, len(x)) f.write("\ntest") with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # buffer exceeded, but new buffer (400) not - previous written self.assertEqual(0, len(x)) f.write("test" * 10) with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # buffer not filled - not written self.assertEqual(0, len(x)) f.write("\ntest") with self.open(file_path, "r", encoding="utf8") as r: x = r.read() self.assertEqual(0, len(x)) def test_writing_text_with_specific_buffer(self): file_path = self.make_path("buffertest.txt") with self.open(file_path, "w", encoding="utf8", buffering=2) as f: f.write("a" * 8000) with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # buffer not filled - not written self.assertEqual(0, len(x)) f.write("test") with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # buffer exceeded, but new buffer (400) not - previous written self.assertEqual(0, len(x)) f.write("test") with self.open(file_path, "r", encoding="utf8") as r: x = r.read() # buffer not filled - not written self.assertEqual(0, len(x)) f.write("test") with self.open(file_path, "r", encoding="utf8") as r: x = r.read() self.assertEqual(0, len(x)) # with self.open(file_path, "r") as r: # x = r.read() # self.assertEqual(35, len(x)) def test_append_with_specific_buffer(self): file_path = self.make_path("buffertest.bin") with self.open(file_path, "wb", buffering=512) as f: f.write(b"a" * 500) with self.open(file_path, "ab", buffering=512) as f: f.write(b"a" * 500) with self.open(file_path, "rb") as r: x = r.read() # buffer not filled - not written self.assertEqual(500, len(x)) f.write(b"a" * 400) with self.open(file_path, "rb") as r: x = r.read() # buffer exceeded, but new buffer (400) not - previous written self.assertEqual(1000, len(x)) f.write(b"a" * 100) with self.open(file_path, "rb") as r: x = r.read() # buffer not full (500) not written self.assertEqual(1000, len(x)) f.write(b"a" * 100) with self.open(file_path, "rb") as r: x = r.read() # buffer exceeded (600) -> write previous # new buffer not full (100) - not written self.assertEqual(1500, len(x)) f.write(b"a" * 600) with self.open(file_path, "rb") as r: x = r.read() # new buffer exceeded (600) -> all written self.assertEqual(2200, len(x)) def test_failed_flush_does_not_truncate_file(self): # regression test for #548 self.skip_real_fs() # cannot set fs size in real fs self.filesystem.set_disk_usage(100) self.os.makedirs("foo") file_path = self.os.path.join("foo", "bar.txt") with self.open(file_path, "wb") as f: f.write(b"a" * 50) f.flush() with self.open(file_path, "rb") as r: x = r.read() self.assertTrue(x.startswith(b"a" * 50)) with self.assertRaises(OSError): f.write(b"b" * 200) f.flush() with self.open(file_path, "rb") as r: x = r.read() self.assertTrue(x.startswith(b"a" * 50)) f.truncate(50) def test_failed_write_does_not_truncate_file(self): # test the same with no buffering and no flush self.skip_real_fs() # cannot set fs size in real fs self.filesystem.set_disk_usage(100) self.os.makedirs("foo") file_path = self.os.path.join("foo", "bar.txt") with self.open(file_path, "wb", buffering=0) as f: f.write(b"a" * 50) with self.open(file_path, "rb") as r: x = r.read() self.assertEqual(b"a" * 50, x) with self.assertRaises(OSError): f.write(b"b" * 200) with self.open(file_path, "rb") as r: x = r.read() self.assertEqual(b"a" * 50, x) def test_writing_over_buffer_end(self): # regression test for #1120 dir_path = self.make_path("foo") self.create_dir(dir_path) file_path = self.os.path.join(dir_path, "bar.txt") line_end_size = len(self.os.linesep) char_count = io.DEFAULT_BUFFER_SIZE // 256 - line_end_size for line_count in (255, 256, 257, 511, 512, 513): with self.open(file_path, "w") as f: for i in range(line_count): f.write("x" * char_count + "\n") with self.open(file_path) as f: lines = f.readlines() self.assertEqual(line_count, len(lines)) class RealBufferingTest(BufferingModeTest): def use_real_fs(self): return True class OpenFileWithEncodingTest(FakeFileOpenTestBase): """Tests that are similar to some open file tests above but using an explicit text encoding.""" def setUp(self): super().setUp() self.file_path = self.make_path("foo") def test_write_str_read_bytes(self): str_contents = "علي بابا" with self.open(self.file_path, "w", encoding="arabic") as f: f.write(str_contents) with self.open(self.file_path, "rb") as f: contents = f.read() self.assertEqual(str_contents, contents.decode("arabic")) def test_write_str_error_modes(self): str_contents = "علي بابا" with self.open(self.file_path, "w", encoding="cyrillic") as f: with self.assertRaises(UnicodeEncodeError): f.write(str_contents) with self.open( self.file_path, "w", encoding="ascii", errors="xmlcharrefreplace" ) as f: f.write(str_contents) with self.open(self.file_path, "r", encoding="ascii") as f: contents = f.read() self.assertEqual("علي بابا", contents) with self.open( self.file_path, "w", encoding="ascii", errors="namereplace" ) as f: f.write(str_contents) with self.open(self.file_path, "r", encoding="ascii") as f: contents = f.read() self.assertEqual( r"\N{ARABIC LETTER AIN}\N{ARABIC LETTER LAM}\N" r"{ARABIC LETTER YEH} \N{ARABIC LETTER BEH}\N" r"{ARABIC LETTER ALEF}\N{ARABIC LETTER BEH}" r"\N{ARABIC LETTER ALEF}", contents, ) def test_read_str_error_modes(self): str_contents = "علي بابا" with self.open(self.file_path, "w", encoding="arabic") as f: f.write(str_contents) # default strict encoding with self.open(self.file_path, encoding="ascii") as f: with self.assertRaises(UnicodeDecodeError): f.read() with self.open(self.file_path, encoding="ascii", errors="replace") as f: contents = f.read() self.assertNotEqual(str_contents, contents) with self.open( self.file_path, encoding="ascii", errors="backslashreplace" ) as f: contents = f.read() self.assertEqual(r"\xd9\xe4\xea \xc8\xc7\xc8\xc7", contents) def test_write_and_read_str(self): str_contents = "علي بابا" with self.open(self.file_path, "w", encoding="arabic") as f: f.write(str_contents) with self.open(self.file_path, "r", encoding="arabic") as f: contents = f.read() self.assertEqual(str_contents, contents) def test_create_file_with_append(self): contents = [ "Allons enfants de la Patrie,Le jour de gloire est arrivé!", "Contre nous de la tyrannie,", "L’étendard sanglant est levé.", ] with self.open(self.file_path, "a", encoding="utf-8") as fake_file: for line in contents: fake_file.write(line + "\n") with self.open(self.file_path, encoding="utf-8") as fake_file: result = [line.rstrip() for line in fake_file] self.assertEqual(contents, result) def test_append_existing_file(self): contents = [ "Оригинальное содержаниеДополнительное содержание", ] self.create_file(self.file_path, contents=contents[0], encoding="cyrillic") with self.open(self.file_path, "a", encoding="cyrillic") as fake_file: for line in contents[1:]: fake_file.write(line + "\n") with self.open(self.file_path, encoding="cyrillic") as fake_file: result = [line.rstrip() for line in fake_file] self.assertEqual(contents, result) def test_open_with_wplus(self): self.create_file( self.file_path, contents="старое содержание", encoding="cyrillic" ) with self.open(self.file_path, "r", encoding="cyrillic") as fake_file: self.assertEqual("старое содержание", fake_file.read()) with self.open(self.file_path, "w+", encoding="cyrillic") as fake_file: fake_file.write("новое содержание") fake_file.seek(0) self.assertTrue("новое содержание", fake_file.read()) def test_open_with_append_flag(self): contents = ["Калинка,\n", "калинка,\n", "калинка моя,\n"] additional_contents = ["В саду ягода-малинка,\n", "малинка моя.\n"] self.create_file( self.file_path, contents="".join(contents), encoding="cyrillic" ) with self.open(self.file_path, "a", encoding="cyrillic") as fake_file: with self.assertRaises(io.UnsupportedOperation): fake_file.read(0) with self.assertRaises(io.UnsupportedOperation): fake_file.readline() self.assertEqual(len("".join(contents)), fake_file.tell()) fake_file.seek(0) self.assertEqual(0, fake_file.tell()) fake_file.writelines(additional_contents) with self.open(self.file_path, encoding="cyrillic") as fake_file: self.assertEqual(contents + additional_contents, fake_file.readlines()) def test_append_with_aplus(self): self.create_file( self.file_path, contents="старое содержание", encoding="cyrillic" ) fake_file = self.open(self.file_path, "r", encoding="cyrillic") fake_file.close() with self.open(self.file_path, "a+", encoding="cyrillic") as fake_file: self.assertEqual(17, fake_file.tell()) fake_file.write("новое содержание") self.assertEqual(33, fake_file.tell()) fake_file.seek(0) self.assertEqual("старое содержаниеновое содержание", fake_file.read()) def test_read_with_rplus(self): self.create_file( self.file_path, contents="старое содержание здесь", encoding="cyrillic", ) fake_file = self.open(self.file_path, "r", encoding="cyrillic") fake_file.close() with self.open(self.file_path, "r+", encoding="cyrillic") as fake_file: self.assertEqual("старое содержание здесь", fake_file.read()) fake_file.seek(0) fake_file.write("новое содержание") fake_file.seek(0) self.assertEqual("новое содержание здесь", fake_file.read()) class OpenRealFileWithEncodingTest(OpenFileWithEncodingTest): def use_real_fs(self): return True class FakeFileOpenLineEndingTest(FakeFileOpenTestBase): def setUp(self): super().setUp() def test_read_default_newline_mode(self): file_path = self.make_path("some_file") for contents in (b"1\n2", b"1\r\n2", b"1\r2"): self.create_file(file_path, contents=contents) with self.open(file_path, mode="r", encoding="utf8") as f: self.assertEqual(["1\n", "2"], f.readlines()) with self.open(file_path, mode="r", encoding="utf8") as f: self.assertEqual("1\n2", f.read()) with self.open(file_path, mode="rb") as f: self.assertEqual(contents, f.read()) def test_write_universal_newline_mode(self): file_path = self.make_path("some_file") with self.open(file_path, "w", encoding="utf8") as f: f.write("1\n2") with self.open(file_path, mode="rb") as f: self.assertEqual(b"1" + self.os.linesep.encode() + b"2", f.read()) with self.open(file_path, "w", encoding="utf8") as f: f.write("1\r\n2") with self.open(file_path, mode="rb") as f: self.assertEqual(b"1\r" + self.os.linesep.encode() + b"2", f.read()) def test_read_with_newline_arg(self): file_path = self.make_path("some_file") file_contents = b"1\r\n2\n3\r4" self.create_file(file_path, contents=file_contents) with self.open(file_path, mode="r", encoding="utf8", newline="") as f: self.assertEqual("1\r\n2\n3\r4", f.read()) with self.open(file_path, mode="r", encoding="utf8", newline="\r") as f: self.assertEqual("1\r\n2\n3\r4", f.read()) with self.open(file_path, mode="r", encoding="utf8", newline="\n") as f: self.assertEqual("1\r\n2\n3\r4", f.read()) with self.open(file_path, mode="r", encoding="utf8", newline="\r\n") as f: self.assertEqual("1\r\n2\n3\r4", f.read()) def test_readlines_with_newline_arg(self): file_path = self.make_path("some_file") file_contents = b"1\r\n2\n3\r4" self.create_file(file_path, contents=file_contents) with self.open(file_path, mode="r", encoding="utf8", newline="") as f: self.assertEqual(["1\r\n", "2\n", "3\r", "4"], f.readlines()) with self.open(file_path, mode="r", encoding="utf8", newline="\r") as f: self.assertEqual(["1\r", "\n2\n3\r", "4"], f.readlines()) with self.open(file_path, mode="r", encoding="utf8", newline="\n") as f: self.assertEqual(["1\r\n", "2\n", "3\r4"], f.readlines()) with self.open(file_path, mode="r", encoding="utf8", newline="\r\n") as f: self.assertEqual(["1\r\n", "2\n3\r4"], f.readlines()) @unittest.skipIf(sys.version_info >= (3, 10), "U flag no longer supported") def test_read_with_ignored_universal_newlines_flag(self): file_path = self.make_path("some_file") file_contents = b"1\r\n2\n3\r4" self.create_file(file_path, contents=file_contents) with self.open(file_path, mode="r", encoding="utf8", newline="\r") as f: self.assertEqual("1\r\n2\n3\r4", f.read()) with self.open(file_path, mode="r", encoding="utf8", newline="\r") as f: self.assertEqual("1\r\n2\n3\r4", f.read()) with self.open(file_path, mode="U", encoding="utf8", newline="\r") as f: self.assertEqual("1\r\n2\n3\r4", f.read()) @unittest.skipIf(sys.version_info < (3, 11), "U flag still supported") def test_universal_newlines_flag_not_supported(self): file_path = self.make_path("some_file") file_contents = b"1\r\n2\n3\r4" self.create_file(file_path, contents=file_contents) with self.assertRaises(ValueError): self.open(file_path, mode="U", encoding="utf8", newline="\r") def test_write_with_newline_arg(self): file_path = self.make_path("some_file") with self.open(file_path, "w", encoding="utf8", newline="") as f: f.write("1\r\n2\n3\r4") with self.open(file_path, mode="rb") as f: self.assertEqual(b"1\r\n2\n3\r4", f.read()) with self.open(file_path, "w", encoding="utf8", newline="\n") as f: f.write("1\r\n2\n3\r4") with self.open(file_path, mode="rb") as f: self.assertEqual(b"1\r\n2\n3\r4", f.read()) with self.open(file_path, "w", encoding="utf8", newline="\r\n") as f: f.write("1\r\n2\n3\r4") with self.open(file_path, mode="rb") as f: self.assertEqual(b"1\r\r\n2\r\n3\r4", f.read()) with self.open(file_path, "w", encoding="utf8", newline="\r") as f: f.write("1\r\n2\n3\r4") with self.open(file_path, mode="rb") as f: self.assertEqual(b"1\r\r2\r3\r4", f.read()) def test_binary_readline(self): file_path = self.make_path("some_file") file_contents = b"\x80\n\x80\r\x80\r\n\x80" def chunk_line(): px = 0 while px < len(file_contents): ix = file_contents.find(b"\n", px) if ix == -1: yield file_contents[px:] return yield file_contents[px : ix + 1] px = ix + 1 chunked_contents = list(chunk_line()) self.create_file(file_path, contents=file_contents) with self.open(file_path, mode="rb") as f: self.assertEqual(chunked_contents, list(f)) class RealFileOpenLineEndingTest(FakeFileOpenLineEndingTest): def use_real_fs(self): return True class FakeFileOpenLineEndingWithEncodingTest(FakeFileOpenTestBase): def setUp(self): super().setUp() def test_read_standard_newline_mode(self): file_path = self.make_path("some_file") for contents in ("раз\nдва", "раз\r\nдва", "раз\rдва"): self.create_file(file_path, contents=contents, encoding="cyrillic") with self.open(file_path, mode="r", encoding="cyrillic") as fake_file: self.assertEqual(["раз\n", "два"], fake_file.readlines()) with self.open(file_path, mode="r", encoding="cyrillic") as fake_file: self.assertEqual("раз\nдва", fake_file.read()) def test_write_universal_newline_mode(self): file_path = self.make_path("some_file") with self.open(file_path, "w", encoding="cyrillic") as f: f.write("раз\nдва") with self.open(file_path, mode="rb") as f: self.assertEqual( "раз".encode("cyrillic") + self.os.linesep.encode() + "два".encode("cyrillic"), f.read(), ) with self.open(file_path, "w", encoding="cyrillic") as f: f.write("раз\r\nдва") with self.open(file_path, mode="rb") as f: self.assertEqual( "раз\r".encode("cyrillic") + self.os.linesep.encode() + "два".encode("cyrillic"), f.read(), ) def test_read_with_newline_arg(self): file_path = self.make_path("some_file") file_contents = "раз\r\nдва\nтри\rчетыре" self.create_file(file_path, contents=file_contents, encoding="cyrillic") with self.open(file_path, mode="r", newline="", encoding="cyrillic") as f: self.assertEqual("раз\r\nдва\nтри\rчетыре", f.read()) with self.open(file_path, mode="r", newline="\r", encoding="cyrillic") as f: self.assertEqual("раз\r\nдва\nтри\rчетыре", f.read()) with self.open(file_path, mode="r", newline="\n", encoding="cyrillic") as f: self.assertEqual("раз\r\nдва\nтри\rчетыре", f.read()) with self.open(file_path, mode="r", newline="\r\n", encoding="cyrillic") as f: self.assertEqual("раз\r\nдва\nтри\rчетыре", f.read()) def test_readlines_with_newline_arg(self): file_path = self.make_path("some_file") file_contents = "раз\r\nдва\nтри\rчетыре" self.create_file(file_path, contents=file_contents, encoding="cyrillic") with self.open(file_path, mode="r", newline="", encoding="cyrillic") as f: self.assertEqual(["раз\r\n", "два\n", "три\r", "четыре"], f.readlines()) with self.open(file_path, mode="r", newline="\r", encoding="cyrillic") as f: self.assertEqual(["раз\r", "\nдва\nтри\r", "четыре"], f.readlines()) with self.open(file_path, mode="r", newline="\n", encoding="cyrillic") as f: self.assertEqual(["раз\r\n", "два\n", "три\rчетыре"], f.readlines()) with self.open(file_path, mode="r", newline="\r\n", encoding="cyrillic") as f: self.assertEqual(["раз\r\n", "два\nтри\rчетыре"], f.readlines()) def test_write_with_newline_arg(self): file_path = self.make_path("some_file") with self.open(file_path, "w", newline="", encoding="cyrillic") as f: f.write("раз\r\nдва\nтри\rчетыре") with self.open(file_path, mode="rb") as f: self.assertEqual("раз\r\nдва\nтри\rчетыре".encode("cyrillic"), f.read()) with self.open(file_path, "w", newline="\n", encoding="cyrillic") as f: f.write("раз\r\nдва\nтри\rчетыре") with self.open(file_path, mode="rb") as f: self.assertEqual("раз\r\nдва\nтри\rчетыре".encode("cyrillic"), f.read()) with self.open(file_path, "w", newline="\r\n", encoding="cyrillic") as f: f.write("раз\r\nдва\nтри\rчетыре") with self.open(file_path, mode="rb") as f: self.assertEqual("раз\r\r\nдва\r\nтри\rчетыре".encode("cyrillic"), f.read()) with self.open(file_path, "w", newline="\r", encoding="cyrillic") as f: f.write("раз\r\nдва\nтри\rчетыре") with self.open(file_path, mode="rb") as f: self.assertEqual("раз\r\rдва\rтри\rчетыре".encode("cyrillic"), f.read()) class RealFileOpenLineEndingWithEncodingTest(FakeFileOpenLineEndingWithEncodingTest): def use_real_fs(self): return True class OpenWithFileDescriptorTest(FakeFileOpenTestBase): def test_open_with_file_descriptor(self): file_path = self.make_path("this", "file") self.create_file(file_path) fd = self.os.open(file_path, os.O_CREAT) self.assertEqual(fd, self.open(fd, "r", encoding="utf8").fileno()) def test_closefd_with_file_descriptor(self): file_path = self.make_path("this", "file") self.create_file(file_path) fd = self.os.open(file_path, os.O_CREAT) fh = self.open(fd, "r", encoding="utf8", closefd=False) fh.close() self.assertIsNotNone(self.filesystem.open_files[fd]) fh = self.open(fd, "r", encoding="utf8", closefd=True) fh.close() self.assertIsNone(self.filesystem.open_files[fd]) class OpenWithRealFileDescriptorTest(FakeFileOpenTestBase): def use_real_fs(self): return True class OpenWithFlagsTestBase(FakeFileOpenTestBase): def setUp(self): super().setUp() self.file_path = self.make_path("some_file") self.file_contents = None def open_file(self, mode): kwargs = {"mode": mode} if "b" not in mode: kwargs["encoding"] = "utf8" return self.open(self.file_path, **kwargs) def open_file_and_seek(self, mode): fake_file = self.open(self.file_path, mode=mode) fake_file.seek(0, 2) return fake_file def write_and_reopen_file(self, fake_file, mode="r", encoding=None): fake_file.write(self.file_contents) fake_file.close() args = {"mode": mode} if encoding: args["encoding"] = encoding return self.open(self.file_path, **args) class OpenWithBinaryFlagsTest(OpenWithFlagsTestBase): def setUp(self): super().setUp() self.file_contents = b"real binary contents: \x1f\x8b" self.create_file(self.file_path, contents=self.file_contents) def test_read_binary(self): with self.open_file("rb") as fake_file: self.assertEqual(self.file_contents, fake_file.read()) def test_write_binary(self): with self.open_file_and_seek("wb") as f: self.assertEqual(0, f.tell()) with self.write_and_reopen_file(f, mode="rb") as f1: self.assertEqual(self.file_contents, f1.read()) # Attempt to reopen the file in text mode with self.open_file("wb") as f2: with self.write_and_reopen_file( f2, mode="r", encoding="ascii" ) as f3: with self.assertRaises(UnicodeDecodeError): f3.read() def test_write_and_read_binary(self): with self.open_file_and_seek("w+b") as f: self.assertEqual(0, f.tell()) with self.write_and_reopen_file(f, mode="rb") as f1: self.assertEqual(self.file_contents, f1.read()) class RealOpenWithBinaryFlagsTest(OpenWithBinaryFlagsTest): def use_real_fs(self): return True class OpenWithTextModeFlagsTest(OpenWithFlagsTestBase): def setUp(self): super().setUp() self.setUpFileSystem() def setUpFileSystem(self): self.file_path = self.make_path("some_file") self.file_contents = b"two\r\nlines" self.original_contents = "two\r\nlines" self.converted_contents = "two\nlines" self.create_file(self.file_path, contents=self.file_contents) def test_read_text(self): """Test that text mode flag is ignored""" self.check_windows_only() with self.open_file("r") as f: self.assertEqual(self.converted_contents, f.read()) with self.open_file("rt") as f: self.assertEqual(self.converted_contents, f.read()) def test_mixed_text_and_binary_flags(self): with self.assertRaises(ValueError): self.open_file_and_seek("w+bt") class RealOpenWithTextModeFlagsTest(OpenWithTextModeFlagsTest): def use_real_fs(self): return True class OpenWithInvalidFlagsTest(FakeFileOpenTestBase): def test_capital_r(self): with self.assertRaises(ValueError): self.open("some_file", "R") def test_capital_w(self): with self.assertRaises(ValueError): self.open("some_file", "W") def test_capital_a(self): with self.assertRaises(ValueError): self.open("some_file", "A") def test_lower_u(self): with self.assertRaises(ValueError): self.open("some_file", "u") def test_lower_rw(self): with self.assertRaises(ValueError): self.open("some_file", "rw") class OpenWithInvalidFlagsRealFsTest(OpenWithInvalidFlagsTest): def use_real_fs(self): return True class ResolvePathTest(FakeFileOpenTestBase): def write_to_file(self, file_name): with self.open(file_name, "w", encoding="utf8") as fh: fh.write("x") def test_none_filepath_raises_type_error(self): with self.assertRaises(TypeError): self.open(None, "w", encoding="utf8") def test_empty_filepath_raises_io_error(self): with self.assertRaises(OSError): self.open("", "w", encoding="utf8") def test_normal_path(self): file_path = self.make_path("foo") self.write_to_file(file_path) self.assertTrue(self.os.path.exists(file_path)) def test_link_within_same_directory(self): skip_if_symlink_not_supported() final_target = self.make_path("foo", "baz") link_path = self.make_path("foo", "bar") self.create_symlink(link_path, "baz") self.write_to_file(link_path) self.assertTrue(self.os.path.exists(final_target)) self.assertEqual(1, self.os.stat(final_target)[stat.ST_SIZE]) def test_link_to_sub_directory(self): skip_if_symlink_not_supported() final_target = self.make_path("foo", "baz", "bip") dir_path = self.make_path("foo", "baz") self.create_dir(dir_path) link_path = self.make_path("foo", "bar") target_path = self.os.path.join("baz", "bip") self.create_symlink(link_path, target_path) self.write_to_file(link_path) self.assertTrue(self.os.path.exists(final_target)) self.assertEqual(1, self.os.stat(final_target)[stat.ST_SIZE]) self.assertTrue(self.os.path.exists(dir_path)) # Make sure that intermediate directory got created. self.assertTrue(self.os.stat(dir_path)[stat.ST_MODE] & stat.S_IFDIR) def test_link_to_parent_directory(self): skip_if_symlink_not_supported() final_target = self.make_path("baz", "bip") self.create_dir(self.make_path("foo")) self.create_dir(self.make_path("baz")) link_path = self.make_path("foo", "bar") self.create_symlink(link_path, self.os.path.join("..", "baz")) self.write_to_file(self.make_path("foo", "bar", "bip")) self.assertTrue(self.os.path.exists(final_target)) self.assertEqual(1, self.os.stat(final_target)[stat.ST_SIZE]) self.assertTrue(self.os.path.exists(link_path)) def test_link_to_absolute_path(self): skip_if_symlink_not_supported() final_target = self.make_path("foo", "baz", "bip") self.create_dir(self.make_path("foo", "baz")) link_path = self.make_path("foo", "bar") self.create_symlink(link_path, final_target) self.write_to_file(link_path) self.assertTrue(self.os.path.exists(final_target)) def test_relative_links_work_after_chdir(self): skip_if_symlink_not_supported() final_target = self.make_path("foo", "baz", "bip") self.create_dir(self.make_path("foo", "baz")) link_path = self.make_path("foo", "bar") self.create_symlink(link_path, self.os.path.join(".", "baz", "bip")) if not self.is_windows: self.assert_equal_paths(final_target, self.os.path.realpath(link_path)) self.assertTrue(self.os.path.islink(link_path)) self.os.chdir(self.make_path("foo")) self.assert_equal_paths(self.make_path("foo"), self.os.getcwd()) self.assertTrue(self.os.path.islink("bar")) if not self.is_windows: self.assert_equal_paths(final_target, self.os.path.realpath("bar")) self.write_to_file(link_path) self.assertTrue(self.os.path.exists(final_target)) def test_absolute_links_work_after_chdir(self): skip_if_symlink_not_supported() final_target = self.make_path("foo", "baz", "bip") self.create_dir(self.make_path("foo", "baz")) link_path = self.make_path("foo", "bar") self.create_symlink(link_path, final_target) if not self.is_windows: self.assert_equal_paths(final_target, self.os.path.realpath(link_path)) self.assertTrue(self.os.path.islink(link_path)) self.os.chdir(self.make_path("foo")) self.assert_equal_paths(self.make_path("foo"), self.os.getcwd()) self.assertTrue(self.os.path.islink("bar")) if not self.is_windows: self.assert_equal_paths(final_target, self.os.path.realpath("bar")) self.write_to_file(link_path) self.assertTrue(self.os.path.exists(final_target)) def test_chdir_through_relative_link(self): self.check_posix_only() dir1_path = self.make_path("x", "foo") dir2_path = self.make_path("x", "bar") self.create_dir(dir1_path) self.create_dir(dir2_path) link_path = self.make_path("x", "foo", "bar") self.create_symlink(link_path, self.os.path.join("..", "bar")) self.assert_equal_paths(dir2_path, self.os.path.realpath(link_path)) self.os.chdir(dir1_path) self.assert_equal_paths(dir1_path, self.os.getcwd()) self.assert_equal_paths(dir2_path, self.os.path.realpath("bar")) self.os.chdir("bar") self.assert_equal_paths(dir2_path, self.os.getcwd()) def test_chdir_uses_open_fd_as_path(self): self.check_posix_only() if self.is_pypy: # unclear behavior with PyPi self.skip_real_fs() self.assert_raises_os_error([errno.ENOTDIR, errno.EBADF], self.os.chdir, 500) dir_path = self.make_path("foo", "bar") self.create_dir(dir_path) path_des = self.os.open(dir_path, os.O_RDONLY) self.os.chdir(path_des) self.os.close(path_des) self.assert_equal_paths(dir_path, self.os.getcwd()) def test_read_link_to_link(self): # Write into the final link target and read back from a file which will # point to that. skip_if_symlink_not_supported() link_path = self.make_path("foo", "bar") self.create_symlink(link_path, "link") self.create_symlink(self.make_path("foo", "link"), "baz") self.write_to_file(self.make_path("foo", "baz")) fh = self.open(link_path, "r", encoding="utf8") self.assertEqual("x", fh.read()) def test_write_link_to_link(self): skip_if_symlink_not_supported() final_target = self.make_path("foo", "baz") link_path = self.make_path("foo", "bar") self.create_symlink(link_path, "link") self.create_symlink(self.make_path("foo", "link"), "baz") self.write_to_file(link_path) self.assertTrue(self.os.path.exists(final_target)) def test_multiple_links(self): skip_if_symlink_not_supported() self.os.makedirs(self.make_path("a", "link1", "c", "link2")) self.create_symlink(self.make_path("a", "b"), "link1") if not self.is_windows: self.assert_equal_paths( self.make_path("a", "link1"), self.os.path.realpath(self.make_path("a", "b")), ) self.assert_equal_paths( self.make_path("a", "link1", "c"), self.os.path.realpath(self.make_path("a", "b", "c")), ) link_path = self.make_path("a", "link1", "c", "d") self.create_symlink(link_path, "link2") self.assertTrue(self.os.path.exists(link_path)) self.assertTrue(self.os.path.exists(self.make_path("a", "b", "c", "d"))) final_target = self.make_path("a", "link1", "c", "link2", "e") self.assertFalse(self.os.path.exists(final_target)) self.write_to_file(self.make_path("a", "b", "c", "d", "e")) self.assertTrue(self.os.path.exists(final_target)) def test_utime_link(self): """os.utime() and os.stat() via symbolic link (issue #49)""" skip_if_symlink_not_supported() self.create_dir(self.make_path("foo", "baz")) target_path = self.make_path("foo", "baz", "bip") self.write_to_file(target_path) link_name = self.make_path("foo", "bar") self.create_symlink(link_name, target_path) self.os.utime(link_name, (1, 2)) st = self.os.stat(link_name) self.assertEqual(1, st.st_atime) self.assertEqual(2, st.st_mtime) self.os.utime(link_name, (3, 4)) st = self.os.stat(link_name) self.assertEqual(3, st.st_atime) self.assertEqual(4, st.st_mtime) def test_too_many_links(self): self.check_posix_only() link_path = self.make_path("a", "loop") self.create_symlink(link_path, "loop") self.assertFalse(self.os.path.exists(link_path)) def test_that_drive_letters_are_preserved(self): self.check_windows_only() self.skip_real_fs() self.assertEqual("C:!foo!bar", self.filesystem.resolve_path("C:!foo!!bar")) def test_that_unc_paths_are_preserved(self): self.check_windows_only() self.skip_real_fs() self.assertEqual( "!!foo!bar!baz", self.filesystem.resolve_path("!!foo!bar!baz!!") ) class RealResolvePathTest(ResolvePathTest): def use_real_fs(self): return True class SkipOpenTest(unittest.TestCase): def test_open_in_skipped_module(self): with Patcher(additional_skip_names=["skipped_pathlib"]): contents = read_open("skipped_pathlib.py") self.assertTrue(contents.startswith("# Licensed under the Apache License")) if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fake_os_test.py0000644000175100001660000073725014764107375020765 0ustar00runnerdocker# # Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unit tests for fake_os.FakeOsModule.""" import errno import os import stat import sys import unittest from pyfakefs import fake_filesystem, fake_os, fake_open, fake_file from pyfakefs.fake_filesystem import ( FakeFileOpen, is_root, set_uid, set_gid, ) from pyfakefs.helpers import IN_DOCKER, IS_PYPY, get_uid, get_gid, reset_ids from pyfakefs.tests.test_utils import ( TestCase, RealFsTestCase, skip_if_symlink_not_supported, ) class FakeOsModuleTestBase(RealFsTestCase): def setUp(self): super().setUp() self.umask = self.os.umask(0o022) def tearDown(self): self.os.umask(self.umask) def createTestFile(self, path): self.create_file(path) self.assertTrue(self.os.path.exists(path)) st = self.os.stat(path) # under Windows, the umask has no effect mode = 0o666 if self.is_windows_fs else 0o0644 self.assertEqual(mode, stat.S_IMODE(st.st_mode)) self.assertTrue(st.st_mode & stat.S_IFREG) self.assertFalse(st.st_mode & stat.S_IFDIR) def createTestDirectory(self, path): self.create_dir(path) self.assertTrue(self.os.path.exists(path)) st = self.os.stat(path) mode = 0o777 if self.is_windows_fs else 0o755 self.assertEqual(mode, stat.S_IMODE(st.st_mode)) self.assertFalse(st.st_mode & stat.S_IFREG) self.assertTrue(st.st_mode & stat.S_IFDIR) class FakeOsModuleTest(FakeOsModuleTestBase): def setUp(self): super().setUp() self.rwx = self.os.R_OK | self.os.W_OK | self.os.X_OK self.rw = self.os.R_OK | self.os.W_OK def test_chdir(self): """chdir should work on a directory.""" directory = self.make_path("foo") self.create_dir(directory) self.os.chdir(directory) def test_chdir_fails_non_exist(self): """chdir should raise OSError if the target does not exist.""" directory = self.make_path("no", "such", "directory") self.assert_raises_os_error(errno.ENOENT, self.os.chdir, directory) def test_chdir_fails_non_directory(self): """chdir should raise OSError if the target is not a directory.""" filename = self.make_path("foo", "bar") self.create_file(filename) self.assert_raises_os_error(errno.ENOTDIR, self.os.chdir, filename) def test_consecutive_chdir(self): """Consecutive relative chdir calls should work.""" dir1 = self.make_path("foo") dir2 = "bar" full_dirname = self.os.path.join(dir1, dir2) self.create_dir(full_dirname) self.os.chdir(dir1) self.os.chdir(dir2) # use real path to handle symlink /var to /private/var in MacOs self.assertEqual( os.path.realpath(self.os.getcwd()), os.path.realpath(full_dirname) ) def test_backwards_chdir(self): """chdir into '..' should behave appropriately.""" # skipping real fs test - can't test root dir self.skip_real_fs() rootdir = self.os.getcwd() dirname = "foo" abs_dirname = self.os.path.abspath(dirname) self.filesystem.create_dir(dirname) self.os.chdir(dirname) self.assertEqual(abs_dirname, self.os.getcwd()) self.os.chdir("..") self.assertEqual(rootdir, self.os.getcwd()) self.os.chdir(self.os.path.join(dirname, "..")) self.assertEqual(rootdir, self.os.getcwd()) def test_get_cwd(self): # skipping real fs test - can't test root dir self.skip_real_fs() dirname = self.make_path("foo", "bar") self.create_dir(dirname) self.assertEqual(self.filesystem.root_dir_name, self.os.getcwd()) self.os.chdir(dirname) self.assertEqual(dirname, self.os.getcwd()) def test_listdir(self): self.assert_raises_os_error( errno.ENOENT, self.os.listdir, "non_existing/fake_dir" ) directory = self.make_path("xyzzy", "plugh") files = ["foo", "bar", "baz"] for f in files: self.create_file(self.os.path.join(directory, f)) files.sort() self.assertEqual(files, sorted(self.os.listdir(directory))) def test_listdir_uses_open_fd_as_path(self): self.check_posix_only() if os.listdir not in os.supports_fd: self.skip_real_fs() self.assert_raises_os_error(errno.EBADF, self.os.listdir, 500) dir_path = self.make_path("xyzzy", "plugh") files = ["foo", "bar", "baz"] for f in files: self.create_file(self.os.path.join(dir_path, f)) files.sort() path_des = self.os.open(dir_path, os.O_RDONLY) self.assertEqual(files, sorted(self.os.listdir(path_des))) def test_listdir_returns_list(self): directory_root = self.make_path("xyzzy") self.os.mkdir(directory_root) directory = self.os.path.join(directory_root, "bug") self.os.mkdir(directory) self.create_file(self.make_path(directory, "foo")) self.assertEqual(["foo"], self.os.listdir(directory)) def test_listdir_on_symlink(self): skip_if_symlink_not_supported() directory = self.make_path("xyzzy") files = ["foo", "bar", "baz"] for f in files: self.create_file(self.make_path(directory, f)) self.create_symlink(self.make_path("symlink"), self.make_path("xyzzy")) files.sort() self.assertEqual(files, sorted(self.os.listdir(self.make_path("symlink")))) def test_listdir_error(self): file_path = self.make_path("foo", "bar", "baz") self.create_file(file_path) self.assert_raises_os_error(errno.ENOTDIR, self.os.listdir, file_path) def test_exists_current_dir(self): self.assertTrue(self.os.path.exists(".")) def test_listdir_current(self): files = ["foo", "bar", "baz"] for f in files: self.create_file(self.make_path(f)) files.sort() self.assertEqual(files, sorted(self.os.listdir(self.base_path))) def test_fdopen(self): file_path1 = self.make_path("some_file1") self.create_file(file_path1, contents="contents here1") with self.open(file_path1, "r", encoding="utf8") as fake_file1: fileno = fake_file1.fileno() fake_file2 = self.os.fdopen(fileno, encoding="utf8") self.assertNotEqual(fake_file2, fake_file1) with self.assertRaises(TypeError): self.os.fdopen(None) with self.assertRaises(TypeError): self.os.fdopen("a string") def test_out_of_range_fdopen(self): # test some file descriptor that is clearly out of range kwargs = {"encoding": "utf8"} self.assert_raises_os_error(errno.EBADF, self.os.fdopen, 500, **kwargs) def test_closed_file_descriptor(self): first_path = self.make_path("some_file1") second_path = self.make_path("some_file2") third_path = self.make_path("some_file3") self.create_file(first_path, contents="contents here1") self.create_file(second_path, contents="contents here2") self.create_file(third_path, contents="contents here3") fake_file1 = self.open(first_path, "r", encoding="utf8") fake_file2 = self.open(second_path, "r", encoding="utf8") fake_file3 = self.open(third_path, "r", encoding="utf8") fileno1 = fake_file1.fileno() fileno2 = fake_file2.fileno() fileno3 = fake_file3.fileno() self.os.close(fileno2) self.assert_raises_os_error(errno.EBADF, self.os.close, fileno2) self.assertEqual(fileno1, fake_file1.fileno()) self.assertEqual(fileno3, fake_file3.fileno()) with self.os.fdopen(fileno1, encoding="utf8") as f: self.assertFalse(f is fake_file1) with self.os.fdopen(fileno3, encoding="utf8") as f: self.assertFalse(f is fake_file3) kwargs = {"encoding": "utf8"} self.assert_raises_os_error(errno.EBADF, self.os.fdopen, fileno2, **kwargs) def test_fdopen_twice(self): # regression test for #997 file_path = self.make_path("some_file1") self.create_file(file_path, contents="contents here1") fake_file = self.open(file_path, "r", encoding="utf8") fd = fake_file.fileno() # note: we need to assign the files to objects, # otherwise the file will be closed immediately in the CPython implementation # note that this case is not (and will probably not be) handled in pyfakefs file1 = self.open(fd, encoding="utf8") # noqa: F841 file2 = self.open(fd, encoding="utf8") # noqa: F841 self.os.close(fd) def test_open_fd_write_mode_for_ro_file(self): # Create a writable file handle to a read-only file, see #967 file_path = self.make_path("file.txt") fd = self.os.open(file_path, os.O_CREAT | os.O_WRONLY, 0o555) with self.open(fd, "w", encoding="utf8") as out: out.write("hey") with self.open(file_path, encoding="utf8") as f: assert f.read() == "hey" self.os.chmod(file_path, 0o655) def test_open_fd_read_mode_for_ro_file(self): # Create a read-only handle to a read-only file, see #967 file_path = self.make_path("file.txt") fd = self.os.open(file_path, os.O_CREAT | os.O_RDONLY, 0x555) # Attempt to open a write stream to the underlying file out = self.open(fd, "wb") out.flush() # Does not fail: The buffer is empty, so no write() # Fails: Tries to flush a non-empty buffer out.write(b"a") # file.write() may fail by an implicit flush! with self.assertRaises(OSError) as cm: out.flush() assert cm.exception.errno == errno.EBADF # Fails: Tries to flush() again with self.assertRaises(OSError) as cm: out.close() assert cm.exception.errno == errno.EBADF out.close() # Okay: The file is already closed self.os.chmod(file_path, 0o655) def test_fstat(self): directory = self.make_path("xyzzy") file_path = self.os.path.join(directory, "plugh") self.create_file(file_path, contents="ABCDE") with self.open(file_path, encoding="utf8") as file_obj: fileno = file_obj.fileno() self.assertTrue(stat.S_IFREG & self.os.fstat(fileno)[stat.ST_MODE]) self.assertTrue(stat.S_IFREG & self.os.fstat(fileno).st_mode) self.assertEqual(5, self.os.fstat(fileno)[stat.ST_SIZE]) def test_stat(self): directory = self.make_path("xyzzy") file_path = self.os.path.join(directory, "plugh") self.create_file(file_path, contents="ABCDE") self.assertTrue(stat.S_IFDIR & self.os.stat(directory)[stat.ST_MODE]) self.assertTrue(stat.S_IFREG & self.os.stat(file_path)[stat.ST_MODE]) self.assertTrue(stat.S_IFREG & self.os.stat(file_path).st_mode) self.assertEqual(5, self.os.stat(file_path)[stat.ST_SIZE]) def test_st_blocks(self): self.check_posix_only() file_path = self.make_path("foo1") self.create_file(file_path, contents=b"") self.assertEqual(0, self.os.stat(file_path).st_blocks) file_path = self.make_path("foo2") self.create_file(file_path, contents=b"t") self.assertEqual(8, self.os.stat(file_path).st_blocks) file_path = self.make_path("foo3") self.create_file(file_path, contents=b"t" * 4095) self.assertEqual(8, self.os.stat(file_path).st_blocks) file_path = self.make_path("foo4") self.create_file(file_path, contents=b"t" * 4096) self.assertEqual(8, self.os.stat(file_path).st_blocks) file_path = self.make_path("foo5") self.create_file(file_path, contents=b"t" * 4097) self.assertEqual(16, self.os.stat(file_path).st_blocks) def test_no_st_blocks_in_windows(self): self.check_windows_only() file_path = self.make_path("foo") self.create_file(file_path, contents=b"") with self.assertRaises(AttributeError): self.os.stat(file_path).st_blocks def test_stat_with_unc_path(self): self.skip_real_fs() self.check_windows_only() directory = "//root/share/dir" file_path = self.os.path.join(directory, "plugh") self.create_file(file_path, contents="ABCDE") self.assertTrue(stat.S_IFDIR & self.os.stat(directory)[stat.ST_MODE]) self.assertTrue(stat.S_IFREG & self.os.stat(file_path)[stat.ST_MODE]) self.assertTrue(stat.S_IFREG & self.os.stat(file_path).st_mode) self.assertEqual(5, self.os.stat(file_path)[stat.ST_SIZE]) def test_stat_with_drive(self): self.skip_real_fs() self.check_windows_only() directory = "C:/foo/dir" file_path = self.os.path.join(directory, "plugh") self.create_file(file_path, contents="ABCDE") self.assertTrue(stat.S_IFDIR & self.os.stat(directory)[stat.ST_MODE]) self.assertTrue(stat.S_IFREG & self.os.stat(file_path)[stat.ST_MODE]) self.assertTrue(stat.S_IFREG & self.os.stat(file_path).st_mode) self.assertEqual(5, self.os.stat(file_path)[stat.ST_SIZE]) def test_stat_uses_open_fd_as_path(self): self.skip_real_fs() self.assert_raises_os_error(errno.EBADF, self.os.stat, 5) file_path = self.make_path("foo", "bar") self.create_file(file_path) with self.open(file_path, encoding="utf8") as f: self.assertTrue(stat.S_IFREG & self.os.stat(f.filedes)[stat.ST_MODE]) def test_stat_no_follow_symlinks_posix(self): """Test that stat with follow_symlinks=False behaves like lstat.""" self.check_posix_only() directory = self.make_path("xyzzy") base_name = "plugh" file_contents = "frobozz" # Just make sure we didn't accidentally make our test data meaningless. self.assertNotEqual(len(base_name), len(file_contents)) file_path = self.os.path.join(directory, base_name) link_path = self.os.path.join(directory, "link") self.create_file(file_path, contents=file_contents) self.create_symlink(link_path, base_name) self.assertEqual( len(file_contents), self.os.stat(file_path, follow_symlinks=False)[stat.ST_SIZE], ) self.assertEqual( len(base_name), self.os.stat(link_path, follow_symlinks=False)[stat.ST_SIZE], ) def test_stat_no_follow_symlinks_windows(self): """Test that stat with follow_symlinks=False behaves like lstat.""" self.check_windows_only() skip_if_symlink_not_supported() directory = self.make_path("xyzzy") base_name = "plugh" file_contents = "frobozz" # Just make sure we didn't accidentally make our test data meaningless. self.assertNotEqual(len(base_name), len(file_contents)) file_path = self.os.path.join(directory, base_name) link_path = self.os.path.join(directory, "link") self.create_file(file_path, contents=file_contents) self.create_symlink(link_path, base_name) self.assertEqual( len(file_contents), self.os.stat(file_path, follow_symlinks=False)[stat.ST_SIZE], ) self.assertEqual( 0, self.os.stat(link_path, follow_symlinks=False)[stat.ST_SIZE] ) def test_lstat_size_posix(self): self.check_posix_only() directory = self.make_path("xyzzy") base_name = "plugh" file_contents = "frobozz" # Just make sure we didn't accidentally make our test data meaningless. self.assertNotEqual(len(base_name), len(file_contents)) file_path = self.os.path.join(directory, base_name) link_path = self.os.path.join(directory, "link") self.create_file(file_path, contents=file_contents) self.create_symlink(link_path, base_name) self.assertEqual(len(file_contents), self.os.lstat(file_path)[stat.ST_SIZE]) self.assertEqual(len(base_name), self.os.lstat(link_path)[stat.ST_SIZE]) def test_lstat_size_windows(self): self.check_windows_only() skip_if_symlink_not_supported() directory = self.make_path("xyzzy") base_name = "plugh" file_contents = "frobozz" # Just make sure we didn't accidentally make our test data meaningless. self.assertNotEqual(len(base_name), len(file_contents)) file_path = self.os.path.join(directory, base_name) link_path = self.os.path.join(directory, "link") self.create_file(file_path, contents=file_contents) self.create_symlink(link_path, base_name) self.assertEqual(len(file_contents), self.os.lstat(file_path)[stat.ST_SIZE]) self.assertEqual(0, self.os.lstat(link_path)[stat.ST_SIZE]) def test_lstat_trailing_sep(self): # regression test for #342 stat_result = self.os.lstat(self.base_path) self.assertEqual( stat_result, self.os.lstat(self.base_path + self.path_separator()) ) self.assertEqual( stat_result, self.os.lstat( self.base_path + self.path_separator() + self.path_separator() ), ) def test_stat_with_byte_string(self): stat_str = self.os.stat(self.base_path) base_path_bytes = self.base_path.encode("utf8") stat_bytes = self.os.stat(base_path_bytes) self.assertEqual(stat_bytes, stat_str) def test_lstat_with_byte_string(self): stat_str = self.os.lstat(self.base_path) base_path_bytes = self.base_path.encode("utf8") stat_bytes = self.os.lstat(base_path_bytes) self.assertEqual(stat_bytes, stat_str) def test_stat_with_current_dir(self): # regression test for #516 stat_result = self.os.stat(".") lstat_result = self.os.lstat(".") self.assertEqual(stat_result, lstat_result) def test_exists_with_trailing_sep(self): # regression test for #364 file_path = self.make_path("alpha") self.create_file(file_path) self.assertFalse(self.os.path.exists(file_path + self.os.sep)) def test_mkdir_with_trailing_sep(self): # regression test for #367 dir_path = self.make_path("foo") self.os.mkdir(dir_path + self.os.sep + self.os.sep) self.assertTrue(self.os.path.exists(dir_path)) def test_readlink_empty_path(self): self.check_posix_only() self.assert_raises_os_error(errno.ENOENT, self.os.readlink, "") def test_readlink_ending_with_sep_posix(self): # regression test for #359 self.check_posix_only() link_path = self.make_path("foo") self.os.symlink(self.base_path, link_path) self.assert_raises_os_error( errno.EINVAL, self.os.readlink, link_path + self.os.sep ) def test_lstat_symlink_with_trailing_sep_linux(self): # regression test for #366 self.check_linux_only() skip_if_symlink_not_supported() link_path = self.make_path("foo") self.os.symlink(self.base_path, link_path) # used to raise self.assertTrue(self.os.lstat(link_path + self.os.sep).st_mode) def test_lstat_symlink_with_trailing_sep_macos(self): # regression test for #366 self.check_macos_only() skip_if_symlink_not_supported() link_path = self.make_path("foo") self.os.symlink(self.base_path, link_path) # used to raise self.assertTrue(self.os.lstat(link_path + self.os.sep).st_mode) def test_readlink_ending_with_sep_windows(self): self.check_windows_only() skip_if_symlink_not_supported() link_path = self.make_path("foo") self.os.symlink(self.base_path, link_path) self.assert_equal_paths( self.base_path, self.os.readlink(link_path + self.os.sep) ) def test_islink_with_trailing_sep_windows(self): self.check_windows_only() skip_if_symlink_not_supported() link_path = self.make_path("foo") self.os.symlink(self.base_path, link_path) self.assertTrue(self.os.path.islink(link_path + self.os.path.sep)) def test_islink_with_trailing_sep_linux(self): self.check_linux_only() link_path = self.make_path("foo") self.os.symlink(self.base_path, link_path) self.assertFalse(self.os.path.islink(link_path + self.os.sep)) def test_islink_with_trailing_sep_macos(self): self.check_macos_only() link_path = self.make_path("foo") self.os.symlink(self.base_path, link_path) self.assertFalse(self.os.path.islink(link_path + self.os.sep)) def check_getsize_raises_with_trailing_separator(self, error_nr): file_path = self.make_path("bar") self.create_file(file_path) self.assert_raises_os_error( error_nr, self.os.path.getsize, file_path + self.os.sep ) def test_getsize_raises_with_trailing_separator_posix(self): self.check_posix_only() self.check_getsize_raises_with_trailing_separator(errno.ENOTDIR) def test_getsize_raises_with_trailing_separator_windows(self): self.check_windows_only() self.check_getsize_raises_with_trailing_separator(errno.EINVAL) def check_remove_link_ending_with_sep(self, error_nr): # regression test for #360 link_path = self.make_path("foo") self.os.symlink(self.base_path, link_path) self.assert_raises_os_error(error_nr, self.os.remove, link_path + self.os.sep) def test_remove_link_ending_with_sep_linux(self): self.check_linux_only() self.check_remove_link_ending_with_sep(errno.ENOTDIR) def test_remove_link_ending_with_sep_macos(self): self.check_macos_only() self.check_remove_link_ending_with_sep(errno.EPERM) def test_remove_link_ending_with_sep_windows(self): self.check_windows_only() skip_if_symlink_not_supported() self.check_remove_link_ending_with_sep(errno.EACCES) def test_lstat_uses_open_fd_as_path(self): skip_if_symlink_not_supported() if os.lstat not in os.supports_fd: self.skip_real_fs() self.assert_raises_os_error(errno.EBADF, self.os.lstat, 5) file_path = self.make_path("foo", "bar") link_path = self.make_path("foo", "link") file_contents = b"contents" self.create_file(file_path, contents=file_contents) self.create_symlink(link_path, file_path) with self.open(file_path, encoding="utf8") as f: self.assertEqual(len(file_contents), self.os.lstat(f.filedes)[stat.ST_SIZE]) def test_stat_non_existent_file(self): # set up file_path = self.make_path("non", "existent", "file") self.assertFalse(self.os.path.exists(file_path)) # actual tests try: # Use try-catch to check exception attributes. self.os.stat(file_path) self.fail("Exception is expected.") # COV_NF_LINE except OSError as os_error: self.assertEqual(errno.ENOENT, os_error.errno) self.assertEqual(file_path, os_error.filename) def check_open_raises_with_trailing_separator(self, error_nr): file_path = self.make_path("bar") + self.os.sep self.assert_raises_os_error( error_nr, self.os.open, file_path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, ) def test_open_raises_with_trailing_separator_linux(self): self.check_linux_only() self.check_open_raises_with_trailing_separator(errno.EISDIR) def test_open_raises_with_trailing_separator_macos(self): self.check_macos_only() self.check_open_raises_with_trailing_separator(errno.ENOENT) def test_open_raises_with_trailing_separator_windows(self): self.check_windows_only() self.check_open_raises_with_trailing_separator(errno.EINVAL) @unittest.skipIf(not hasattr(os, "O_DIRECTORY"), "opening directory not supported") def test_open_with_o_directory(self): self.check_posix_only() with self.assertRaises(FileNotFoundError): self.os.open("bogus", os.O_RDONLY | os.O_DIRECTORY) file_path = self.make_path("file.txt") self.create_file(file_path, contents="foo") with self.assertRaises(NotADirectoryError): self.os.open(file_path, os.O_RDONLY | os.O_DIRECTORY) dir_path = self.make_path("dir") self.create_dir(dir_path) with self.assertRaises(IsADirectoryError): self.os.open(dir_path, os.O_RDWR | os.O_DIRECTORY) @unittest.skipIf(not hasattr(os, "O_NOFOLLOW"), "NOFOLLOW attribute not supported") def test_open_nofollow_symlink_raises(self): skip_if_symlink_not_supported() file_path = self.make_path("file.txt") self.create_file(file_path, contents="foo") link_path = self.make_path("link") self.create_symlink(link_path, file_path) with self.assertRaises(OSError) as cm: self.os.open(link_path, os.O_RDONLY | os.O_NOFOLLOW) assert cm.exception.errno == errno.ELOOP @unittest.skipIf(not hasattr(os, "O_NOFOLLOW"), "NOFOLLOW attribute not supported") def test_open_nofollow_symlink_as_parent_works(self): skip_if_symlink_not_supported() dir_path = self.make_path("dir") self.create_dir(dir_path) link_path = self.make_path("link") self.create_symlink(link_path, dir_path) file_path = self.os.path.join(link_path, "file.txt") self.create_file(file_path, contents="foo") fd = self.os.open(file_path, os.O_RDONLY | os.O_NOFOLLOW) self.assertGreater(fd, 0) self.os.close(fd) def test_lexists_with_trailing_separator_linux_windows(self): self.check_linux_and_windows() skip_if_symlink_not_supported() file_path = self.make_path("foo") self.os.symlink(file_path, file_path) self.assertFalse(self.os.path.lexists(file_path + self.os.sep)) def test_lexists_with_trailing_separator_macos(self): # regression test for #373 self.check_macos_only() file_path = self.make_path("foo") self.os.symlink(file_path, file_path) self.assertTrue(self.os.path.lexists(file_path + self.os.sep)) def test_islink_with_trailing_separator_linux_windows(self): self.check_linux_and_windows() skip_if_symlink_not_supported() file_path = self.make_path("foo") self.os.symlink(file_path, file_path) self.assertFalse(self.os.path.islink(file_path + self.os.sep)) def test_islink_with_trailing_separator_macos(self): # regression test for #373 self.check_macos_only() file_path = self.make_path("foo") self.os.symlink(file_path, file_path) self.assertTrue(self.os.path.islink(file_path + self.os.sep)) def test_isfile_with_trailing_separator_linux_windows(self): self.check_linux_and_windows() file_path = self.make_path("foo") self.create_file(file_path) self.assertFalse(self.os.path.isfile(file_path + self.os.sep)) def test_isfile_with_trailing_separator_macos(self): # regression test for #374 self.check_macos_only() file_path = self.make_path("foo") self.create_file(file_path) self.assertFalse(self.os.path.isfile(file_path + self.os.sep)) def test_isfile_not_readable_file(self): file_path = self.make_path("foo") self.create_file(file_path, perm=0) self.assertTrue(self.os.path.isfile(file_path)) def check_stat_with_trailing_separator(self, error_nr): # regression test for #376 file_path = self.make_path("foo") self.create_file(file_path) self.assert_raises_os_error(error_nr, self.os.stat, file_path + self.os.sep) def test_stat_with_trailing_separator_posix(self): self.check_posix_only() self.check_stat_with_trailing_separator(errno.ENOTDIR) def test_stat_with_trailing_separator_windows(self): self.check_windows_only() self.check_stat_with_trailing_separator(errno.EINVAL) def check_remove_with_trailing_separator(self, error_nr): # regression test for #377 file_path = self.make_path("foo") self.create_file(file_path) self.assert_raises_os_error(error_nr, self.os.remove, file_path + self.os.sep) def test_remove_with_trailing_separator_posix(self): self.check_posix_only() self.check_remove_with_trailing_separator(errno.ENOTDIR) def test_remove_with_trailing_separator_windows(self): self.check_windows_only() self.check_remove_with_trailing_separator(errno.EINVAL) def test_readlink(self): skip_if_symlink_not_supported() link_path = self.make_path("foo", "bar", "baz") target = self.make_path("tarJAY") self.create_symlink(link_path, target) self.assert_equal_paths(self.os.readlink(link_path), target) def check_readlink_raises_if_path_is_not_a_link(self): file_path = self.make_path("foo", "bar", "eleventyone") self.create_file(file_path) self.assert_raises_os_error(errno.EINVAL, self.os.readlink, file_path) def test_readlink_raises_if_path_is_not_a_link_windows(self): self.check_windows_only() skip_if_symlink_not_supported() self.check_readlink_raises_if_path_is_not_a_link() def test_readlink_raises_if_path_is_not_a_link_posix(self): self.check_posix_only() self.check_readlink_raises_if_path_is_not_a_link() def check_readlink_raises_if_path_has_file(self, error_subtype): self.create_file(self.make_path("a_file")) file_path = self.make_path("a_file", "foo") self.assert_raises_os_error(error_subtype, self.os.readlink, file_path) file_path = self.make_path("a_file", "foo", "bar") self.assert_raises_os_error(error_subtype, self.os.readlink, file_path) def test_readlink_raises_if_path_has_file_windows(self): self.check_windows_only() skip_if_symlink_not_supported() self.check_readlink_raises_if_path_has_file(errno.ENOENT) def test_readlink_raises_if_path_has_file_posix(self): self.check_posix_only() self.check_readlink_raises_if_path_has_file(errno.ENOTDIR) def test_readlink_raises_if_path_does_not_exist(self): skip_if_symlink_not_supported() self.assert_raises_os_error( errno.ENOENT, self.os.readlink, "/this/path/does/not/exist" ) def test_readlink_raises_if_path_is_none(self): skip_if_symlink_not_supported() with self.assertRaises(TypeError): self.os.readlink(None) def test_broken_symlink_with_trailing_separator_linux(self): self.check_linux_only() file_path = self.make_path("foo") link_path = self.make_path("link") self.os.symlink(file_path, link_path) self.assert_raises_os_error( errno.EEXIST, self.os.symlink, link_path + self.os.sep, link_path + self.os.sep, ) def test_broken_symlink_with_trailing_separator_macos(self): # regression test for #371 self.check_macos_only() file_path = self.make_path("foo") link_path = self.make_path("link") self.os.symlink(file_path, link_path) self.os.symlink(link_path + self.os.sep, link_path + self.os.sep) def test_broken_symlink_with_trailing_separator_windows(self): self.check_windows_only() skip_if_symlink_not_supported() file_path = self.make_path("foo") link_path = self.make_path("link") self.os.symlink(file_path, link_path) self.assert_raises_os_error( errno.EINVAL, self.os.symlink, link_path + self.os.sep, link_path + self.os.sep, ) def test_circular_readlink_with_trailing_separator_linux(self): # Regression test for #372 self.check_linux_only() file_path = self.make_path("foo") self.os.symlink(file_path, file_path) self.assert_raises_os_error( errno.ELOOP, self.os.readlink, file_path + self.os.sep ) def test_circular_readlink_with_trailing_separator_macos(self): # Regression test for #372 self.check_macos_only() file_path = self.make_path("foo") self.os.symlink(file_path, file_path) self.os.readlink(file_path + self.os.sep) def test_circular_readlink_with_trailing_separator_windows(self): # Regression test for #372 self.check_windows_only() skip_if_symlink_not_supported() file_path = self.make_path("foo") self.os.symlink(file_path, file_path) self.assert_raises_os_error( errno.EINVAL, self.os.readlink, file_path + self.os.sep ) def test_readlink_with_links_in_path(self): skip_if_symlink_not_supported() self.create_symlink( self.make_path("meyer", "lemon", "pie"), self.make_path("yum") ) self.create_symlink(self.make_path("geo", "metro"), self.make_path("meyer")) self.assert_equal_paths( self.make_path("yum"), self.os.readlink(self.make_path("geo", "metro", "lemon", "pie")), ) def test_readlink_with_chained_links_in_path(self): skip_if_symlink_not_supported() self.create_symlink( self.make_path("eastern", "european", "wolfhounds", "chase"), self.make_path("cats"), ) self.create_symlink( self.make_path("russian"), self.make_path("eastern", "european") ) self.create_symlink( self.make_path("dogs"), self.make_path("russian", "wolfhounds") ) self.assert_equal_paths( self.make_path("cats"), self.os.readlink(self.make_path("dogs", "chase")), ) def check_remove_dir(self, dir_error): directory = self.make_path("xyzzy") dir_path = self.os.path.join(directory, "plugh") self.create_dir(dir_path) self.assertTrue(self.os.path.exists(dir_path)) self.assert_raises_os_error(dir_error, self.os.remove, dir_path) self.assertTrue(self.os.path.exists(dir_path)) self.os.chdir(directory) self.assert_raises_os_error(dir_error, self.os.remove, dir_path) self.assertTrue(self.os.path.exists(dir_path)) self.assert_raises_os_error(errno.ENOENT, self.os.remove, "/plugh") def test_remove_dir_linux(self): self.check_linux_only() self.check_remove_dir(errno.EISDIR) def test_remove_dir_mac_os(self): self.check_macos_only() self.check_remove_dir(errno.EPERM) def test_remove_dir_windows(self): self.check_windows_only() self.check_remove_dir(errno.EACCES) def test_remove_dir_with_drive(self): # regression test for issue #337 self.check_windows_only() self.skip_real_fs() dir_path = self.os.path.join("C:", "test") self.filesystem.create_dir(dir_path) self.assert_raises_os_error(errno.EACCES, self.os.remove, dir_path) def test_remove_file(self): directory = self.make_path("zzy") file_path = self.os.path.join(directory, "plugh") self.create_file(file_path) self.assertTrue(self.os.path.exists(file_path)) self.os.remove(file_path) self.assertFalse(self.os.path.exists(file_path)) def test_remove_file_no_directory(self): directory = self.make_path("zzy") file_name = "plugh" file_path = self.os.path.join(directory, file_name) self.create_file(file_path) self.assertTrue(self.os.path.exists(file_path)) self.os.chdir(directory) self.os.remove(file_name) self.assertFalse(self.os.path.exists(file_path)) def test_remove_file_with_read_permission_raises_in_windows(self): self.check_windows_only() self.skip_root() path = self.make_path("foo", "bar") self.create_file(path) self.os.chmod(path, 0o444) self.assert_raises_os_error(errno.EACCES, self.os.remove, path) self.os.chmod(path, 0o666) def test_remove_file_with_read_permission_shall_succeed_in_posix(self): self.check_posix_only() path = self.make_path("foo", "bar") self.create_file(path) self.os.chmod(path, 0o444) self.os.remove(path) self.assertFalse(self.os.path.exists(path)) def test_remove_file_without_parent_permission_raises_in_posix(self): self.check_posix_only() parent_dir = self.make_path("foo") path = self.os.path.join(parent_dir, "bar") self.create_file(path) self.os.chmod(parent_dir, 0o666) # missing execute permission if not is_root(): self.assert_raises_os_error(errno.EACCES, self.os.remove, path) else: self.os.remove(path) self.assertFalse(self.os.path.exists(path)) self.create_file(path) self.os.chmod(parent_dir, 0o555) # missing write permission if not is_root(): self.assert_raises_os_error(errno.EACCES, self.os.remove, path) else: self.os.remove(path) self.assertFalse(self.os.path.exists(path)) self.create_file(path) self.os.chmod(parent_dir, 0o333) self.os.remove(path) self.assertFalse(self.os.path.exists(path)) def test_remove_open_file_fails_under_windows(self): self.check_windows_only() path = self.make_path("foo", "bar") self.create_file(path) with self.open(path, "r", encoding="utf8"): self.assert_raises_os_error(errno.EACCES, self.os.remove, path) self.assertTrue(self.os.path.exists(path)) def test_remove_open_file_possible_under_posix(self): self.check_posix_only() path = self.make_path("foo", "bar") self.create_file(path) self.open(path, "r", encoding="utf8") self.os.remove(path) self.assertFalse(self.os.path.exists(path)) def test_remove_file_relative_path(self): self.skip_real_fs() original_dir = self.os.getcwd() directory = self.make_path("zzy") subdirectory = self.os.path.join(directory, "zzy") file_name = "plugh" file_path = self.os.path.join(directory, file_name) file_path_relative = self.os.path.join("..", file_name) self.create_file(file_path) self.assertTrue(self.os.path.exists(file_path)) self.create_dir(subdirectory) self.assertTrue(self.os.path.exists(subdirectory)) self.os.chdir(subdirectory) self.os.remove(file_path_relative) self.assertFalse(self.os.path.exists(file_path_relative)) self.os.chdir(original_dir) self.assertFalse(self.os.path.exists(file_path)) def check_remove_dir_raises_error(self, dir_error): directory = self.make_path("zzy") self.create_dir(directory) self.assert_raises_os_error(dir_error, self.os.remove, directory) def test_remove_dir_raises_error_linux(self): self.check_linux_only() self.check_remove_dir_raises_error(errno.EISDIR) def test_remove_dir_raises_error_mac_os(self): self.check_macos_only() self.check_remove_dir_raises_error(errno.EPERM) def test_remove_dir_raises_error_windows(self): self.check_windows_only() self.check_remove_dir_raises_error(errno.EACCES) def test_remove_symlink_to_dir(self): skip_if_symlink_not_supported() directory = self.make_path("zzy") link = self.make_path("link_to_dir") self.create_dir(directory) self.os.symlink(directory, link) self.assertTrue(self.os.path.exists(directory)) self.assertTrue(self.os.path.exists(link)) self.os.remove(link) self.assertTrue(self.os.path.exists(directory)) self.assertFalse(self.os.path.exists(link)) def test_unlink_raises_if_not_exist(self): file_path = self.make_path("file", "does", "not", "exist") self.assertFalse(self.os.path.exists(file_path)) self.assert_raises_os_error(errno.ENOENT, self.os.unlink, file_path) def test_rename_to_nonexistent_file(self): """Can rename a file to an unused name.""" directory = self.make_path("xyzzy") old_file_path = self.os.path.join(directory, "plugh_old") new_file_path = self.os.path.join(directory, "plugh_new") self.create_file(old_file_path, contents="test contents") self.assertTrue(self.os.path.exists(old_file_path)) self.assertFalse(self.os.path.exists(new_file_path)) self.os.rename(old_file_path, new_file_path) self.assertFalse(self.os.path.exists(old_file_path)) self.assertTrue(self.os.path.exists(new_file_path)) self.check_contents(new_file_path, "test contents") def test_rename_dir_to_symlink_posix(self): self.check_posix_only() link_path = self.make_path("link") dir_path = self.make_path("dir") link_target = self.os.path.join(dir_path, "link_target") self.create_dir(dir_path) self.os.symlink(link_target, link_path) self.assert_raises_os_error(errno.ENOTDIR, self.os.rename, dir_path, link_path) def test_rename_dir_to_symlink_windows(self): self.check_windows_only() skip_if_symlink_not_supported() link_path = self.make_path("link") dir_path = self.make_path("dir") link_target = self.os.path.join(dir_path, "link_target") self.create_dir(dir_path) self.os.symlink(link_target, link_path) self.assert_raises_os_error(errno.EEXIST, self.os.rename, dir_path, link_path) def test_rename_file_to_symlink(self): self.check_posix_only() link_path = self.make_path("file_link") file_path = self.make_path("file") self.os.symlink(file_path, link_path) self.create_file(file_path) self.os.rename(file_path, link_path) self.assertFalse(self.os.path.exists(file_path)) self.assertTrue(self.os.path.exists(link_path)) self.assertTrue(self.os.path.isfile(link_path)) def test_rename_symlink_to_symlink(self): self.check_posix_only() base_path = self.make_path("foo", "bar") self.create_dir(base_path) link_path1 = self.os.path.join(base_path, "link1") link_path2 = self.os.path.join(base_path, "link2") self.os.symlink(base_path, link_path1) self.os.symlink(base_path, link_path2) self.os.rename(link_path1, link_path2) self.assertFalse(self.os.path.exists(link_path1)) self.assertTrue(self.os.path.exists(link_path2)) def test_rename_symlink_to_symlink_for_parent_raises(self): self.check_posix_only() dir_link = self.make_path("dir_link") dir_path = self.make_path("dir") dir_in_dir_path = self.os.path.join(dir_link, "inner_dir") self.create_dir(dir_path) self.os.symlink(dir_path, dir_link) self.create_dir(dir_in_dir_path) self.assert_raises_os_error( errno.EINVAL, self.os.rename, dir_path, dir_in_dir_path ) def check_rename_case_with_symlink(self, result): skip_if_symlink_not_supported() self.check_case_insensitive_fs() dir_path_lower = self.make_path("beta") self.create_dir(dir_path_lower) link_path = self.make_path("b") self.os.symlink(self.base_path, link_path) path1 = self.os.path.join(link_path, "Beta") dir_path_upper = self.make_path("Beta") self.os.rename(path1, dir_path_upper) self.assertEqual(result, sorted(self.os.listdir(self.base_path))) def test_rename_case_with_symlink_mac(self): # Regression test for #322 self.check_macos_only() self.check_rename_case_with_symlink(["b", "beta"]) def test_rename_case_with_symlink_windows(self): self.check_windows_only() self.check_rename_case_with_symlink(["Beta", "b"]) def test_recursive_rename_raises(self): self.check_posix_only() base_path = self.make_path("foo", "bar") self.create_dir(base_path) new_path = self.os.path.join(base_path, "new_dir") self.assert_raises_os_error(errno.EINVAL, self.os.rename, base_path, new_path) def test_rename_file_to_parent_dir_file(self): # Regression test for issue 230 dir_path = self.make_path("dir") self.create_dir(dir_path) file_path = self.make_path("old_file") new_file_path = self.os.path.join(dir_path, "new_file") self.create_file(file_path) self.os.rename(file_path, new_file_path) def test_rename_with_target_parent_file_raises_posix(self): self.check_posix_only() file_path = self.make_path("foo", "baz") self.create_file(file_path) self.assert_raises_os_error( errno.ENOTDIR, self.os.rename, file_path, file_path + "/new" ) def test_rename_with_target_parent_file_raises_windows(self): self.check_windows_only() file_path = self.make_path("foo", "baz") self.create_file(file_path) self.assert_raises_os_error( errno.EACCES, self.os.rename, file_path, self.os.path.join(file_path, "new"), ) def test_rename_symlink_to_source(self): self.check_posix_only() base_path = self.make_path("foo") link_path = self.os.path.join(base_path, "slink") file_path = self.os.path.join(base_path, "file") self.create_file(file_path) self.os.symlink(file_path, link_path) self.os.rename(link_path, file_path) self.assertFalse(self.os.path.exists(file_path)) def test_rename_symlink_to_dir_raises(self): self.check_posix_only() base_path = self.make_path("foo", "bar") link_path = self.os.path.join(base_path, "dir_link") dir_path = self.os.path.join(base_path, "dir") self.create_dir(dir_path) self.os.symlink(dir_path, link_path) self.assert_raises_os_error(errno.EISDIR, self.os.rename, link_path, dir_path) def test_rename_broken_symlink(self): self.check_posix_only() base_path = self.make_path("foo") self.create_dir(base_path) link_path = self.os.path.join(base_path, "slink") file_path = self.os.path.join(base_path, "file") self.os.symlink(file_path, link_path) self.os.rename(link_path, file_path) self.assertFalse(self.os.path.exists(file_path)) self.assertTrue(self.os.path.lexists(file_path)) self.assertFalse(self.os.path.exists(link_path)) def test_rename_directory(self): """Can rename a directory to an unused name.""" for old_path, new_path in [("wxyyw", "xyzzy"), ("abccb", "cdeed")]: old_path = self.make_path(old_path) new_path = self.make_path(new_path) self.create_file(self.os.path.join(old_path, "plugh"), contents="test") self.assertTrue(self.os.path.exists(old_path)) self.assertFalse(self.os.path.exists(new_path)) self.os.rename(old_path, new_path) self.assertFalse(self.os.path.exists(old_path)) self.assertTrue(self.os.path.exists(new_path)) self.check_contents(self.os.path.join(new_path, "plugh"), "test") if not self.use_real_fs(): self.assertEqual(3, self.filesystem.get_object(new_path).st_nlink) def check_rename_directory_to_existing_file_raises(self, error_nr): dir_path = self.make_path("dir") file_path = self.make_path("file") self.create_dir(dir_path) self.create_file(file_path) self.assert_raises_os_error(error_nr, self.os.rename, dir_path, file_path) def test_rename_directory_to_existing_file_raises_posix(self): self.check_posix_only() self.check_rename_directory_to_existing_file_raises(errno.ENOTDIR) def test_rename_directory_to_existing_file_raises_windows(self): self.check_windows_only() self.check_rename_directory_to_existing_file_raises(errno.EEXIST) def test_rename_to_existing_directory_should_raise_under_windows(self): """Renaming to an existing directory raises OSError under Windows.""" self.check_windows_only() old_path = self.make_path("foo", "bar") new_path = self.make_path("foo", "baz") self.create_dir(old_path) self.create_dir(new_path) self.assert_raises_os_error(errno.EEXIST, self.os.rename, old_path, new_path) def test_rename_to_a_hardlink_of_same_file_should_do_nothing(self): self.skip_real_fs_failure(skip_posix=False) skip_if_symlink_not_supported() file_path = self.make_path("dir", "file") self.create_file(file_path) link_path = self.make_path("link") self.os.link(file_path, link_path) self.os.rename(file_path, link_path) self.assertTrue(self.os.path.exists(file_path)) self.assertTrue(self.os.path.exists(link_path)) def test_hardlink_works_with_symlink(self): skip_if_symlink_not_supported() base_path = self.make_path("foo") self.create_dir(base_path) symlink_path = self.os.path.join(base_path, "slink") self.os.symlink(base_path, symlink_path) file_path = self.os.path.join(base_path, "slink", "beta") self.create_file(file_path) link_path = self.os.path.join(base_path, "slink", "gamma") self.os.link(file_path, link_path) self.assertTrue(self.os.path.exists(link_path)) self.assertFalse(self.os.path.islink(link_path)) def test_replace_existing_directory_should_raise_under_windows(self): """Renaming to an existing directory raises OSError under Windows.""" self.check_windows_only() old_path = self.make_path("foo", "bar") new_path = self.make_path("foo", "baz") self.create_dir(old_path) self.create_dir(new_path) self.assert_raises_os_error(errno.EACCES, self.os.replace, old_path, new_path) def test_rename_to_existing_directory_under_posix(self): """Renaming to an existing directory changes the existing directory under Posix.""" self.check_posix_only() old_path = self.make_path("foo", "bar") new_path = self.make_path("xyzzy") self.create_dir(self.os.path.join(old_path, "sub")) self.create_dir(new_path) self.os.rename(old_path, new_path) self.assertTrue(self.os.path.exists(self.os.path.join(new_path, "sub"))) self.assertFalse(self.os.path.exists(old_path)) def test_rename_file_to_existing_directory_raises_under_posix(self): self.check_posix_only() file_path = self.make_path("foo", "bar", "baz") new_path = self.make_path("xyzzy") self.create_file(file_path) self.create_dir(new_path) self.assert_raises_os_error(errno.EISDIR, self.os.rename, file_path, new_path) def test_rename_to_existing_dir_under_posix_raises_if_not_empty(self): """Renaming to an existing directory changes the existing directory under Posix.""" self.check_posix_only() old_path = self.make_path("foo", "bar") new_path = self.make_path("foo", "baz") self.create_dir(self.os.path.join(old_path, "sub")) self.create_dir(self.os.path.join(new_path, "sub")) # not testing specific subtype: # raises errno.ENOTEMPTY under Ubuntu 16.04, MacOS and pyfakefs # but raises errno.EEXIST at least under Ubunto 14.04 with self.assertRaises(OSError): self.os.rename(old_path, new_path) def test_rename_to_another_device_should_raise(self): """Renaming to another filesystem device raises OSError.""" self.skip_real_fs() self.filesystem.add_mount_point("/mount") old_path = "/foo/bar" new_path = "/mount/bar" self.filesystem.create_file(old_path) self.assert_raises_os_error(errno.EXDEV, self.os.rename, old_path, new_path) def test_rename_to_existent_file_posix(self): """Can rename a file to a used name under Unix.""" self.check_posix_only() directory = self.make_path("xyzzy") old_file_path = self.os.path.join(directory, "plugh_old") new_file_path = self.os.path.join(directory, "plugh_new") self.create_file(old_file_path, contents="test contents 1") self.create_file(new_file_path, contents="test contents 2") self.assertTrue(self.os.path.exists(old_file_path)) self.assertTrue(self.os.path.exists(new_file_path)) self.os.rename(old_file_path, new_file_path) self.assertFalse(self.os.path.exists(old_file_path)) self.assertTrue(self.os.path.exists(new_file_path)) self.check_contents(new_file_path, "test contents 1") def test_rename_to_existent_file_windows(self): """Renaming a file to a used name raises OSError under Windows.""" self.check_windows_only() directory = self.make_path("xyzzy") old_file_path = self.os.path.join(directory, "plugh_old") new_file_path = self.os.path.join(directory, "plugh_new") self.create_file(old_file_path, contents="test contents 1") self.create_file(new_file_path, contents="test contents 2") self.assertTrue(self.os.path.exists(old_file_path)) self.assertTrue(self.os.path.exists(new_file_path)) self.assert_raises_os_error( errno.EEXIST, self.os.rename, old_file_path, new_file_path ) def test_replace_to_existent_file(self): """Replaces an existing file (does not work with `rename()` under Windows).""" directory = self.make_path("xyzzy") old_file_path = self.os.path.join(directory, "plugh_old") new_file_path = self.os.path.join(directory, "plugh_new") self.create_file(old_file_path, contents="test contents 1") self.create_file(new_file_path, contents="test contents 2") self.assertTrue(self.os.path.exists(old_file_path)) self.assertTrue(self.os.path.exists(new_file_path)) self.os.replace(old_file_path, new_file_path) self.assertFalse(self.os.path.exists(old_file_path)) self.assertTrue(self.os.path.exists(new_file_path)) self.check_contents(new_file_path, "test contents 1") def test_rename_to_nonexistent_dir(self): """Can rename a file to a name in a nonexistent dir.""" directory = self.make_path("xyzzy") old_file_path = self.os.path.join(directory, "plugh_old") new_file_path = self.os.path.join(directory, "no_such_path", "plugh_new") self.create_file(old_file_path, contents="test contents") self.assertTrue(self.os.path.exists(old_file_path)) self.assertFalse(self.os.path.exists(new_file_path)) self.assert_raises_os_error( errno.ENOENT, self.os.rename, old_file_path, new_file_path ) self.assertTrue(self.os.path.exists(old_file_path)) self.assertFalse(self.os.path.exists(new_file_path)) self.check_contents(old_file_path, "test contents") def test_rename_nonexistent_file_should_raise_error(self): """Can't rename a file that doesn't exist.""" self.assert_raises_os_error( errno.ENOENT, self.os.rename, "nonexistent-foo", "doesn't-matter-bar", ) def test_rename_empty_dir(self): """Test a rename of an empty directory.""" directory = self.make_path("xyzzy") before_dir = self.os.path.join(directory, "empty") after_dir = self.os.path.join(directory, "unused") self.create_dir(before_dir) self.assertTrue(self.os.path.exists(self.os.path.join(before_dir, "."))) self.assertFalse(self.os.path.exists(after_dir)) self.os.rename(before_dir, after_dir) self.assertFalse(self.os.path.exists(before_dir)) self.assertTrue(self.os.path.exists(self.os.path.join(after_dir, "."))) def test_rename_symlink(self): self.check_posix_only() base_path = self.make_path("foo", "bar") self.create_dir(base_path) link_path = self.os.path.join(base_path, "link") self.os.symlink(base_path, link_path) file_path = self.os.path.join(link_path, "file") new_file_path = self.os.path.join(link_path, "new") self.create_file(file_path) self.os.rename(file_path, new_file_path) self.assertFalse(self.os.path.exists(file_path)) self.assertTrue(self.os.path.exists(new_file_path)) def check_append_mode_tell_after_truncate(self, tell_result): file_path = self.make_path("baz") with self.open(file_path, "w", encoding="utf8") as f0: with self.open(file_path, "a", encoding="utf8") as f1: f1.write("abcde") f0.seek(2) f0.truncate() self.assertEqual(tell_result, f1.tell()) with self.open(file_path, mode="rb") as f: self.assertEqual(b"\0\0abcde", f.read()) def test_append_mode_tell_linux_windows(self): # Regression test for #300 self.check_linux_and_windows() self.check_append_mode_tell_after_truncate(7) def test_append_mode_tell_macos(self): # Regression test for #300 self.check_macos_only() self.check_append_mode_tell_after_truncate(7) def test_tell_after_seek_in_append_mode(self): # Regression test for #363 file_path = self.make_path("foo") with self.open(file_path, "a", encoding="utf8") as f: f.seek(1) self.assertEqual(1, f.tell()) def test_tell_after_seekback_in_append_mode(self): # Regression test for #414 file_path = self.make_path("foo") with self.open(file_path, "a", encoding="utf8") as f: f.write("aa") f.seek(1) self.assertEqual(1, f.tell()) def test_dir_with_trailing_sep_is_dir(self): # regression test for #387 self.assertTrue(self, self.os.path.isdir(self.base_path + self.os.sep)) def check_rename_dir_with_trailing_sep(self, error): dir_path = self.make_path("dir") + self.os.sep self.os.mkdir(dir_path) self.assert_raises_os_error(error, self.os.rename, dir_path, self.base_path) def test_rename_dir_with_trailing_sep_posix(self): # regression test for #406 self.check_posix_only() self.check_rename_dir_with_trailing_sep(errno.ENOTEMPTY) def test_rename_dir_with_trailing_sep_windows(self): self.check_windows_only() self.check_rename_dir_with_trailing_sep(errno.EEXIST) def test_rename_dir(self): """Test a rename of a directory.""" directory = self.make_path("xyzzy") before_dir = self.os.path.join(directory, "before") before_file = self.os.path.join(directory, "before", "file") after_dir = self.os.path.join(directory, "after") after_file = self.os.path.join(directory, "after", "file") self.create_dir(before_dir) self.create_file(before_file, contents="payload") self.assertTrue(self.os.path.exists(before_dir)) self.assertTrue(self.os.path.exists(before_file)) self.assertFalse(self.os.path.exists(after_dir)) self.assertFalse(self.os.path.exists(after_file)) self.os.rename(before_dir, after_dir) self.assertFalse(self.os.path.exists(before_dir)) self.assertFalse(self.os.path.exists(before_file)) self.assertTrue(self.os.path.exists(after_dir)) self.assertTrue(self.os.path.exists(after_file)) self.check_contents(after_file, "payload") def test_rename_preserves_stat(self): """Test if rename preserves mtime.""" self.check_posix_only() self.skip_real_fs() directory = self.make_path("xyzzy") old_file_path = self.os.path.join(directory, "plugh_old") new_file_path = self.os.path.join(directory, "plugh_new") self.create_file(old_file_path) old_file = self.filesystem.get_object(old_file_path) old_file.st_mtime = old_file.st_mtime - 3600 self.os.chown(old_file_path, 200, 200) self.os.chmod(old_file_path, 0o222) self.create_file(new_file_path) new_file = self.filesystem.get_object(new_file_path) self.assertNotEqual(new_file.st_mtime, old_file.st_mtime) self.os.rename(old_file_path, new_file_path) new_file = self.filesystem.get_object(new_file_path) self.assertEqual(new_file.st_mtime, old_file.st_mtime) self.assertEqual(new_file.st_mode, old_file.st_mode) self.assertEqual(new_file.st_uid, old_file.st_uid) self.assertEqual(new_file.st_gid, old_file.st_gid) def test_rename_same_filenames(self): """Test renaming when old and new names are the same.""" directory = self.make_path("xyzzy") file_contents = "Spam eggs" file_path = self.os.path.join(directory, "eggs") self.create_file(file_path, contents=file_contents) self.os.rename(file_path, file_path) self.check_contents(file_path, file_contents) def test_rmdir(self): """Can remove a directory.""" directory = self.make_path("xyzzy") sub_dir = self.make_path("xyzzy", "abccd") other_dir = self.make_path("xyzzy", "cdeed") self.create_dir(directory) self.assertTrue(self.os.path.exists(directory)) self.os.rmdir(directory) self.assertFalse(self.os.path.exists(directory)) self.create_dir(sub_dir) self.create_dir(other_dir) self.os.chdir(sub_dir) self.os.rmdir("../cdeed") self.assertFalse(self.os.path.exists(other_dir)) self.os.chdir("..") self.os.rmdir("abccd") self.assertFalse(self.os.path.exists(sub_dir)) def test_rmdir_raises_if_not_empty(self): """Raises an exception if the target directory is not empty.""" directory = self.make_path("xyzzy") file_path = self.os.path.join(directory, "plugh") self.create_file(file_path) self.assertTrue(self.os.path.exists(file_path)) self.assert_raises_os_error(errno.ENOTEMPTY, self.os.rmdir, directory) def check_rmdir_raises_if_not_directory(self, error_nr): """Raises an exception if the target is not a directory.""" directory = self.make_path("xyzzy") file_path = self.os.path.join(directory, "plugh") self.create_file(file_path) self.assertTrue(self.os.path.exists(file_path)) self.assert_raises_os_error(errno.ENOTDIR, self.os.rmdir, file_path) self.assert_raises_os_error(error_nr, self.os.rmdir, ".") def test_rmdir_raises_if_not_directory_posix(self): self.check_posix_only() self.check_rmdir_raises_if_not_directory(errno.EINVAL) def test_rmdir_raises_if_not_directory_windows(self): self.check_windows_only() self.check_rmdir_raises_if_not_directory(errno.EACCES) def test_rmdir_raises_if_not_exist(self): """Raises an exception if the target does not exist.""" directory = self.make_path("xyzzy") self.assertFalse(self.os.path.exists(directory)) self.assert_raises_os_error(errno.ENOENT, self.os.rmdir, directory) def test_rmdir_via_symlink(self): self.check_windows_only() skip_if_symlink_not_supported() base_path = self.make_path("foo", "bar") dir_path = self.os.path.join(base_path, "alpha") self.create_dir(dir_path) link_path = self.os.path.join(base_path, "beta") self.os.symlink(base_path, link_path) self.os.rmdir(link_path + "/alpha") self.assertFalse(self.os.path.exists(dir_path)) def remove_dirs_check(self, directory): self.assertTrue(self.os.path.exists(directory)) self.os.removedirs(directory) return not self.os.path.exists(directory) def test_removedirs(self): # no exception raised self.skip_real_fs() data = [ "test1", ("test1", "test2"), ("test1", "extra"), ("test1", "test2", "test3"), ] for directory in data: self.create_dir(self.make_path(directory)) self.assertTrue(self.os.path.exists(self.make_path(directory))) self.assert_raises_os_error( errno.ENOTEMPTY, self.remove_dirs_check, self.make_path(data[0]) ) self.assert_raises_os_error( errno.ENOTEMPTY, self.remove_dirs_check, self.make_path(data[1]) ) self.assertTrue(self.remove_dirs_check(self.make_path(data[3]))) self.assertTrue(self.os.path.exists(self.make_path(data[0]))) self.assertFalse(self.os.path.exists(self.make_path(data[1]))) self.assertTrue(self.os.path.exists(self.make_path(data[2]))) # Should raise because '/test1/extra' is all that is left, and # removedirs('/test1/extra') will eventually try to rmdir('/'). self.assert_raises_os_error( errno.EBUSY, self.remove_dirs_check, self.make_path(data[2]) ) # However, it will still delete '/test1') in the process. self.assertFalse(self.os.path.exists(self.make_path(data[0]))) self.create_dir(self.make_path("test1", "test2")) # Add this to the root directory to avoid raising an exception. self.filesystem.create_dir(self.make_path("test3")) self.assertTrue(self.remove_dirs_check(self.make_path("test1", "test2"))) self.assertFalse(self.os.path.exists(self.make_path("test1", "test2"))) self.assertFalse(self.os.path.exists(self.make_path("test1"))) def test_removedirs_raises_if_removing_root(self): """Raises exception if asked to remove '/'.""" self.skip_real_fs() self.os.rmdir(self.base_path) directory = self.os.path.splitdrive(self.base_path)[0] + self.os.path.sep self.assertTrue(self.os.path.exists(directory)) self.assert_raises_os_error(errno.EBUSY, self.os.removedirs, directory) def test_removedirs_raises_if_cascade_removing_root(self): """Raises exception if asked to remove '/' as part of a larger operation. All of other directories should still be removed, though. """ self.skip_real_fs() directory = self.make_path("foo", "bar") self.create_dir(directory) self.assertTrue(self.os.path.exists(directory)) self.assert_raises_os_error(errno.EBUSY, self.os.removedirs, directory) head, unused_tail = self.os.path.split(directory) while self.os.path.splitdrive(head)[1] != self.os.path.sep: self.assertFalse(self.os.path.exists(directory)) head, unused_tail = self.os.path.split(head) def test_removedirs_with_trailing_slash(self): """removedirs works on directory names with trailing slashes.""" # separate this case from the removing-root-directory case self.create_dir(self.make_path("baz")) directory = self.make_path("foo", "bar") self.create_dir(directory) self.assertTrue(self.os.path.exists(directory)) self.os.removedirs(directory) self.assertFalse(self.os.path.exists(directory)) def test_remove_dirs_with_top_symlink_fails(self): self.check_posix_only() dir_path = self.make_path("dir") dir_link = self.make_path("dir_link") self.create_dir(dir_path) self.os.symlink(dir_path, dir_link) self.assert_raises_os_error(errno.ENOTDIR, self.os.removedirs, dir_link) def test_remove_dirs_with_non_top_symlink_succeeds(self): self.check_posix_only() dir_path = self.make_path("dir") dir_link = self.make_path("dir_link") self.create_dir(dir_path) self.os.symlink(dir_path, dir_link) dir_in_dir = self.os.path.join(dir_link, "dir2") self.create_dir(dir_in_dir) self.os.removedirs(dir_in_dir) self.assertFalse(self.os.path.exists(dir_in_dir)) # ensure that the symlink is not removed self.assertTrue(self.os.path.exists(dir_link)) def test_mkdir(self): """mkdir can create a relative directory.""" self.skip_real_fs() directory = "xyzzy" self.assertFalse(self.filesystem.exists(directory)) self.os.mkdir(directory) self.assertTrue(self.filesystem.exists("/%s" % directory)) self.os.chdir(directory) self.os.mkdir(directory) self.assertTrue(self.filesystem.exists(f"/{directory}/{directory}")) self.os.chdir(directory) self.os.mkdir("../abccb") self.assertTrue(self.os.path.exists("/%s/abccb" % directory)) def test_mkdir_with_trailing_slash(self): """mkdir can create a directory named with a trailing slash.""" directory = self.make_path("foo") self.assertFalse(self.os.path.exists(directory)) self.os.mkdir(directory) self.assertTrue(self.os.path.exists(directory)) self.assertTrue(self.os.path.exists(self.make_path("foo"))) def test_mkdir_raises_if_empty_directory_name(self): """mkdir raises exception if creating directory named ''.""" directory = "" self.assert_raises_os_error(errno.ENOENT, self.os.mkdir, directory) def test_mkdir_raises_if_no_parent(self): """mkdir raises exception if parent directory does not exist.""" parent = "xyzzy" directory = f"{parent}/foo" self.assertFalse(self.os.path.exists(parent)) self.assert_raises_os_error(errno.ENOENT, self.os.mkdir, directory) def test_mkdir_raises_on_symlink_in_posix(self): self.check_posix_only() base_path = self.make_path("foo", "bar") link_path = self.os.path.join(base_path, "link_to_dir") dir_path = self.os.path.join(base_path, "dir") self.create_dir(dir_path) self.os.symlink(dir_path, link_path) self.assert_raises_os_error(errno.ENOTDIR, self.os.rmdir, link_path) def test_mkdir_removes_symlink_in_windows(self): self.check_windows_only() skip_if_symlink_not_supported() base_path = self.make_path("foo", "bar") link_path = self.os.path.join(base_path, "link_to_dir") dir_path = self.os.path.join(base_path, "dir") self.create_dir(dir_path) self.os.symlink(dir_path, link_path) self.os.rmdir(link_path) self.assertFalse(self.os.path.exists(link_path)) self.assertTrue(self.os.path.exists(dir_path)) def test_mkdir_raises_if_directory_exists(self): """mkdir raises exception if directory already exists.""" directory = self.make_path("xyzzy") self.create_dir(directory) self.assertTrue(self.os.path.exists(directory)) self.assert_raises_os_error(errno.EEXIST, self.os.mkdir, directory) def test_mkdir_raises_if_file_exists(self): """mkdir raises exception if name already exists as a file.""" directory = self.make_path("xyzzy") file_path = self.os.path.join(directory, "plugh") self.create_file(file_path) self.assertTrue(self.os.path.exists(file_path)) self.assert_raises_os_error(errno.EEXIST, self.os.mkdir, file_path) def check_mkdir_raises_if_parent_is_file(self, error_type): """mkdir raises exception if name already exists as a file.""" directory = self.make_path("xyzzy") file_path = self.os.path.join(directory, "plugh") self.create_file(file_path) self.assert_raises_os_error( error_type, self.os.mkdir, self.os.path.join(file_path, "ff") ) def test_mkdir_raises_if_parent_is_file_posix(self): self.check_posix_only() self.check_mkdir_raises_if_parent_is_file(errno.ENOTDIR) def test_mkdir_raises_if_parent_is_file_windows(self): self.check_windows_only() self.check_mkdir_raises_if_parent_is_file(errno.ENOENT) def test_mkdir_raises_with_slash_dot_posix(self): """mkdir raises exception if mkdir foo/. (trailing /.).""" self.check_posix_only() self.assert_raises_os_error(errno.EEXIST, self.os.mkdir, self.os.sep + ".") directory = self.make_path("xyzzy", ".") self.assert_raises_os_error(errno.ENOENT, self.os.mkdir, directory) self.create_dir(self.make_path("xyzzy")) self.assert_raises_os_error(errno.EEXIST, self.os.mkdir, directory) def test_mkdir_raises_with_slash_dot_windows(self): """mkdir raises exception if mkdir foo/. (trailing /.).""" self.check_windows_only() self.assert_raises_os_error(errno.EACCES, self.os.mkdir, self.os.sep + ".") directory = self.make_path("xyzzy", ".") self.os.mkdir(directory) self.create_dir(self.make_path("xyzzy")) self.assert_raises_os_error(errno.EEXIST, self.os.mkdir, directory) def test_mkdir_raises_with_double_dots_posix(self): """mkdir raises exception if mkdir foo/foo2/../foo3.""" self.check_posix_only() self.assert_raises_os_error(errno.EEXIST, self.os.mkdir, self.os.sep + "..") directory = self.make_path("xyzzy", "dir1", "dir2", "..", "..", "dir3") self.assert_raises_os_error(errno.ENOENT, self.os.mkdir, directory) self.create_dir(self.make_path("xyzzy")) self.assert_raises_os_error(errno.ENOENT, self.os.mkdir, directory) self.create_dir(self.make_path("xyzzy", "dir1")) self.assert_raises_os_error(errno.ENOENT, self.os.mkdir, directory) self.create_dir(self.make_path("xyzzy", "dir1", "dir2")) self.os.mkdir(directory) self.assertTrue(self.os.path.exists(directory)) directory = self.make_path("xyzzy", "dir1", "..") self.assert_raises_os_error(errno.EEXIST, self.os.mkdir, directory) def test_mkdir_raises_with_double_dots_windows(self): """mkdir raises exception if mkdir foo/foo2/../foo3.""" self.check_windows_only() self.assert_raises_os_error(errno.EACCES, self.os.mkdir, self.os.sep + "..") directory = self.make_path("xyzzy", "dir1", "dir2", "..", "..", "dir3") self.assert_raises_os_error(errno.ENOENT, self.os.mkdir, directory) self.create_dir(self.make_path("xyzzy")) self.os.mkdir(directory) self.assertTrue(self.os.path.exists(directory)) directory = self.make_path("xyzzy", "dir1", "..") self.assert_raises_os_error(errno.EEXIST, self.os.mkdir, directory) def test_mkdir_raises_if_parent_is_read_only(self): """mkdir raises exception if parent is read only.""" self.check_posix_only() directory = self.make_path("a") self.os.mkdir(directory) # Change directory permissions to be read only. self.os.chmod(directory, 0o400) directory = self.make_path("a", "b") if not is_root(): self.assert_raises_os_error(errno.EACCES, self.os.mkdir, directory) else: self.os.mkdir(directory) self.assertTrue(self.os.path.exists(directory)) def test_mkdir_with_with_symlink_parent(self): self.check_posix_only() dir_path = self.make_path("foo", "bar") self.create_dir(dir_path) link_path = self.make_path("foo", "link") self.os.symlink(dir_path, link_path) new_dir = self.os.path.join(link_path, "new_dir") self.os.mkdir(new_dir) self.assertTrue(self.os.path.exists(new_dir)) def test_makedirs(self): """makedirs can create a directory even if parent does not exist.""" parent = self.make_path("xyzzy") directory = self.os.path.join(parent, "foo") self.assertFalse(self.os.path.exists(parent)) self.os.makedirs(directory) self.assertTrue(self.os.path.exists(directory)) def check_makedirs_raises_if_parent_is_file(self, error_type): """makedirs raises exception if a parent component exists as a file.""" file_path = self.make_path("xyzzy") directory = self.os.path.join(file_path, "plugh") self.create_file(file_path) self.assertTrue(self.os.path.exists(file_path)) self.assert_raises_os_error(error_type, self.os.makedirs, directory) def test_makedirs_raises_if_parent_is_file_posix(self): self.check_posix_only() self.check_makedirs_raises_if_parent_is_file(errno.ENOTDIR) def test_makedirs_raises_if_parent_is_file_windows(self): self.check_windows_only() self.check_makedirs_raises_if_parent_is_file(errno.ENOENT) def test_makedirs_raises_if_parent_is_broken_link(self): self.check_posix_only() link_path = self.make_path("broken_link") self.os.symlink(self.make_path("bogus"), link_path) self.assert_raises_os_error( errno.ENOENT, self.os.makedirs, self.os.path.join(link_path, "newdir"), ) def test_makedirs_raises_if_parent_is_looping_link(self): skip_if_symlink_not_supported() link_path = self.make_path("link") link_target = self.os.path.join(link_path, "link") self.os.symlink(link_target, link_path) self.assert_raises_os_error(errno.EEXIST, self.os.makedirs, link_path) def test_makedirs_if_parent_is_symlink(self): self.check_posix_only() base_dir = self.make_path("foo", "bar") self.create_dir(base_dir) link_dir = self.os.path.join(base_dir, "linked") self.os.symlink(base_dir, link_dir) new_dir = self.os.path.join(link_dir, "f") self.os.makedirs(name=new_dir) self.assertTrue(self.os.path.exists(new_dir)) def test_makedirs_raises_if_access_denied(self): """makedirs raises exception if access denied.""" self.check_posix_only() directory = self.make_path("a") self.os.mkdir(directory) # Change directory permissions to be read only. self.os.chmod(directory, 0o400) directory = self.make_path("a", "b") if not is_root(): with self.assertRaises(OSError): self.os.makedirs(directory) else: self.os.makedirs(directory) self.assertTrue(self.os.path.exists(directory)) def test_makedirs_exist_ok(self): """makedirs uses the exist_ok argument""" directory = self.make_path("xyzzy", "foo") self.create_dir(directory) self.assertTrue(self.os.path.exists(directory)) self.assert_raises_os_error(errno.EEXIST, self.os.makedirs, directory) self.os.makedirs(directory, exist_ok=True) self.assertTrue(self.os.path.exists(directory)) def test_makedirs_in_write_protected_dir(self): self.check_posix_only() directory = self.make_path("foo") self.os.mkdir(directory, mode=0o555) subdir = self.os.path.join(directory, "bar") if not is_root(): self.assert_raises_os_error( errno.EACCES, self.os.makedirs, subdir, exist_ok=True ) self.assert_raises_os_error( errno.EACCES, self.os.makedirs, subdir, exist_ok=False ) else: self.os.makedirs(subdir) self.assertTrue(self.os.path.exists(subdir)) def test_makedirs_raises_on_empty_path(self): self.assert_raises_os_error(errno.ENOENT, self.os.makedirs, "", exist_ok=False) self.assert_raises_os_error(errno.ENOENT, self.os.makedirs, "", exist_ok=True) def test_makedirs_with_relative_paths(self): # regression test for #987 path = self.make_path("base", "foo", "..", "bar") self.os.makedirs(path) self.assertTrue(self.os.path.isdir(self.make_path("base", "bar"))) self.assertTrue(self.os.path.isdir(self.make_path("base", "foo"))) self.assertFalse(self.os.path.isdir(self.make_path("base", "foo", "bar"))) # test fsync and fdatasync def test_fsync_raises_on_non_int(self): with self.assertRaises(TypeError): self.os.fsync("zero") def test_fdatasync_raises_on_non_int(self): self.check_linux_only() self.assertRaises(TypeError, self.os.fdatasync, "zero") def test_fsync_raises_on_invalid_fd(self): self.assert_raises_os_error(errno.EBADF, self.os.fsync, 500) def test_fdatasync_raises_on_invalid_fd(self): # No open files yet self.check_linux_only() self.assert_raises_os_error(errno.EINVAL, self.os.fdatasync, 0) self.assert_raises_os_error(errno.EBADF, self.os.fdatasync, 500) def test_fsync_pass_posix(self): self.check_posix_only() test_file_path = self.make_path("test_file") self.create_file(test_file_path, contents="dummy file contents") with self.open(test_file_path, "r", encoding="utf8") as test_file: test_fd = test_file.fileno() # Test that this doesn't raise anything self.os.fsync(test_fd) # And just for sanity, double-check that this still raises self.assert_raises_os_error(errno.EBADF, self.os.fsync, test_fd + 500) def test_fsync_pass_windows(self): self.check_windows_only() test_file_path = self.make_path("test_file") self.create_file(test_file_path, contents="dummy file contents") with self.open(test_file_path, "r+", encoding="utf8") as test_file: test_fd = test_file.fileno() # Test that this doesn't raise anything self.os.fsync(test_fd) # And just for sanity, double-check that this still raises self.assert_raises_os_error(errno.EBADF, self.os.fsync, test_fd + 500) with self.open(test_file_path, "r", encoding="utf8") as test_file: test_fd = test_file.fileno() self.assert_raises_os_error(errno.EBADF, self.os.fsync, test_fd) def test_fdatasync_pass(self): # setup self.check_linux_only() test_file_path = self.make_path("test_file") self.create_file(test_file_path, contents="dummy file contents") test_file = self.open(test_file_path, "r", encoding="utf8") test_fd = test_file.fileno() # Test that this doesn't raise anything self.os.fdatasync(test_fd) # And just for sanity, double-check that this still raises self.assert_raises_os_error(errno.EBADF, self.os.fdatasync, test_fd + 500) def test_access700(self): # set up self.check_posix_only() path = self.make_path("some_file") self.createTestFile(path) self.os.chmod(path, 0o700) self.assert_mode_equal(0o700, self.os.stat(path).st_mode) # actual tests self.assertTrue(self.os.access(path, self.os.F_OK)) self.assertTrue(self.os.access(path, self.os.R_OK)) self.assertTrue(self.os.access(path, self.os.W_OK)) self.assertTrue(self.os.access(path, self.os.X_OK)) self.assertTrue(self.os.access(path, self.rwx)) def test_access600(self): # set up self.check_posix_only() path = self.make_path("some_file") self.createTestFile(path) self.os.chmod(path, 0o600) self.assert_mode_equal(0o600, self.os.stat(path).st_mode) # actual tests self.assertTrue(self.os.access(path, self.os.F_OK)) self.assertTrue(self.os.access(path, self.os.R_OK)) self.assertTrue(self.os.access(path, self.os.W_OK)) self.assertFalse(self.os.access(path, self.os.X_OK)) self.assertFalse(self.os.access(path, self.rwx)) self.assertTrue(self.os.access(path, self.rw)) def test_access400(self): # set up self.check_posix_only() path = self.make_path("some_file") self.createTestFile(path) self.os.chmod(path, 0o400) self.assert_mode_equal(0o400, self.os.stat(path).st_mode) # actual tests self.assertTrue(self.os.access(path, self.os.F_OK)) self.assertTrue(self.os.access(path, self.os.R_OK)) self.assertFalse(self.os.access(path, self.os.X_OK)) self.assertFalse(self.os.access(path, self.rwx)) if is_root(): self.assertTrue(self.os.access(path, self.os.W_OK)) self.assertTrue(self.os.access(path, self.rw)) else: self.assertFalse(self.os.access(path, self.os.W_OK)) self.assertFalse(self.os.access(path, self.rw)) def test_access_symlink(self): skip_if_symlink_not_supported() self.skip_real_fs() path = self.make_path("some_file") self.createTestFile(path) link_path = self.make_path("link_to_some_file") self.create_symlink(link_path, path) self.os.chmod(link_path, 0o400) # test file self.assertTrue(self.os.access(link_path, self.os.F_OK)) self.assertTrue(self.os.access(link_path, self.os.R_OK)) if is_root(): self.assertTrue(self.os.access(link_path, self.os.W_OK)) self.assertTrue(self.os.access(link_path, self.rw)) else: self.assertFalse(self.os.access(link_path, self.os.W_OK)) self.assertFalse(self.os.access(link_path, self.rw)) self.assertFalse(self.os.access(link_path, self.os.X_OK)) self.assertFalse(self.os.access(link_path, self.rwx)) # test link itself (default under Windows, follow_symlinks not supported) follow_symlinks = None if self.is_windows_fs else False self.assertTrue( self.os.access(link_path, self.os.F_OK, follow_symlinks=follow_symlinks) ) self.assertTrue( self.os.access(link_path, self.os.R_OK, follow_symlinks=follow_symlinks) ) self.assertTrue( self.os.access(link_path, self.os.W_OK, follow_symlinks=follow_symlinks) ) if not self.is_windows_fs: self.assertTrue( self.os.access(link_path, self.os.X_OK, follow_symlinks=False) ) self.assertTrue(self.os.access(link_path, self.rwx, follow_symlinks=False)) self.assertTrue( self.os.access(link_path, self.rw, follow_symlinks=follow_symlinks) ) def test_access_non_existent_file(self): # set up path = self.make_path("non", "existent", "file") self.assertFalse(self.os.path.exists(path)) # actual tests self.assertFalse(self.os.access(path, self.os.F_OK)) self.assertFalse(self.os.access(path, self.os.R_OK)) self.assertFalse(self.os.access(path, self.os.W_OK)) self.assertFalse(self.os.access(path, self.os.X_OK)) self.assertFalse(self.os.access(path, self.rwx)) self.assertFalse(self.os.access(path, self.rw)) def test_arguments_not_supported_under_windows(self): self.check_windows_only() path = self.make_path("foo", "bar") with self.assertRaises(NotImplementedError): self.os.access(path, self.os.F_OK, effective_ids=True) with self.assertRaises(NotImplementedError): self.os.access(path, self.os.F_OK, follow_symlinks=False) def test_chmod(self): # set up self.check_posix_only() self.skip_real_fs() path = self.make_path("some_file") self.createTestFile(path) # actual tests self.os.chmod(path, 0o6543) st = self.os.stat(path) self.assert_mode_equal(0o6543, st.st_mode) self.assertTrue(st.st_mode & stat.S_IFREG) self.assertFalse(st.st_mode & stat.S_IFDIR) def test_chmod_uses_open_fd_as_path(self): if sys.version_info < (3, 13): self.check_posix_only() self.skip_real_fs() self.assert_raises_os_error(errno.EBADF, self.os.chmod, 5, 0o6543) path = self.make_path("some_file") self.createTestFile(path) with self.open(path, encoding="utf8") as f: st = self.os.stat(f.fileno()) # use a mode that will work under Windows self.os.chmod(f.filedes, 0o444) st = self.os.stat(path) self.assert_mode_equal(0o444, st.st_mode) # fchmod should work the same way self.os.fchmod(f.filedes, 0o666) st = self.os.stat(path) self.assert_mode_equal(0o666, st.st_mode) @unittest.skipIf( sys.version_info >= (3, 13), "also available under Windows since Python 3.13" ) def test_chmod_uses_open_fd_as_path_not_available_under_windows(self): self.check_windows_only() self.skip_real_fs() path = self.make_path("some_file") self.createTestFile(path) with self.open(path, encoding="utf8") as f: with self.assertRaises(TypeError): self.os.chmod(f.fileno(), 0o666) with self.assertRaises(AttributeError): self.os.fchmod(f.fileno(), 0o666) def test_chmod_follow_symlink(self): self.check_posix_only() path = self.make_path("some_file") self.createTestFile(path) link_path = self.make_path("link_to_some_file") self.create_symlink(link_path, path) self.os.chmod(link_path, 0o6543) st = self.os.stat(link_path) self.assert_mode_equal(0o6543, st.st_mode) st = self.os.stat(link_path, follow_symlinks=False) # the exact mode depends on OS and Python version self.assertEqual(stat.S_IMODE(0o700), stat.S_IMODE(st.st_mode) & 0o700) def test_chmod_no_follow_symlink(self): if sys.version_info < (3, 13): self.check_posix_only() else: skip_if_symlink_not_supported() path = self.make_path("some_file") self.createTestFile(path) link_path = self.make_path("link_to_some_file") self.create_symlink(link_path, path) if self.os.chmod not in self.os.supports_follow_symlinks or IS_PYPY: with self.assertRaises(NotImplementedError): self.os.chmod(link_path, 0o6543, follow_symlinks=False) else: self.os.chmod(link_path, 0o6543, follow_symlinks=False) st = self.os.stat(link_path) mode = 0o644 if self.is_macos else 0o666 self.assert_mode_equal(mode, st.st_mode) st = self.os.stat(link_path, follow_symlinks=False) mode = 0o444 if self.is_windows_fs else 0o6543 self.assert_mode_equal(mode, st.st_mode) def test_lchmod(self): """lchmod shall behave like chmod with follow_symlinks=True.""" self.check_posix_only() self.skip_real_fs() path = self.make_path("some_file") self.createTestFile(path) link_path = self.make_path("link_to_some_file") self.create_symlink(link_path, path) self.os.lchmod(link_path, 0o6543) st = self.os.stat(link_path) self.assert_mode_equal(0o644, st.st_mode) st = self.os.lstat(link_path) self.assert_mode_equal(0o6543, st.st_mode) def test_chmod_dir(self): # set up self.check_posix_only() self.skip_real_fs() path = self.make_path("some_dir") self.createTestDirectory(path) # actual tests self.os.chmod(path, 0o1434) st = self.os.stat(path) self.assert_mode_equal(0o1434, st.st_mode) self.assertFalse(st.st_mode & stat.S_IFREG) self.assertTrue(st.st_mode & stat.S_IFDIR) def test_chmod_non_existent(self): # set up path = self.make_path("non", "existent", "file") self.assertFalse(self.os.path.exists(path)) # actual tests try: # Use try-catch to check exception attributes. self.os.chmod(path, 0o777) self.fail("Exception is expected.") # COV_NF_LINE except OSError as os_error: self.assertEqual(errno.ENOENT, os_error.errno) self.assertEqual(path, os_error.filename) def test_chown_existing_file(self): # set up self.skip_real_fs() file_path = self.make_path("some_file") self.create_file(file_path) # first set it make sure it's set self.os.chown(file_path, 100, 101) st = self.os.stat(file_path) self.assertEqual(st[stat.ST_UID], 100) self.assertEqual(st[stat.ST_GID], 101) # we can make sure it changed self.os.chown(file_path, 200, 201) st = self.os.stat(file_path) self.assertEqual(st[stat.ST_UID], 200) self.assertEqual(st[stat.ST_GID], 201) # setting a value to -1 leaves it unchanged self.os.chown(file_path, -1, -1) st = self.os.stat(file_path) self.assertEqual(st[stat.ST_UID], 200) self.assertEqual(st[stat.ST_GID], 201) def test_chown_uses_open_fd_as_path(self): self.check_posix_only() self.skip_real_fs() self.assert_raises_os_error(errno.EBADF, self.os.chown, 5, 100, 101) file_path = self.make_path("foo", "bar") self.create_file(file_path) with self.open(file_path, encoding="utf8") as f: self.os.chown(f.filedes, 100, 101) st = self.os.stat(file_path) self.assertEqual(st[stat.ST_UID], 100) def test_chown_follow_symlink(self): self.skip_real_fs() file_path = self.make_path("some_file") self.create_file(file_path) link_path = self.make_path("link_to_some_file") self.create_symlink(link_path, file_path) self.os.chown(link_path, 100, 101) st = self.os.stat(link_path) self.assertEqual(st[stat.ST_UID], 100) self.assertEqual(st[stat.ST_GID], 101) st = self.os.stat(link_path, follow_symlinks=False) self.assertNotEqual(st[stat.ST_UID], 100) self.assertNotEqual(st[stat.ST_GID], 101) def test_chown_no_follow_symlink(self): self.skip_real_fs() file_path = self.make_path("some_file") self.create_file(file_path) link_path = self.make_path("link_to_some_file") self.create_symlink(link_path, file_path) self.os.chown(link_path, 100, 101, follow_symlinks=False) st = self.os.stat(link_path) self.assertNotEqual(st[stat.ST_UID], 100) self.assertNotEqual(st[stat.ST_GID], 101) st = self.os.stat(link_path, follow_symlinks=False) self.assertEqual(st[stat.ST_UID], 100) self.assertEqual(st[stat.ST_GID], 101) def test_chown_bad_arguments(self): """os.chown() with bad args (Issue #30)""" self.check_posix_only() file_path = self.make_path("some_file") self.create_file(file_path) self.assertRaises(TypeError, self.os.chown, file_path, "username", -1) self.assertRaises(TypeError, self.os.chown, file_path, -1, "groupname") def test_chown_nonexisting_file_should_raise_os_error(self): self.check_posix_only() file_path = self.make_path("some_file") self.assertFalse(self.os.path.exists(file_path)) self.assert_raises_os_error(errno.ENOENT, self.os.chown, file_path, 100, 100) def test_fail_add_entry_to_readonly_dir(self): # regression test for #959 self.check_posix_only() self.skip_real_fs() # cannot change owner to root if is_root(): self.skipTest("Non-root test only") # create directory owned by root with permissions 0o755 (rwxr-xr-x) ro_dir = self.make_path("readonly-dir") self.create_dir(ro_dir, perm=0o755) self.os.chown(ro_dir, 0, 0) # adding a new entry to the readonly subdirectory should fail with self.assertRaises(PermissionError): with self.open(f"{ro_dir}/file.txt", "w", encoding="utf8"): pass file_path = self.make_path("file.txt") self.create_file(file_path) with self.assertRaises(PermissionError): self.os.link(file_path, self.os.path.join(ro_dir, "file.txt")) def test_classify_directory_contents(self): """Directory classification should work correctly.""" root_directory = self.make_path("foo") test_directories = ["bar1", "baz2"] test_files = ["baz1", "bar2", "baz3"] self.create_dir(root_directory) for directory in test_directories: directory = self.os.path.join(root_directory, directory) self.create_dir(directory) for test_file in test_files: test_file = self.os.path.join(root_directory, test_file) self.create_file(test_file) test_directories.sort() test_files.sort() generator = self.os.walk(root_directory) root, dirs, files = next(generator) dirs.sort() files.sort() self.assertEqual(root_directory, root) self.assertEqual(test_directories, dirs) self.assertEqual(test_files, files) # os.mknod does not work under MacOS due to permission issues # so we test it under Linux only def test_mk_nod_can_create_a_file(self): self.check_linux_only() filename = self.make_path("foo") self.assertFalse(self.os.path.exists(filename)) self.os.mknod(filename) self.assertTrue(self.os.path.exists(filename)) self.assertEqual(stat.S_IFREG | 0o600, self.os.stat(filename).st_mode) def test_mk_nod_raises_if_empty_file_name(self): self.check_linux_only() filename = "" self.assert_raises_os_error(errno.ENOENT, self.os.mknod, filename) def test_mk_nod_raises_if_parent_dir_doesnt_exist(self): self.check_linux_only() parent = self.make_path("xyzzy") filename = self.os.path.join(parent, "foo") self.assertFalse(self.os.path.exists(parent)) self.assert_raises_os_error(errno.ENOENT, self.os.mknod, filename) def test_mk_nod_raises_if_file_exists(self): self.check_linux_only() filename = self.make_path("tmp", "foo") self.create_file(filename) self.assertTrue(self.os.path.exists(filename)) self.assert_raises_os_error(errno.EEXIST, self.os.mknod, filename) def test_mk_nod_raises_if_filename_is_dot(self): self.check_linux_only() filename = self.make_path("tmp", ".") self.assert_raises_os_error(errno.ENOENT, self.os.mknod, filename) def test_mk_nod_raises_if_filename_is_double_dot(self): self.check_linux_only() filename = self.make_path("tmp", "..") self.assert_raises_os_error(errno.ENOENT, self.os.mknod, filename) def test_mknod_empty_tail_for_existing_file_raises(self): self.check_linux_only() filename = self.make_path("foo") self.create_file(filename) self.assertTrue(self.os.path.exists(filename)) self.assert_raises_os_error(errno.EEXIST, self.os.mknod, filename) def test_mknod_empty_tail_for_nonexistent_file_raises(self): self.check_linux_only() filename = self.make_path("tmp", "foo") self.assert_raises_os_error(errno.ENOENT, self.os.mknod, filename) def test_mknod_raises_if_filename_is_empty_string(self): self.check_linux_only() filename = "" self.assert_raises_os_error(errno.ENOENT, self.os.mknod, filename) def test_mknod_raises_if_unsupported_options(self): self.check_posix_only() # behavior seems to have changed in ubuntu-20.04, version 20210606.1 # skipping real fs tests for now self.skip_real_fs() filename = "abcde" if not is_root(): self.assert_raises_os_error( errno.EPERM, self.os.mknod, filename, stat.S_IFCHR ) else: self.os.mknod(filename, stat.S_IFCHR) self.os.remove(filename) def test_mknod_raises_if_parent_is_not_a_directory(self): self.check_linux_only() filename1 = self.make_path("foo") self.create_file(filename1) self.assertTrue(self.os.path.exists(filename1)) filename2 = self.make_path("foo", "bar") self.assert_raises_os_error(errno.ENOTDIR, self.os.mknod, filename2) def test_symlink(self): skip_if_symlink_not_supported() file_path = self.make_path("foo", "bar", "baz") self.create_dir(self.make_path("foo", "bar")) self.os.symlink("bogus", file_path) self.assertTrue(self.os.path.lexists(file_path)) self.assertFalse(self.os.path.exists(file_path)) self.create_file(self.make_path("foo", "bar", "bogus")) self.assertTrue(self.os.path.lexists(file_path)) self.assertTrue(self.os.path.exists(file_path)) def test_symlink_on_nonexisting_path_raises(self): self.check_posix_only() dir_path = self.make_path("bar") link_path = self.os.path.join(dir_path, "bar") self.assert_raises_os_error(errno.ENOENT, self.os.symlink, link_path, link_path) self.assert_raises_os_error(errno.ENOENT, self.os.symlink, dir_path, link_path) def test_symlink_with_path_ending_with_sep_in_posix(self): self.check_posix_only() dir_path = self.make_path("dir") self.create_dir(dir_path) self.assert_raises_os_error( errno.EEXIST, self.os.symlink, self.base_path, dir_path + self.os.sep, ) dir_path = self.make_path("bar") self.assert_raises_os_error( errno.ENOENT, self.os.symlink, self.base_path, dir_path + self.os.sep, ) def test_symlink_with_path_ending_with_sep_in_windows(self): self.check_windows_only() skip_if_symlink_not_supported() dir_path = self.make_path("dir") self.create_dir(dir_path) self.assert_raises_os_error( errno.EEXIST, self.os.symlink, self.base_path, dir_path + self.os.sep, ) dir_path = self.make_path("bar") # does not raise under Windows self.os.symlink(self.base_path, dir_path + self.os.sep) def test_broken_symlink_with_trailing_sep_posix(self): # Regression test for #390 self.check_linux_only() path0 = self.make_path("foo") + self.os.sep self.assert_raises_os_error(errno.ENOENT, self.os.symlink, path0, path0) def test_broken_symlink_with_trailing_sep_windows(self): self.check_windows_only() skip_if_symlink_not_supported() path0 = self.make_path("foo") + self.os.sep self.assert_raises_os_error(errno.EINVAL, self.os.symlink, path0, path0) def test_rename_symlink_with_trailing_sep_linux(self): # Regression test for #391 self.check_linux_only() path = self.make_path("foo") self.os.symlink(self.base_path, path) self.assert_raises_os_error( errno.ENOTDIR, self.os.rename, path + self.os.sep, self.base_path ) def test_rename_symlink_with_trailing_sep_macos(self): # Regression test for #391 self.check_macos_only() path = self.make_path("foo") self.os.symlink(self.base_path, path) self.os.rename(path + self.os.sep, self.base_path) def test_rename_symlink_with_trailing_sep_windows(self): self.check_windows_only() skip_if_symlink_not_supported() path = self.make_path("foo") self.os.symlink(self.base_path, path) self.assert_raises_os_error( errno.EEXIST, self.os.rename, path + self.os.sep, self.base_path ) def test_rename_symlink_to_other_case(self): # Regression test for #389 skip_if_symlink_not_supported() link_path = self.make_path("foo") self.os.symlink(self.base_path, link_path) link_to_link_path = self.make_path("BAR") self.os.symlink(link_path, link_to_link_path) new_link_to_link_path = self.os.path.join(link_path, "bar") self.os.rename(link_to_link_path, new_link_to_link_path) self.assertEqual(["bar", "foo"], sorted(self.os.listdir(new_link_to_link_path))) def create_broken_link_path_with_trailing_sep(self): # Regression tests for #396 skip_if_symlink_not_supported() link_path = self.make_path("link") target_path = self.make_path("target") self.os.symlink(target_path, link_path) link_path += self.os.sep return link_path def test_lstat_broken_link_with_trailing_sep_linux(self): self.check_linux_only() link_path = self.create_broken_link_path_with_trailing_sep() self.assert_raises_os_error(errno.ENOENT, self.os.lstat, link_path) def test_lstat_broken_link_with_trailing_sep_macos(self): self.check_macos_only() link_path = self.create_broken_link_path_with_trailing_sep() self.assert_raises_os_error(errno.ENOENT, self.os.lstat, link_path) def test_lstat_broken_link_with_trailing_sep_windows(self): self.check_windows_only() link_path = self.create_broken_link_path_with_trailing_sep() self.assert_raises_os_error(errno.EINVAL, self.os.lstat, link_path) def test_mkdir_broken_link_with_trailing_sep_linux_windows(self): self.check_linux_and_windows() link_path = self.create_broken_link_path_with_trailing_sep() self.assert_raises_os_error(errno.EEXIST, self.os.mkdir, link_path) self.assert_raises_os_error(errno.EEXIST, self.os.makedirs, link_path) def test_mkdir_broken_link_with_trailing_sep_macos(self): self.check_macos_only() link_path = self.create_broken_link_path_with_trailing_sep() self.os.mkdir(link_path) # no error def test_makedirs_broken_link_with_trailing_sep_macos(self): self.check_macos_only() link_path = self.create_broken_link_path_with_trailing_sep() self.os.makedirs(link_path) # no error def test_remove_broken_link_with_trailing_sep_linux(self): self.check_linux_only() link_path = self.create_broken_link_path_with_trailing_sep() self.assert_raises_os_error(errno.ENOTDIR, self.os.remove, link_path) def test_remove_broken_link_with_trailing_sep_macos(self): self.check_macos_only() link_path = self.create_broken_link_path_with_trailing_sep() self.assert_raises_os_error(errno.ENOENT, self.os.remove, link_path) def test_remove_broken_link_with_trailing_sep_windows(self): self.check_windows_only() link_path = self.create_broken_link_path_with_trailing_sep() self.assert_raises_os_error(errno.EINVAL, self.os.remove, link_path) def test_rename_broken_link_with_trailing_sep_linux(self): self.check_linux_only() link_path = self.create_broken_link_path_with_trailing_sep() self.assert_raises_os_error( errno.ENOTDIR, self.os.rename, link_path, self.make_path("target") ) def test_rename_broken_link_with_trailing_sep_macos(self): self.check_macos_only() link_path = self.create_broken_link_path_with_trailing_sep() self.assert_raises_os_error( errno.ENOENT, self.os.rename, link_path, self.make_path("target") ) def test_rename_broken_link_with_trailing_sep_windows(self): self.check_windows_only() link_path = self.create_broken_link_path_with_trailing_sep() self.assert_raises_os_error( errno.EINVAL, self.os.rename, link_path, self.make_path("target") ) def test_readlink_broken_link_with_trailing_sep_posix(self): self.check_posix_only() link_path = self.create_broken_link_path_with_trailing_sep() self.assert_raises_os_error(errno.ENOENT, self.os.readlink, link_path) def test_readlink_broken_link_with_trailing_sep_windows(self): self.check_windows_only() link_path = self.create_broken_link_path_with_trailing_sep() self.assert_raises_os_error(errno.EINVAL, self.os.readlink, link_path) def test_islink_broken_link_with_trailing_sep(self): link_path = self.create_broken_link_path_with_trailing_sep() self.assertFalse(self.os.path.islink(link_path)) def test_lexists_broken_link_with_trailing_sep(self): link_path = self.create_broken_link_path_with_trailing_sep() self.assertFalse(self.os.path.lexists(link_path)) def test_rename_link_with_trailing_sep_to_self_windows(self): self.check_windows_only() skip_if_symlink_not_supported() path = self.make_path("foo") self.os.symlink(self.base_path, path) self.os.rename(path + self.os.sep, path) # no error def test_rename_link_with_trailing_sep_to_self_posix(self): # Regression test for #395 self.check_posix_only() path = self.make_path("foo") self.os.symlink(self.base_path, path) self.assert_raises_os_error( errno.ENOTDIR, self.os.rename, path + self.os.sep, path ) def check_open_broken_symlink_to_path_with_trailing_sep(self, error): # Regression tests for #397 skip_if_symlink_not_supported() target_path = self.make_path("target") + self.os.sep link_path = self.make_path("link") self.os.symlink(target_path, link_path) self.assert_raises_os_error(error, self.open, link_path, "a") self.assert_raises_os_error(error, self.open, link_path, "w") def test_open_broken_symlink_to_path_with_trailing_sep_linux(self): self.check_linux_only() self.check_open_broken_symlink_to_path_with_trailing_sep(errno.EISDIR) def test_open_broken_symlink_to_path_with_trailing_sep_macos(self): self.check_macos_only() self.check_open_broken_symlink_to_path_with_trailing_sep(errno.ENOENT) def test_open_broken_symlink_to_path_with_trailing_sep_windows(self): self.check_windows_only() self.check_open_broken_symlink_to_path_with_trailing_sep(errno.EINVAL) def check_link_path_ending_with_sep(self, error): # Regression tests for #399 skip_if_symlink_not_supported() file_path = self.make_path("foo") link_path = self.make_path("link") with self.open(file_path, "w", encoding="utf8"): self.assert_raises_os_error( error, self.os.link, file_path + self.os.sep, link_path ) def test_link_path_ending_with_sep_posix(self): self.check_posix_only() self.check_link_path_ending_with_sep(errno.ENOTDIR) def test_link_path_ending_with_sep_windows(self): self.check_windows_only() self.check_link_path_ending_with_sep(errno.EINVAL) def test_link_to_path_ending_with_sep_posix(self): # regression test for #407 self.check_posix_only() path0 = self.make_path("foo") + self.os.sep path1 = self.make_path("bar") with self.open(path1, "w", encoding="utf8"): self.assert_raises_os_error(errno.ENOENT, self.os.link, path1, path0) def test_link_to_path_ending_with_sep_windows(self): self.check_windows_only() skip_if_symlink_not_supported() path0 = self.make_path("foo") + self.os.sep path1 = self.make_path("bar") with self.open(path1, "w", encoding="utf8"): self.os.link(path1, path0) self.assertTrue(self.os.path.exists(path1)) def check_rename_to_path_ending_with_sep(self, error): # Regression tests for #400 file_path = self.make_path("foo") with self.open(file_path, "w", encoding="utf8"): self.assert_raises_os_error( error, self.os.rename, file_path + self.os.sep, file_path ) def test_rename_to_path_ending_with_sep_posix(self): self.check_posix_only() self.check_rename_to_path_ending_with_sep(errno.ENOTDIR) def test_rename_to_path_ending_with_sep_windows(self): self.check_windows_only() self.check_rename_to_path_ending_with_sep(errno.EINVAL) def test_rmdir_link_with_trailing_sep_linux(self): self.check_linux_only() dir_path = self.make_path("foo") self.os.mkdir(dir_path) link_path = self.make_path("link") self.os.symlink(dir_path, link_path) self.assert_raises_os_error( errno.ENOTDIR, self.os.rmdir, link_path + self.os.sep ) def test_rmdir_link_with_trailing_sep_macos(self): # Regression test for #398 self.check_macos_only() dir_path = self.make_path("foo") self.os.mkdir(dir_path) link_path = self.make_path("link") self.os.symlink(dir_path, link_path) self.os.rmdir(link_path + self.os.sep) self.assertFalse(self.os.path.exists(link_path)) def test_rmdir_link_with_trailing_sep_windows(self): self.check_windows_only() skip_if_symlink_not_supported() dir_path = self.make_path("foo") self.os.mkdir(dir_path) link_path = self.make_path("link") self.os.symlink(dir_path, link_path) self.os.rmdir(link_path + self.os.sep) self.assertFalse(self.os.path.exists(link_path)) def test_readlink_circular_link_with_trailing_sep_linux(self): self.check_linux_only() path1 = self.make_path("foo") path0 = self.make_path("bar") self.os.symlink(path0, path1) self.os.symlink(path1, path0) self.assert_raises_os_error(errno.ELOOP, self.os.readlink, path0 + self.os.sep) def test_readlink_circular_link_with_trailing_sep_macos(self): # Regression test for #392 self.check_macos_only() path1 = self.make_path("foo") path0 = self.make_path("bar") self.os.symlink(path0, path1) self.os.symlink(path1, path0) self.assertEqual(path0, self.os.readlink(path0 + self.os.sep)) def test_readlink_circular_link_with_trailing_sep_windows(self): self.check_windows_only() skip_if_symlink_not_supported() path1 = self.make_path("foo") path0 = self.make_path("bar") self.os.symlink(path0, path1) self.os.symlink(path1, path0) self.assert_raises_os_error(errno.EINVAL, self.os.readlink, path0 + self.os.sep) # hard link related tests def test_link_bogus(self): # trying to create a link from a non-existent file should fail skip_if_symlink_not_supported() self.assert_raises_os_error( errno.ENOENT, self.os.link, "/nonexistent_source", "/link_dest" ) def test_link_delete(self): skip_if_symlink_not_supported() file1_path = self.make_path("test_file1") file2_path = self.make_path("test_file2") contents1 = "abcdef" # Create file self.create_file(file1_path, contents=contents1) # link to second file self.os.link(file1_path, file2_path) # delete first file self.os.unlink(file1_path) # assert that second file exists, and its contents are the same self.assertTrue(self.os.path.exists(file2_path)) with self.open(file2_path, encoding="utf8") as f: self.assertEqual(f.read(), contents1) def test_link_update(self): skip_if_symlink_not_supported() file1_path = self.make_path("test_file1") file2_path = self.make_path("test_file2") contents1 = "abcdef" contents2 = "ghijkl" # Create file and link self.create_file(file1_path, contents=contents1) self.os.link(file1_path, file2_path) # assert that the second file contains contents1 with self.open(file2_path, encoding="utf8") as f: self.assertEqual(f.read(), contents1) # update the first file with self.open(file1_path, "w", encoding="utf8") as f: f.write(contents2) # assert that second file contains contents2 with self.open(file2_path, encoding="utf8") as f: self.assertEqual(f.read(), contents2) def test_link_non_existent_parent(self): skip_if_symlink_not_supported() file1_path = self.make_path("test_file1") breaking_link_path = self.make_path("nonexistent", "test_file2") contents1 = "abcdef" # Create file and link self.create_file(file1_path, contents=contents1) # trying to create a link under a non-existent directory should fail self.assert_raises_os_error( errno.ENOENT, self.os.link, file1_path, breaking_link_path ) def test_link_is_existing_file(self): skip_if_symlink_not_supported() file_path = self.make_path("foo", "bar") self.create_file(file_path) self.assert_raises_os_error(errno.EEXIST, self.os.link, file_path, file_path) def test_link_target_is_dir_windows(self): self.check_windows_only() skip_if_symlink_not_supported() dir_path = self.make_path("foo", "bar") link_path = self.os.path.join(dir_path, "link") self.create_dir(dir_path) self.assert_raises_os_error(errno.EACCES, self.os.link, dir_path, link_path) def test_link_target_is_dir_posix(self): self.check_posix_only() dir_path = self.make_path("foo", "bar") link_path = self.os.path.join(dir_path, "link") self.create_dir(dir_path) self.assert_raises_os_error(errno.EPERM, self.os.link, dir_path, link_path) def test_link_count1(self): """Test that hard link counts are updated correctly.""" skip_if_symlink_not_supported() file1_path = self.make_path("test_file1") file2_path = self.make_path("test_file2") file3_path = self.make_path("test_file3") self.create_file(file1_path) # initial link count should be one self.assertEqual(self.os.stat(file1_path).st_nlink, 1) self.os.link(file1_path, file2_path) # the count should be incremented for each hard link created self.assertEqual(self.os.stat(file1_path).st_nlink, 2) self.assertEqual(self.os.stat(file2_path).st_nlink, 2) # Check that the counts are all updated together self.os.link(file2_path, file3_path) self.assertEqual(self.os.stat(file1_path).st_nlink, 3) self.assertEqual(self.os.stat(file2_path).st_nlink, 3) self.assertEqual(self.os.stat(file3_path).st_nlink, 3) # Counts should be decremented when links are removed self.os.unlink(file3_path) self.assertEqual(self.os.stat(file1_path).st_nlink, 2) self.assertEqual(self.os.stat(file2_path).st_nlink, 2) # check that it gets decremented correctly again self.os.unlink(file1_path) self.assertEqual(self.os.stat(file2_path).st_nlink, 1) @unittest.skipIf(IS_PYPY, "follow_symlinks not supported in PyPi") def test_link_no_follow_symlink(self): skip_if_symlink_not_supported() target_path = self.make_path("target_path") self.create_file(target_path, contents="foo") symlink_path = self.make_path("symlink_to_file") self.create_symlink(symlink_path, target_path) link_path = self.make_path("link_to_symlink") self.os.link(symlink_path, link_path, follow_symlinks=False) self.assertTrue(self.os.path.islink(link_path)) @unittest.skipIf(not IS_PYPY, "follow_symlinks only not supported in PyPi") def test_link_follow_symlink_not_supported_inPypy(self): skip_if_symlink_not_supported() target_path = self.make_path("target_path") self.create_file(target_path, contents="foo") symlink_path = self.make_path("symlink_to_file") self.create_symlink(symlink_path, target_path) link_path = self.make_path("link_to_symlink") with self.assertRaises(OSError) as cm: self.os.link(symlink_path, link_path, follow_symlinks=False) self.assertEqual(errno.EINVAL, cm.exception.errno) def test_nlink_for_directories(self): self.skip_real_fs() self.create_dir(self.make_path("foo", "bar")) self.create_file(self.make_path("foo", "baz")) self.assertEqual( 2, self.filesystem.get_object(self.make_path("foo", "bar")).st_nlink, ) self.assertEqual(4, self.filesystem.get_object(self.make_path("foo")).st_nlink) self.create_file(self.make_path("foo", "baz2")) self.assertEqual(5, self.filesystem.get_object(self.make_path("foo")).st_nlink) def test_umask(self): self.check_posix_only() umask = self.os.umask(0o22) self.assertEqual(umask, self.os.umask(0o12)) self.os.umask(umask) def test_mkdir_umask_applied(self): """mkdir creates a directory with umask applied.""" self.check_posix_only() self.os.umask(0o22) dir1 = self.make_path("dir1") self.os.mkdir(dir1) self.assert_mode_equal(0o755, self.os.stat(dir1).st_mode) self.os.umask(0o67) dir2 = self.make_path("dir2") self.os.mkdir(dir2) self.assert_mode_equal(0o710, self.os.stat(dir2).st_mode) def test_makedirs_umask_applied(self): """makedirs creates a directories with umask applied.""" self.check_posix_only() umask = self.os.umask(0o22) self.os.makedirs(self.make_path("p1", "dir1")) self.assert_mode_equal(0o755, self.os.stat(self.make_path("p1")).st_mode) self.assert_mode_equal( 0o755, self.os.stat(self.make_path("p1", "dir1")).st_mode ) self.os.umask(0o67) self.os.makedirs(self.make_path("p2", "dir2")) self.assert_mode_equal(0o710, self.os.stat(self.make_path("p2")).st_mode) self.assert_mode_equal( 0o710, self.os.stat(self.make_path("p2", "dir2")).st_mode ) self.os.umask(umask) def test_mknod_umask_applied(self): """mkdir creates a device with umask applied.""" # skipping MacOs due to mknod permission issues self.check_linux_only() self.os.umask(0o22) node1 = self.make_path("nod1") self.os.mknod(node1, stat.S_IFREG | 0o666) self.assert_mode_equal(0o644, self.os.stat(node1).st_mode) self.os.umask(0o27) node2 = self.make_path("nod2") self.os.mknod(node2, stat.S_IFREG | 0o666) self.assert_mode_equal(0o640, self.os.stat(node2).st_mode) def test_open_umask_applied(self): """open creates a file with umask applied.""" self.check_posix_only() self.os.umask(0o22) file1 = self.make_path("file1") self.open(file1, "w", encoding="utf8").close() self.assert_mode_equal(0o644, self.os.stat(file1).st_mode) self.os.umask(0o27) file2 = self.make_path("file2") self.open(file2, "w", encoding="utf8").close() self.assert_mode_equal(0o640, self.os.stat(file2).st_mode) def test_open_pipe(self): read_fd, write_fd = self.os.pipe() self.os.close(read_fd) self.os.close(write_fd) def test_open_pipe_with_existing_fd(self): file1 = self.make_path("file1") fd = self.os.open(file1, os.O_CREAT) read_fd, write_fd = self.os.pipe() self.assertGreater(read_fd, fd) self.os.close(fd) self.os.close(read_fd) self.os.close(write_fd) def test_open_file_with_existing_pipe(self): read_fd, write_fd = self.os.pipe() file1 = self.make_path("file1") fd = self.os.open(file1, os.O_CREAT) self.assertGreater(fd, write_fd) self.os.close(read_fd) self.os.close(write_fd) self.os.close(fd) def test_read_write_pipe(self): read_fd, write_fd = self.os.pipe() self.assertEqual(4, self.os.write(write_fd, b"test")) self.assertEqual(b"test", self.os.read(read_fd, 4)) self.os.close(read_fd) self.os.close(write_fd) def test_open_existing_pipe(self): # create some regular files to ensure that real and fake fd # are out of sync (see #581) fds = [] for i in range(5): path = self.make_path("file" + str(i)) fds.append(self.os.open(path, os.O_CREAT)) file_path = self.make_path("file.txt") self.create_file(file_path) with self.open(file_path, encoding="utf8"): read_fd, write_fd = self.os.pipe() with self.open(write_fd, "wb") as f: self.assertEqual(4, f.write(b"test")) with self.open(read_fd, "rb") as f: self.assertEqual(b"test", f.read()) for fd in fds: self.os.close(fd) def test_write_to_pipe(self): read_fd, write_fd = self.os.pipe() self.os.write(write_fd, b"test") self.assertEqual(b"test", self.os.read(read_fd, 4)) self.os.close(read_fd) self.os.close(write_fd) @unittest.skipIf( sys.platform not in ("win32", "darwin", "linux"), "Pipe implementation may differ on other platforms", ) def test_write_to_read_fd(self): read_fd, write_fd = self.os.pipe() self.assert_raises_os_error(errno.EBADF, self.os.write, read_fd, b"test") self.os.close(read_fd) self.os.close(write_fd) def test_truncate(self): file_path = self.make_path("foo", "bar") self.create_file(file_path, contents="012345678901234567") self.os.truncate(file_path, 10) with self.open(file_path, encoding="utf8") as f: self.assertEqual("0123456789", f.read()) def test_truncate_non_existing(self): self.assert_raises_os_error(errno.ENOENT, self.os.truncate, "foo", 10) def test_truncate_to_larger(self): file_path = self.make_path("foo", "bar") self.create_file(file_path, contents="0123456789") fd = self.os.open(file_path, os.O_RDWR) self.os.truncate(fd, 20) self.assertEqual(20, self.os.stat(file_path).st_size) with self.open(file_path, encoding="utf8") as f: self.assertEqual("0123456789" + "\0" * 10, f.read()) def test_truncate_with_fd(self): if os.truncate not in os.supports_fd: self.skip_real_fs() self.assert_raises_os_error(errno.EBADF, self.os.ftruncate, 50, 10) file_path = self.make_path("some_file") self.create_file(file_path, contents="01234567890123456789") fd = self.os.open(file_path, os.O_RDWR) self.os.truncate(fd, 10) self.assertEqual(10, self.os.stat(file_path).st_size) with self.open(file_path, encoding="utf8") as f: self.assertEqual("0123456789", f.read()) def test_ftruncate(self): if self.is_pypy: # not correctly supported self.skip_real_fs() self.assert_raises_os_error(errno.EBADF, self.os.ftruncate, 50, 10) file_path = self.make_path("some_file") self.create_file(file_path, contents="0123456789012345") fd = self.os.open(file_path, os.O_RDWR) self.os.truncate(fd, 10) self.assertEqual(10, self.os.stat(file_path).st_size) with self.open(file_path, encoding="utf8") as f: self.assertEqual("0123456789", f.read()) def test_capabilities(self): """Make sure that the fake capabilities are the same as the real ones.""" self.assertEqual( self.os.stat in self.os.supports_follow_symlinks, os.stat in os.supports_follow_symlinks, ) self.assertEqual(self.os.stat in self.os.supports_fd, os.stat in os.supports_fd) self.assertEqual( self.os.stat in self.os.supports_dir_fd, os.stat in os.supports_dir_fd ) self.assertEqual( self.os.stat in self.os.supports_effective_ids, os.stat in os.supports_effective_ids, ) def test_dup(self): with self.assertRaises(OSError) as cm: self.os.dup(500) self.assertEqual(errno.EBADF, cm.exception.errno) file_path = self.make_path("test.txt") self.create_file(file_path, contents="heythere") fd1 = self.os.open(file_path, os.O_RDONLY) fd2 = self.os.dup(fd1) self.assertEqual(b"hey", self.os.read(fd1, 3)) self.assertEqual(b"there", self.os.read(fd1, 10)) self.os.close(fd2) self.os.close(fd1) def test_dup_uses_freed_fd(self): file_path1 = self.make_path("foo.txt") file_path2 = self.make_path("bar.txt") self.create_file(file_path1, contents="foo here") self.create_file(file_path2, contents="bar here") fd1 = self.os.open(file_path1, os.O_RDONLY) fd2 = self.os.open(file_path2, os.O_RDONLY) self.os.close(fd1) fd3 = self.os.dup(fd2) self.assertEqual(fd1, fd3) self.os.close(fd2) def test_dup2_uses_existing_fd(self): with self.assertRaises(OSError) as cm: self.os.dup2(500, 501) self.assertEqual(errno.EBADF, cm.exception.errno) file_path1 = self.make_path("foo.txt") file_path2 = self.make_path("bar.txt") self.create_file(file_path1, contents="foo here") self.create_file(file_path2, contents="bar here") fd1 = self.os.open(file_path1, os.O_RDONLY) fd2 = self.os.open(file_path2, os.O_RDONLY) self.assertEqual(b"bar", self.os.read(fd2, 3)) fd2 = self.os.dup2(fd1, fd2) self.assertEqual(b"foo", self.os.read(fd2, 3)) self.os.lseek(fd2, 0, 0) self.assertEqual(b"foo", self.os.read(fd1, 3)) self.os.close(fd2) def test_dup2_with_new_fd(self): file_path1 = self.make_path("foo.txt") file_path2 = self.make_path("bar.txt") self.create_file(file_path1) self.create_file(file_path2) fd1 = self.os.open(file_path1, os.O_RDONLY) fd2 = fd1 + 2 self.assertEqual(fd2, self.os.dup2(fd1, fd2)) fd3 = self.os.open(file_path2, os.O_RDONLY) fd4 = self.os.dup(fd3) self.os.close(fd4) self.os.close(fd2) # we have a free position before fd2 that is now filled self.assertEqual(fd1 + 1, fd3) self.assertEqual(fd1 + 3, fd4) class RealOsModuleTest(FakeOsModuleTest): def use_real_fs(self): return True class FakeOsModuleTestCaseInsensitiveFS(FakeOsModuleTestBase): def setUp(self): super().setUp() self.check_case_insensitive_fs() self.rwx = self.os.R_OK | self.os.W_OK | self.os.X_OK self.rw = self.os.R_OK | self.os.W_OK def test_chdir_fails_non_directory(self): """chdir should raise OSError if the target is not a directory.""" filename = self.make_path("foo", "bar") self.create_file(filename) filename1 = self.make_path("Foo", "Bar") self.assert_raises_os_error(errno.ENOTDIR, self.os.chdir, filename1) def test_listdir_returns_list(self): directory_root = self.make_path("xyzzy") self.os.mkdir(directory_root) directory = self.os.path.join(directory_root, "bug") self.os.mkdir(directory) directory_upper = self.make_path("XYZZY", "BUG") self.create_file(self.make_path(directory, "foo")) self.assertEqual(["foo"], self.os.listdir(directory_upper)) def test_listdir_on_symlink(self): skip_if_symlink_not_supported() directory = self.make_path("xyzzy") files = ["foo", "bar", "baz"] for f in files: self.create_file(self.make_path(directory, f)) self.create_symlink(self.make_path("symlink"), self.make_path("xyzzy")) files.sort() self.assertEqual(files, sorted(self.os.listdir(self.make_path("SymLink")))) def test_listdir_possible_without_exe_permission(self): # regression test for #960 self.check_posix_only() self.skip_root() directory = self.make_path("testdir") file_path = self.os.path.join(directory, "file.txt") self.create_file(file_path, contents="hey", perm=0o777) self.os.chmod(directory, 0o655) # rw-r-xr-x # We cannot create any files in the directory, because that requires # searching it another_file = self.make_path("file.txt") self.create_file(another_file, contents="hey") with self.assertRaises(PermissionError): self.os.link(another_file, self.os.path.join(directory, "link.txt")) # We can enumerate the directory using listdir and scandir: assert self.os.listdir(directory) == ["file.txt"] assert len(list(self.os.scandir(directory))) == 1 # We cannot read files inside of the directory, # even if we have read access to the file with self.assertRaises(PermissionError): self.os.stat(file_path) with self.assertRaises(PermissionError): with self.open(file_path, encoding="utf8") as f: f.read() def test_listdir_impossible_without_read_permission(self): # regression test for #960 self.check_posix_only() self.skip_root() directory = self.make_path("testdir") file_path = self.os.path.join(directory, "file.txt") self.create_file(file_path, contents="hey", perm=0o777) self.os.chmod(directory, 0o355) # -wxr-xr-x another_file = self.make_path("file.txt") self.create_file(another_file, contents="hey") self.os.link(another_file, self.os.path.join(directory, "link.txt")) # We cannot enumerate the directory using listdir or scandir: with self.assertRaises(PermissionError): self.os.listdir(directory) with self.assertRaises(PermissionError): self.os.scandir(directory) # we can access the file if we know the file name assert self.os.stat(file_path).st_mode & 0o777 == 0o755 with self.open(file_path, encoding="utf8") as f: assert f.read() == "hey" def test_stat(self): directory = self.make_path("xyzzy") directory1 = self.make_path("XYZZY") file_path = self.os.path.join(directory, "plugh") self.create_file(file_path, contents="ABCDE") self.assertTrue(stat.S_IFDIR & self.os.stat(directory1)[stat.ST_MODE]) file_path1 = self.os.path.join(directory1, "Plugh") self.assertTrue(stat.S_IFREG & self.os.stat(file_path1)[stat.ST_MODE]) self.assertTrue(stat.S_IFREG & self.os.stat(file_path1).st_mode) self.assertEqual(5, self.os.stat(file_path1)[stat.ST_SIZE]) def test_stat_no_follow_symlinks_posix(self): """Test that stat with follow_symlinks=False behaves like lstat.""" self.check_posix_only() directory = self.make_path("xyzzy") base_name = "plugh" file_contents = "frobozz" # Just make sure we didn't accidentally make our test data meaningless. self.assertNotEqual(len(base_name), len(file_contents)) file_path = self.os.path.join(directory, base_name) link_path = self.os.path.join(directory, "link") self.create_file(file_path, contents=file_contents) self.create_symlink(link_path, base_name) self.assertEqual( len(file_contents), self.os.stat(file_path.upper(), follow_symlinks=False)[stat.ST_SIZE], ) self.assertEqual( len(base_name), self.os.stat(link_path.upper(), follow_symlinks=False)[stat.ST_SIZE], ) def test_lstat_posix(self): self.check_posix_only() directory = self.make_path("xyzzy") base_name = "plugh" file_contents = "frobozz" # Just make sure we didn't accidentally make our test data meaningless. self.assertNotEqual(len(base_name), len(file_contents)) file_path = self.os.path.join(directory, base_name) link_path = self.os.path.join(directory, "link") self.create_file(file_path, contents=file_contents) self.create_symlink(link_path, base_name) self.assertEqual( len(file_contents), self.os.lstat(file_path.upper())[stat.ST_SIZE] ) self.assertEqual(len(base_name), self.os.lstat(link_path.upper())[stat.ST_SIZE]) def test_readlink(self): skip_if_symlink_not_supported() link_path = self.make_path("foo", "bar", "baz") target = self.make_path("tarJAY") self.create_symlink(link_path, target) self.assert_equal_paths(self.os.readlink(link_path.upper()), target) def check_readlink_raises_if_path_not_a_link(self): file_path = self.make_path("foo", "bar", "eleventyone") self.create_file(file_path) self.assert_raises_os_error(errno.EINVAL, self.os.readlink, file_path.upper()) def test_readlink_raises_if_path_not_a_link_windows(self): self.check_windows_only() skip_if_symlink_not_supported() self.check_readlink_raises_if_path_not_a_link() def test_readlink_raises_if_path_not_a_link_posix(self): self.check_posix_only() self.check_readlink_raises_if_path_not_a_link() def check_readlink_raises_if_path_has_file(self, error_subtype): self.create_file(self.make_path("a_file")) file_path = self.make_path("a_file", "foo") self.assert_raises_os_error(error_subtype, self.os.readlink, file_path.upper()) file_path = self.make_path("a_file", "foo", "bar") self.assert_raises_os_error(error_subtype, self.os.readlink, file_path.upper()) def test_readlink_raises_if_path_has_file_windows(self): self.check_windows_only() skip_if_symlink_not_supported() self.check_readlink_raises_if_path_has_file(errno.ENOENT) def test_readlink_raises_if_path_has_file_posix(self): self.check_posix_only() self.check_readlink_raises_if_path_has_file(errno.ENOTDIR) def test_readlink_with_links_in_path(self): skip_if_symlink_not_supported() self.create_symlink( self.make_path("meyer", "lemon", "pie"), self.make_path("yum") ) self.create_symlink(self.make_path("geo", "metro"), self.make_path("Meyer")) self.assert_equal_paths( self.make_path("yum"), self.os.readlink(self.make_path("Geo", "Metro", "Lemon", "Pie")), ) def test_readlink_with_chained_links_in_path(self): skip_if_symlink_not_supported() self.create_symlink( self.make_path("eastern", "european", "wolfhounds", "chase"), self.make_path("cats"), ) self.create_symlink( self.make_path("russian"), self.make_path("Eastern", "European") ) self.create_symlink( self.make_path("dogs"), self.make_path("Russian", "Wolfhounds") ) self.assert_equal_paths( self.make_path("cats"), self.os.readlink(self.make_path("DOGS", "Chase")), ) def check_remove_dir(self, dir_error): directory = self.make_path("xyzzy") dir_path = self.os.path.join(directory, "plugh") self.create_dir(dir_path) dir_path = dir_path.upper() self.assertTrue(self.os.path.exists(dir_path.upper())) self.assert_raises_os_error(dir_error, self.os.remove, dir_path) self.assertTrue(self.os.path.exists(dir_path)) self.os.chdir(directory) self.assert_raises_os_error(dir_error, self.os.remove, dir_path) self.assertTrue(self.os.path.exists(dir_path)) self.assert_raises_os_error(errno.ENOENT, self.os.remove, "/Plugh") def test_remove_dir_mac_os(self): self.check_macos_only() self.check_remove_dir(errno.EPERM) def test_remove_dir_windows(self): self.check_windows_only() self.check_remove_dir(errno.EACCES) def test_remove_file(self): directory = self.make_path("zzy") file_path = self.os.path.join(directory, "plugh") self.create_file(file_path) self.assertTrue(self.os.path.exists(file_path.upper())) self.os.remove(file_path.upper()) self.assertFalse(self.os.path.exists(file_path)) def test_remove_file_no_directory(self): directory = self.make_path("zzy") file_name = "plugh" file_path = self.os.path.join(directory, file_name) self.create_file(file_path) self.assertTrue(self.os.path.exists(file_path)) self.os.chdir(directory.upper()) self.os.remove(file_name.upper()) self.assertFalse(self.os.path.exists(file_path)) def test_remove_open_file_fails_under_windows(self): self.check_windows_only() path = self.make_path("foo", "bar") self.create_file(path) with self.open(path, "r", encoding="utf8"): self.assert_raises_os_error(errno.EACCES, self.os.remove, path.upper()) self.assertTrue(self.os.path.exists(path)) def test_remove_open_file_possible_under_posix(self): self.check_posix_only() path = self.make_path("foo", "bar") self.create_file(path) self.open(path, "r", encoding="utf8") self.os.remove(path.upper()) self.assertFalse(self.os.path.exists(path)) def test_remove_file_relative_path(self): self.skip_real_fs() original_dir = self.os.getcwd() directory = self.make_path("zzy") subdirectory = self.os.path.join(directory, "zzy") file_name = "plugh" file_path = self.os.path.join(directory, file_name) file_path_relative = self.os.path.join("..", file_name) self.create_file(file_path.upper()) self.assertTrue(self.os.path.exists(file_path)) self.create_dir(subdirectory) self.assertTrue(self.os.path.exists(subdirectory)) self.os.chdir(subdirectory.upper()) self.os.remove(file_path_relative.upper()) self.assertFalse(self.os.path.exists(file_path_relative)) self.os.chdir(original_dir.upper()) self.assertFalse(self.os.path.exists(file_path)) def check_remove_dir_raises_error(self, dir_error): directory = self.make_path("zzy") self.create_dir(directory) self.assert_raises_os_error(dir_error, self.os.remove, directory.upper()) def test_remove_dir_raises_error_mac_os(self): self.check_macos_only() self.check_remove_dir_raises_error(errno.EPERM) def test_remove_dir_raises_error_windows(self): self.check_windows_only() self.check_remove_dir_raises_error(errno.EACCES) def test_remove_symlink_to_dir(self): skip_if_symlink_not_supported() directory = self.make_path("zzy") link = self.make_path("link_to_dir") self.create_dir(directory) self.os.symlink(directory, link) self.assertTrue(self.os.path.exists(directory)) self.assertTrue(self.os.path.exists(link)) self.os.remove(link.upper()) self.assertTrue(self.os.path.exists(directory)) self.assertFalse(self.os.path.exists(link)) def test_rename_dir_to_symlink_posix(self): self.check_posix_only() link_path = self.make_path("link") dir_path = self.make_path("dir") link_target = self.os.path.join(dir_path, "link_target") self.create_dir(dir_path) self.os.symlink(link_target.upper(), link_path.upper()) self.assert_raises_os_error(errno.ENOTDIR, self.os.rename, dir_path, link_path) def test_rename_dir_to_symlink_windows(self): self.check_windows_only() skip_if_symlink_not_supported() link_path = self.make_path("link") dir_path = self.make_path("dir") link_target = self.os.path.join(dir_path, "link_target") self.create_dir(dir_path) self.os.symlink(link_target.upper(), link_path.upper()) self.assert_raises_os_error(errno.EEXIST, self.os.rename, dir_path, link_path) def test_rename_dir_to_existing_dir(self): # Regression test for #317 self.check_posix_only() dest_dir_path = self.make_path("Dest") # seems to behave differently under different MacOS versions self.skip_real_fs() new_dest_dir_path = self.make_path("dest") self.os.mkdir(dest_dir_path) source_dir_path = self.make_path("src") self.os.mkdir(source_dir_path) self.os.rename(source_dir_path, new_dest_dir_path) self.assertEqual(["dest"], self.os.listdir(self.base_path)) def test_rename_file_to_symlink(self): self.check_posix_only() link_path = self.make_path("file_link") file_path = self.make_path("file") self.os.symlink(file_path, link_path) self.create_file(file_path) self.os.rename(file_path.upper(), link_path) self.assertFalse(self.os.path.exists(file_path)) self.assertTrue(self.os.path.exists(link_path.upper())) self.assertTrue(self.os.path.isfile(link_path.upper())) def test_rename_symlink_to_symlink(self): self.check_posix_only() base_path = self.make_path("foo", "bar") self.create_dir(base_path) link_path1 = self.os.path.join(base_path, "link1") link_path2 = self.os.path.join(base_path, "link2") self.os.symlink(base_path.upper(), link_path1) self.os.symlink(base_path, link_path2) self.os.rename(link_path1.upper(), link_path2.upper()) self.assertFalse(self.os.path.exists(link_path1)) self.assertTrue(self.os.path.exists(link_path2)) def test_rename_symlink_to_symlink_for_parent_raises(self): self.check_posix_only() dir_link = self.make_path("dir_link") dir_path = self.make_path("dir") dir_in_dir_path = self.os.path.join(dir_link, "inner_dir") self.create_dir(dir_path) self.os.symlink(dir_path.upper(), dir_link) self.create_dir(dir_in_dir_path) self.assert_raises_os_error( errno.EINVAL, self.os.rename, dir_path, dir_in_dir_path.upper() ) def test_rename_directory_to_linked_dir(self): # Regression test for #314 skip_if_symlink_not_supported() link_path = self.make_path("link") self.os.symlink(self.base_path, link_path) link_subdir = self.os.path.join(link_path, "dir") dir_path = self.make_path("Dir") self.os.mkdir(dir_path) self.os.rename(dir_path, link_subdir) self.assertEqual(["dir", "link"], sorted(self.os.listdir(self.base_path))) def test_recursive_rename_raises(self): self.check_posix_only() base_path = self.make_path("foo", "bar") self.create_dir(base_path) new_path = self.os.path.join(base_path, "new_dir") self.assert_raises_os_error( errno.EINVAL, self.os.rename, base_path.upper(), new_path ) def test_rename_with_target_parent_file_raises_posix(self): self.check_posix_only() file_path = self.make_path("foo", "baz") self.create_file(file_path) self.assert_raises_os_error( errno.ENOTDIR, self.os.rename, file_path, file_path.upper() + "/new", ) def test_rename_with_target_parent_file_raises_windows(self): self.check_windows_only() file_path = self.make_path("foo", "baz") self.create_file(file_path) self.assert_raises_os_error( errno.EACCES, self.os.rename, file_path, self.os.path.join(file_path.upper(), "new"), ) def test_rename_looping_symlink(self): # Regression test for #315 skip_if_symlink_not_supported() path_lower = self.make_path("baz") path_upper = self.make_path("BAZ") self.os.symlink(path_lower, path_upper) self.os.rename(path_upper, path_lower) self.assertEqual(["baz"], self.os.listdir(self.base_path)) def test_rename_symlink_to_source(self): self.check_posix_only() base_path = self.make_path("foo") link_path = self.os.path.join(base_path, "slink") file_path = self.os.path.join(base_path, "file") self.create_file(file_path) self.os.symlink(file_path, link_path) self.os.rename(link_path.upper(), file_path.upper()) self.assertFalse(self.os.path.exists(file_path)) def test_rename_symlink_to_dir_raises(self): self.check_posix_only() base_path = self.make_path("foo", "bar") link_path = self.os.path.join(base_path, "dir_link") dir_path = self.os.path.join(base_path, "dir") self.create_dir(dir_path) self.os.symlink(dir_path, link_path.upper()) self.assert_raises_os_error( errno.EISDIR, self.os.rename, link_path, dir_path.upper() ) def test_rename_broken_symlink(self): self.check_posix_only() base_path = self.make_path("foo") self.create_dir(base_path) link_path = self.os.path.join(base_path, "slink") file_path = self.os.path.join(base_path, "file") self.os.symlink(file_path.upper(), link_path) self.os.rename(link_path.upper(), file_path) self.assertFalse(self.os.path.exists(file_path)) self.assertTrue(self.os.path.lexists(file_path)) self.assertFalse(self.os.path.exists(link_path)) def test_change_case_in_case_insensitive_file_system(self): """Can use `rename()` to change filename case in a case-insensitive file system.""" old_file_path = self.make_path("fileName") new_file_path = self.make_path("FileNAME") self.create_file(old_file_path, contents="test contents") self.assertEqual(["fileName"], self.os.listdir(self.base_path)) self.os.rename(old_file_path, new_file_path) self.assertTrue(self.os.path.exists(old_file_path)) self.assertTrue(self.os.path.exists(new_file_path)) self.assertEqual(["FileNAME"], self.os.listdir(self.base_path)) def test_rename_symlink_with_changed_case(self): # Regression test for #313 skip_if_symlink_not_supported() link_path = self.make_path("link") self.os.symlink(self.base_path, link_path) link_path = self.os.path.join(link_path, "link") link_path_upper = self.make_path("link", "LINK") self.os.rename(link_path_upper, link_path) def test_rename_directory(self): """Can rename a directory to an unused name.""" for old_path, new_path in [("wxyyw", "xyzzy"), ("abccb", "cdeed")]: old_path = self.make_path(old_path) new_path = self.make_path(new_path) self.create_file(self.os.path.join(old_path, "plugh"), contents="test") self.assertTrue(self.os.path.exists(old_path)) self.assertFalse(self.os.path.exists(new_path)) self.os.rename(old_path.upper(), new_path.upper()) self.assertFalse(self.os.path.exists(old_path)) self.assertTrue(self.os.path.exists(new_path)) self.check_contents(self.os.path.join(new_path, "plugh"), "test") if not self.use_real_fs(): self.assertEqual(3, self.filesystem.get_object(new_path).st_nlink) def check_rename_directory_to_existing_file_raises(self, error_nr): dir_path = self.make_path("dir") file_path = self.make_path("file") self.create_dir(dir_path) self.create_file(file_path) self.assert_raises_os_error( error_nr, self.os.rename, dir_path, file_path.upper() ) def test_rename_directory_to_existing_file_raises_posix(self): self.check_posix_only() self.check_rename_directory_to_existing_file_raises(errno.ENOTDIR) def test_rename_directory_to_existing_file_raises_windows(self): self.check_windows_only() self.check_rename_directory_to_existing_file_raises(errno.EEXIST) def test_rename_to_existing_directory_should_raise_under_windows(self): """Renaming to an existing directory raises OSError under Windows.""" self.check_windows_only() old_path = self.make_path("foo", "bar") new_path = self.make_path("foo", "baz") self.create_dir(old_path) self.create_dir(new_path) self.assert_raises_os_error( errno.EEXIST, self.os.rename, old_path.upper(), new_path.upper() ) def test_rename_to_a_hardlink_of_same_file_should_do_nothing(self): self.skip_real_fs_failure(skip_posix=False) skip_if_symlink_not_supported() file_path = self.make_path("dir", "file") self.create_file(file_path) link_path = self.make_path("link") self.os.link(file_path.upper(), link_path) self.os.rename(file_path, link_path.upper()) self.assertTrue(self.os.path.exists(file_path)) self.assertTrue(self.os.path.exists(link_path)) def test_rename_with_incorrect_source_case(self): # Regression test for #308 base_path = self.make_path("foo") path0 = self.os.path.join(base_path, "bar") path1 = self.os.path.join(base_path, "Bar") self.create_dir(path0) self.os.rename(path1, path0) self.assertTrue(self.os.path.exists(path0)) def test_rename_symlink_to_other_case_does_nothing_in_mac_os(self): # Regression test for #318 self.check_macos_only() path0 = self.make_path("beta") self.os.symlink(self.base_path, path0) path0 = self.make_path("beta", "Beta") path1 = self.make_path("Beta") self.os.rename(path0, path1) self.assertEqual(["beta"], sorted(self.os.listdir(path0))) def test_rename_symlink_to_other_case_works_in_windows(self): self.check_windows_only() skip_if_symlink_not_supported() path0 = self.make_path("beta") self.os.symlink(self.base_path, path0) path0 = self.make_path("beta", "Beta") path1 = self.make_path("Beta") self.os.rename(path0, path1) self.assertEqual(["Beta"], sorted(self.os.listdir(path0))) def test_renames_creates_missing_dirs(self): old_path = self.make_path("foo.txt") self.create_file(old_path) new_path = self.make_path("new", "dir", "bar.txt") self.os.renames(old_path, new_path) self.assertTrue(self.os.path.exists(new_path)) self.assertFalse(self.os.path.exists(old_path)) def test_renames_removes_empty_dirs(self): old_base_path = self.make_path("old") old_path = self.make_path("old", "dir1", "dir2", "foo.txt") other_file = self.os.path.join(old_base_path, "foo.png") self.create_file(old_path) self.create_file(other_file) new_path = self.make_path("new", "bar.txt") self.os.renames(old_path, new_path) self.assertTrue(self.os.path.exists(new_path)) self.assertFalse(self.os.path.exists(old_path)) self.assertTrue(self.os.path.exists(old_base_path)) removed_path = self.os.path.join(old_base_path, "dir1") self.assertFalse(self.os.path.exists(removed_path)) def test_stat_with_mixed_case(self): # Regression test for #310 skip_if_symlink_not_supported() base_path = self.make_path("foo") path = self.os.path.join(base_path, "bar") self.create_dir(path) path = self.os.path.join(path, "Bar") self.os.symlink(base_path, path) path = self.os.path.join(path, "Bar") # used to raise self.os.stat(path) def test_hardlink_works_with_symlink(self): skip_if_symlink_not_supported() base_path = self.make_path("foo") self.create_dir(base_path) symlink_path = self.os.path.join(base_path, "slink") self.os.symlink(base_path.upper(), symlink_path) file_path = self.os.path.join(base_path, "slink", "beta") self.create_file(file_path) link_path = self.os.path.join(base_path, "Slink", "gamma") self.os.link(file_path, link_path) self.assertTrue(self.os.path.exists(link_path)) def test_replace_existing_directory_should_raise_under_windows(self): """Renaming to an existing directory raises OSError under Windows.""" self.check_windows_only() old_path = self.make_path("foo", "bar") new_path = self.make_path("foo", "baz") self.create_dir(old_path) self.create_dir(new_path) self.assert_raises_os_error( errno.EACCES, self.os.replace, old_path, new_path.upper() ) def test_rename_to_existing_directory_under_posix(self): """Renaming to an existing directory changes the existing directory under Posix.""" self.check_posix_only() old_path = self.make_path("foo", "bar") new_path = self.make_path("xyzzy") self.create_dir(self.os.path.join(old_path, "sub")) self.create_dir(new_path) self.os.rename(old_path.upper(), new_path.upper()) self.assertTrue(self.os.path.exists(self.os.path.join(new_path, "sub"))) self.assertFalse(self.os.path.exists(old_path)) def test_rename_file_to_existing_directory_raises_under_posix(self): self.check_posix_only() file_path = self.make_path("foo", "bar", "baz") new_path = self.make_path("xyzzy") self.create_file(file_path) self.create_dir(new_path) self.assert_raises_os_error( errno.EISDIR, self.os.rename, file_path.upper(), new_path.upper() ) def test_rename_to_existent_file_posix(self): """Can rename a file to a used name under Unix.""" self.check_posix_only() directory = self.make_path("xyzzy") old_file_path = self.os.path.join(directory, "plugh_old") new_file_path = self.os.path.join(directory, "plugh_new") self.create_file(old_file_path, contents="test contents 1") self.create_file(new_file_path, contents="test contents 2") self.assertTrue(self.os.path.exists(old_file_path)) self.assertTrue(self.os.path.exists(new_file_path)) self.os.rename(old_file_path.upper(), new_file_path.upper()) self.assertFalse(self.os.path.exists(old_file_path)) self.assertTrue(self.os.path.exists(new_file_path)) self.check_contents(new_file_path, "test contents 1") def test_rename_to_existent_file_windows(self): """Renaming a file to a used name raises OSError under Windows.""" self.check_windows_only() directory = self.make_path("xyzzy") old_file_path = self.os.path.join(directory, "plugh_old") new_file_path = self.os.path.join(directory, "plugh_new") self.create_file(old_file_path, contents="test contents 1") self.create_file(new_file_path, contents="test contents 2") self.assertTrue(self.os.path.exists(old_file_path)) self.assertTrue(self.os.path.exists(new_file_path)) self.assert_raises_os_error( errno.EEXIST, self.os.rename, old_file_path.upper(), new_file_path.upper(), ) def test_replace_to_existent_file(self): """Replaces an existing file (does not work with `rename()` under Windows).""" directory = self.make_path("xyzzy") old_file_path = self.os.path.join(directory, "plugh_old") new_file_path = self.os.path.join(directory, "plugh_new") self.create_file(old_file_path, contents="test contents 1") self.create_file(new_file_path, contents="test contents 2") self.assertTrue(self.os.path.exists(old_file_path)) self.assertTrue(self.os.path.exists(new_file_path)) self.os.replace(old_file_path.upper(), new_file_path.upper()) self.assertFalse(self.os.path.exists(old_file_path)) self.assertTrue(self.os.path.exists(new_file_path)) self.check_contents(new_file_path, "test contents 1") def test_rename_to_nonexistent_dir(self): """Can rename a file to a name in a nonexistent dir.""" directory = self.make_path("xyzzy") old_file_path = self.os.path.join(directory, "plugh_old") new_file_path = self.os.path.join(directory, "no_such_path", "plugh_new") self.create_file(old_file_path, contents="test contents") self.assertTrue(self.os.path.exists(old_file_path)) self.assertFalse(self.os.path.exists(new_file_path)) self.assert_raises_os_error( errno.ENOENT, self.os.rename, old_file_path.upper(), new_file_path.upper(), ) self.assertTrue(self.os.path.exists(old_file_path)) self.assertFalse(self.os.path.exists(new_file_path)) self.check_contents(old_file_path, "test contents") def check_rename_case_only_with_symlink_parent(self): # Regression test for #319 self.os.symlink(self.base_path, self.make_path("link")) dir_upper = self.make_path("link", "Alpha") self.os.mkdir(dir_upper) dir_lower = self.make_path("alpha") self.os.rename(dir_upper, dir_lower) self.assertEqual(["alpha", "link"], sorted(self.os.listdir(self.base_path))) def test_rename_case_only_with_symlink_parent_windows(self): self.check_windows_only() skip_if_symlink_not_supported() self.check_rename_case_only_with_symlink_parent() def test_rename_case_only_with_symlink_parent_macos(self): self.check_macos_only() self.check_rename_case_only_with_symlink_parent() def test_rename_dir(self): """Test a rename of a directory.""" directory = self.make_path("xyzzy") before_dir = self.os.path.join(directory, "before") before_file = self.os.path.join(directory, "before", "file") after_dir = self.os.path.join(directory, "after") after_file = self.os.path.join(directory, "after", "file") self.create_dir(before_dir) self.create_file(before_file, contents="payload") self.assertTrue(self.os.path.exists(before_dir.upper())) self.assertTrue(self.os.path.exists(before_file.upper())) self.assertFalse(self.os.path.exists(after_dir.upper())) self.assertFalse(self.os.path.exists(after_file.upper())) self.os.rename(before_dir.upper(), after_dir) self.assertFalse(self.os.path.exists(before_dir.upper())) self.assertFalse(self.os.path.exists(before_file.upper())) self.assertTrue(self.os.path.exists(after_dir.upper())) self.assertTrue(self.os.path.exists(after_file.upper())) self.check_contents(after_file, "payload") def test_rename_same_filenames(self): """Test renaming when old and new names are the same.""" directory = self.make_path("xyzzy") file_contents = "Spam eggs" file_path = self.os.path.join(directory, "eggs") self.create_file(file_path, contents=file_contents) self.os.rename(file_path, file_path.upper()) self.check_contents(file_path, file_contents) def test_rmdir(self): """Can remove a directory.""" directory = self.make_path("xyzzy") sub_dir = self.make_path("xyzzy", "abccd") other_dir = self.make_path("xyzzy", "cdeed") self.create_dir(directory) self.assertTrue(self.os.path.exists(directory)) self.os.rmdir(directory) self.assertFalse(self.os.path.exists(directory)) self.create_dir(sub_dir) self.create_dir(other_dir) self.os.chdir(sub_dir) self.os.rmdir("../CDEED") self.assertFalse(self.os.path.exists(other_dir)) self.os.chdir("..") self.os.rmdir("AbcCd") self.assertFalse(self.os.path.exists(sub_dir)) def test_rmdir_via_symlink(self): self.check_windows_only() skip_if_symlink_not_supported() base_path = self.make_path("foo", "bar") dir_path = self.os.path.join(base_path, "alpha") self.create_dir(dir_path) link_path = self.os.path.join(base_path, "beta") self.os.symlink(base_path, link_path) self.os.rmdir(link_path + "/Alpha") self.assertFalse(self.os.path.exists(dir_path)) def test_remove_dirs_with_non_top_symlink_succeeds(self): self.check_posix_only() dir_path = self.make_path("dir") dir_link = self.make_path("dir_link") self.create_dir(dir_path) self.os.symlink(dir_path, dir_link) dir_in_dir = self.os.path.join(dir_link, "dir2") self.create_dir(dir_in_dir) self.os.removedirs(dir_in_dir.upper()) self.assertFalse(self.os.path.exists(dir_in_dir)) # ensure that the symlink is not removed self.assertTrue(self.os.path.exists(dir_link)) def test_mkdir_raises_on_symlink_in_posix(self): self.check_posix_only() base_path = self.make_path("foo", "bar") link_path = self.os.path.join(base_path, "link_to_dir") dir_path = self.os.path.join(base_path, "dir") self.create_dir(dir_path) self.os.symlink(dir_path.upper(), link_path.upper()) self.assert_raises_os_error(errno.ENOTDIR, self.os.rmdir, link_path) def test_mkdir_removes_symlink_in_windows(self): self.check_windows_only() skip_if_symlink_not_supported() base_path = self.make_path("foo", "bar") link_path = self.os.path.join(base_path, "link_to_dir") dir_path = self.os.path.join(base_path, "dir") self.create_dir(dir_path) self.os.symlink(dir_path.upper(), link_path.upper()) self.os.rmdir(link_path) self.assertFalse(self.os.path.exists(link_path)) self.assertTrue(self.os.path.exists(dir_path)) def test_mkdir_raises_if_directory_exists(self): """mkdir raises exception if directory already exists.""" directory = self.make_path("xyzzy") self.create_dir(directory) self.assertTrue(self.os.path.exists(directory)) self.assert_raises_os_error(errno.EEXIST, self.os.mkdir, directory.upper()) def test_mkdir_raises_if_file_exists(self): """mkdir raises exception if name already exists as a file.""" directory = self.make_path("xyzzy") file_path = self.os.path.join(directory, "plugh") self.create_file(file_path) self.assertTrue(self.os.path.exists(file_path)) self.assert_raises_os_error(errno.EEXIST, self.os.mkdir, file_path.upper()) def test_mkdir_raises_if_symlink_exists(self): # Regression test for #309 skip_if_symlink_not_supported() path1 = self.make_path("baz") self.os.symlink(path1, path1) path2 = self.make_path("Baz") self.assert_raises_os_error(errno.EEXIST, self.os.mkdir, path2) def check_mkdir_raises_if_parent_is_file(self, error_type): """mkdir raises exception if name already exists as a file.""" directory = self.make_path("xyzzy") file_path = self.os.path.join(directory, "plugh") self.create_file(file_path) self.assert_raises_os_error( error_type, self.os.mkdir, self.os.path.join(file_path.upper(), "ff"), ) def test_mkdir_raises_if_parent_is_file_posix(self): self.check_posix_only() self.check_mkdir_raises_if_parent_is_file(errno.ENOTDIR) def test_mkdir_raises_if_parent_is_file_windows(self): self.check_windows_only() self.check_mkdir_raises_if_parent_is_file(errno.ENOENT) def test_makedirs(self): """makedirs can create a directory even if parent does not exist.""" parent = self.make_path("xyzzy") directory = self.os.path.join(parent, "foo") self.assertFalse(self.os.path.exists(parent)) self.os.makedirs(directory.upper()) self.assertTrue(self.os.path.exists(directory)) def check_makedirs_raises_if_parent_is_file(self, error_type): """makedirs raises exception if a parent component exists as a file.""" file_path = self.make_path("xyzzy") directory = self.os.path.join(file_path, "plugh") self.create_file(file_path) self.assertTrue(self.os.path.exists(file_path)) self.assert_raises_os_error(error_type, self.os.makedirs, directory.upper()) def test_makedirs_raises_if_parent_is_file_posix(self): self.check_posix_only() self.check_makedirs_raises_if_parent_is_file(errno.ENOTDIR) def test_makedirs_raises_if_parent_is_file_windows(self): self.check_windows_only() self.check_makedirs_raises_if_parent_is_file(errno.ENOENT) def test_makedirs_raises_if_parent_is_broken_link(self): self.check_posix_only() link_path = self.make_path("broken_link") self.os.symlink(self.make_path("bogus"), link_path) self.assert_raises_os_error( errno.ENOENT, self.os.makedirs, self.os.path.join(link_path.upper(), "newdir"), ) def test_makedirs_exist_ok(self): """makedirs uses the exist_ok argument""" directory = self.make_path("xyzzy", "foo") self.create_dir(directory) self.assertTrue(self.os.path.exists(directory)) self.assert_raises_os_error(errno.EEXIST, self.os.makedirs, directory.upper()) self.os.makedirs(directory.upper(), exist_ok=True) self.assertTrue(self.os.path.exists(directory)) # test fsync and fdatasync def test_fsync_pass(self): test_file_path = self.make_path("test_file") self.create_file(test_file_path, contents="dummy file contents") test_file = self.open(test_file_path.upper(), "r+", encoding="utf8") test_fd = test_file.fileno() # Test that this doesn't raise anything self.os.fsync(test_fd) # And just for sanity, double-check that this still raises self.assert_raises_os_error(errno.EBADF, self.os.fsync, test_fd + 10) test_file.close() def test_chmod(self): # set up self.check_posix_only() self.skip_real_fs() path = self.make_path("some_file") self.createTestFile(path) # actual tests self.os.chmod(path.upper(), 0o6543) st = self.os.stat(path) self.assert_mode_equal(0o6543, st.st_mode) self.assertTrue(st.st_mode & stat.S_IFREG) self.assertFalse(st.st_mode & stat.S_IFDIR) def test_symlink(self): skip_if_symlink_not_supported() file_path = self.make_path("foo", "bar", "baz") self.create_dir(self.make_path("foo", "bar")) self.os.symlink("bogus", file_path.upper()) self.assertTrue(self.os.path.lexists(file_path)) self.assertFalse(self.os.path.exists(file_path)) self.create_file(self.make_path("Foo", "Bar", "Bogus")) self.assertTrue(self.os.path.lexists(file_path)) self.assertTrue(self.os.path.exists(file_path)) # hard link related tests def test_link_delete(self): skip_if_symlink_not_supported() file1_path = self.make_path("test_file1") file2_path = self.make_path("test_file2") contents1 = "abcdef" # Create file self.create_file(file1_path, contents=contents1) # link to second file self.os.link(file1_path.upper(), file2_path) # delete first file self.os.unlink(file1_path) # assert that second file exists, and its contents are the same self.assertTrue(self.os.path.exists(file2_path)) with self.open(file2_path.upper(), encoding="utf8") as f: self.assertEqual(f.read(), contents1) def test_link_is_existing_file(self): skip_if_symlink_not_supported() file_path = self.make_path("foo", "bar") self.create_file(file_path) self.assert_raises_os_error( errno.EEXIST, self.os.link, file_path.upper(), file_path.upper() ) def test_link_is_broken_symlink(self): # Regression test for #311 skip_if_symlink_not_supported() self.check_case_insensitive_fs() file_path = self.make_path("baz") self.create_file(file_path) path_lower = self.make_path("foo") self.os.symlink(path_lower, path_lower) path_upper = self.make_path("Foo") self.assert_raises_os_error(errno.EEXIST, self.os.link, file_path, path_upper) def test_link_with_changed_case(self): # Regression test for #312 skip_if_symlink_not_supported() self.check_case_insensitive_fs() link_path = self.make_path("link") self.os.symlink(self.base_path, link_path) link_path = self.os.path.join(link_path, "Link") self.assertTrue(self.os.lstat(link_path)) class RealOsModuleTestCaseInsensitiveFS(FakeOsModuleTestCaseInsensitiveFS): def use_real_fs(self): return True class FakeOsModuleTimeTest(FakeOsModuleTestBase): def test_chmod_st_ctime(self): with self.mock_time(start=200): file_path = "some_file" self.filesystem.create_file(file_path) self.assertTrue(self.os.path.exists(file_path)) st = self.os.stat(file_path) self.assertEqual(200, st.st_ctime) # tests self.os.chmod(file_path, 0o765) st = self.os.stat(file_path) self.assertEqual(220, st.st_ctime) def test_utime_sets_current_time_if_args_is_none(self): path = self.make_path("some_file") self.createTestFile(path) with self.mock_time(start=200): self.os.utime(path, times=None) st = self.os.stat(path) self.assertEqual(200, st.st_atime) self.assertEqual(200, st.st_mtime) def test_utime_sets_specified_time(self): # set up path = self.make_path("some_file") self.createTestFile(path) self.os.stat(path) # actual tests self.os.utime(path, times=(1, 2)) st = self.os.stat(path) self.assertEqual(1, st.st_atime) self.assertEqual(2, st.st_mtime) def test_utime_dir(self): # set up path = "/some_dir" self.createTestDirectory(path) # actual tests self.os.utime(path, times=(1.0, 2.0)) st = self.os.stat(path) self.assertEqual(1.0, st.st_atime) self.assertEqual(2.0, st.st_mtime) def test_utime_follow_symlinks(self): path = self.make_path("some_file") self.createTestFile(path) link_path = "/link_to_some_file" self.filesystem.create_symlink(link_path, path) self.os.utime(link_path, times=(1, 2)) st = self.os.stat(link_path) self.assertEqual(1, st.st_atime) self.assertEqual(2, st.st_mtime) def test_utime_no_follow_symlinks(self): path = self.make_path("some_file") self.createTestFile(path) link_path = "/link_to_some_file" self.filesystem.create_symlink(link_path, path) self.os.utime(link_path, times=(1, 2), follow_symlinks=False) st = self.os.stat(link_path) self.assertNotEqual(1, st.st_atime) self.assertNotEqual(2, st.st_mtime) st = self.os.stat(link_path, follow_symlinks=False) self.assertEqual(1, st.st_atime) self.assertEqual(2, st.st_mtime) def test_utime_non_existent(self): path = "/non/existent/file" self.assertFalse(self.os.path.exists(path)) self.assert_raises_os_error(errno.ENOENT, self.os.utime, path, (1, 2)) def test_utime_invalid_times_arg_raises(self): path = "/some_dir" self.createTestDirectory(path) # the error message differs with different Python versions # we don't expect the same message here self.assertRaises(TypeError, self.os.utime, path, (1, 2, 3)) self.assertRaises(TypeError, self.os.utime, path, (1, "str")) def test_utime_sets_specified_time_in_ns(self): # set up path = self.make_path("some_file") self.createTestFile(path) self.os.stat(path) # actual tests self.os.utime(path, ns=(200000000, 400000000)) st = self.os.stat(path) self.assertEqual(0.2, st.st_atime) self.assertEqual(0.4, st.st_mtime) def test_utime_incorrect_ns_argument_raises(self): file_path = "some_file" self.filesystem.create_file(file_path) self.assertRaises(TypeError, self.os.utime, file_path, ns=200000000) self.assertRaises(TypeError, self.os.utime, file_path, ns=("a", "b")) self.assertRaises( ValueError, self.os.utime, file_path, times=(1, 2), ns=(100, 200) ) def test_utime_uses_open_fd_as_path(self): if os.utime not in os.supports_fd: self.skip_real_fs() self.assert_raises_os_error(errno.EBADF, self.os.utime, 5, (1, 2)) path = self.make_path("some_file") self.createTestFile(path) with FakeFileOpen(self.filesystem)(path, encoding="utf8") as f: self.os.utime(f.filedes, times=(1, 2)) st = self.os.stat(path) self.assertEqual(1, st.st_atime) self.assertEqual(2, st.st_mtime) class FakeOsModuleLowLevelFileOpTest(FakeOsModuleTestBase): """Test low level functions `os.open()`, `os.read()` and `os.write()`.""" def test_open_read_only(self): file_path = self.make_path("file1") self.create_file(file_path, contents=b"contents") file_des = self.os.open(file_path, os.O_RDONLY) self.assertEqual(b"contents", self.os.read(file_des, 8)) self.assert_raises_os_error(errno.EBADF, self.os.write, file_des, b"test") self.os.close(file_des) def test_open_read_only_write_zero_bytes_posix(self): self.check_posix_only() file_path = self.make_path("file1") self.create_file(file_path, contents=b"contents") file_des = self.os.open(file_path, os.O_RDONLY) self.assert_raises_os_error(errno.EBADF, self.os.write, file_des, b"test") self.os.close(file_des) def test_open_read_only_write_zero_bytes_windows(self): # under Windows, writing an empty string to a read only file # is not an error self.check_windows_only() file_path = self.make_path("file1") self.create_file(file_path, contents=b"contents") file_des = self.os.open(file_path, os.O_RDONLY) self.assertEqual(0, self.os.write(file_des, b"")) self.os.close(file_des) def test_open_write_only(self): file_path = self.make_path("file1") self.create_file(file_path, contents=b"contents") file_des = self.os.open(file_path, os.O_WRONLY) self.assertEqual(4, self.os.write(file_des, b"test")) self.check_contents(file_path, b"testents") self.os.close(file_des) def test_open_write_only_raises_on_read(self): file_path = self.make_path("file1") self.create_file(file_path, contents=b"contents") file_des = self.os.open(file_path, os.O_WRONLY) self.assert_raises_os_error(errno.EBADF, self.os.read, file_des, 5) self.os.close(file_des) file_des = self.os.open(file_path, os.O_WRONLY | os.O_TRUNC) self.assert_raises_os_error(errno.EBADF, self.os.read, file_des, 5) self.os.close(file_des) file_path2 = self.make_path("file2") file_des = self.os.open(file_path2, os.O_CREAT | os.O_WRONLY) self.assert_raises_os_error(errno.EBADF, self.os.read, file_des, 5) self.os.close(file_des) file_des = self.os.open(file_path2, os.O_CREAT | os.O_WRONLY | os.O_TRUNC) self.assert_raises_os_error(errno.EBADF, self.os.read, file_des, 5) self.os.close(file_des) def test_open_write_only_read_zero_bytes_posix(self): self.check_posix_only() file_path = self.make_path("file1") file_des = self.os.open(file_path, os.O_CREAT | os.O_WRONLY) self.assert_raises_os_error(errno.EBADF, self.os.read, file_des, 0) self.os.close(file_des) def test_open_write_only_read_zero_bytes_windows(self): # under Windows, reading 0 bytes from a write only file is not an error self.check_windows_only() file_path = self.make_path("file1") file_des = self.os.open(file_path, os.O_CREAT | os.O_WRONLY) self.assertEqual(b"", self.os.read(file_des, 0)) self.os.close(file_des) def test_open_read_write(self): file_path = self.make_path("file1") self.create_file(file_path, contents=b"contents") file_des = self.os.open(file_path, os.O_RDWR) self.assertEqual(4, self.os.write(file_des, b"test")) self.check_contents(file_path, b"testents") self.os.close(file_des) def test_open_create_is_read_only(self): file_path = self.make_path("file1") file_des = self.os.open(file_path, os.O_CREAT) self.assertEqual(b"", self.os.read(file_des, 1)) self.assert_raises_os_error(errno.EBADF, self.os.write, file_des, b"foo") self.os.close(file_des) def test_open_create_truncate_is_read_only(self): file_path = self.make_path("file1") file_des = self.os.open(file_path, os.O_CREAT | os.O_TRUNC) self.assertEqual(b"", self.os.read(file_des, 1)) self.assert_raises_os_error(errno.EBADF, self.os.write, file_des, b"foo") self.os.close(file_des) def test_open_raises_if_does_not_exist(self): file_path = self.make_path("file1") self.assert_raises_os_error(errno.ENOENT, self.os.open, file_path, os.O_RDONLY) self.assert_raises_os_error(errno.ENOENT, self.os.open, file_path, os.O_WRONLY) self.assert_raises_os_error(errno.ENOENT, self.os.open, file_path, os.O_RDWR) def test_exclusive_open_raises_without_create_mode(self): self.skip_real_fs() file_path = self.make_path("file1") self.assertRaises(NotImplementedError, self.os.open, file_path, os.O_EXCL) self.assertRaises( NotImplementedError, self.os.open, file_path, os.O_EXCL | os.O_WRONLY, ) self.assertRaises( NotImplementedError, self.os.open, file_path, os.O_EXCL | os.O_RDWR ) self.assertRaises( NotImplementedError, self.os.open, file_path, os.O_EXCL | os.O_TRUNC | os.O_APPEND, ) def test_open_raises_if_parent_does_not_exist(self): path = self.make_path("alpha", "alpha") self.assert_raises_os_error( errno.ENOENT, self.os.open, path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, ) def test_open_truncate(self): file_path = self.make_path("file1") self.create_file(file_path, contents=b"contents") file_des = self.os.open(file_path, os.O_RDWR | os.O_TRUNC) self.assertEqual(b"", self.os.read(file_des, 8)) self.assertEqual(4, self.os.write(file_des, b"test")) self.check_contents(file_path, b"test") self.os.close(file_des) @unittest.skipIf(not TestCase.is_windows, "O_TEMPORARY only present in Windows") def test_temp_file(self): file_path = self.make_path("file1") fd = self.os.open(file_path, os.O_CREAT | os.O_RDWR | os.O_TEMPORARY) self.assertTrue(self.os.path.exists(file_path)) self.os.close(fd) self.assertFalse(self.os.path.exists(file_path)) def test_open_append(self): file_path = self.make_path("file1") self.create_file(file_path, contents=b"contents") file_des = self.os.open(file_path, os.O_WRONLY | os.O_APPEND) self.assertEqual(4, self.os.write(file_des, b"test")) self.check_contents(file_path, b"contentstest") self.os.close(file_des) def test_open_create(self): file_path = self.make_path("file1") file_des = self.os.open(file_path, os.O_RDWR | os.O_CREAT) self.assertTrue(self.os.path.exists(file_path)) self.assertEqual(4, self.os.write(file_des, b"test")) self.check_contents(file_path, "test") self.os.close(file_des) def test_can_read_after_create_exclusive(self): self.check_posix_only() path1 = self.make_path("alpha") file_des = self.os.open(path1, os.O_CREAT | os.O_EXCL) self.assertEqual(b"", self.os.read(file_des, 0)) self.assert_raises_os_error(errno.EBADF, self.os.write, file_des, b"") self.os.close(file_des) def test_open_create_mode_posix(self): self.check_posix_only() file_path = self.make_path("file1") file_des = self.os.open(file_path, os.O_WRONLY | os.O_CREAT, 0o700) self.assertTrue(self.os.path.exists(file_path)) self.assert_raises_os_error(errno.EBADF, self.os.read, file_des, 5) self.assertEqual(4, self.os.write(file_des, b"test")) self.assert_mode_equal(0o700, self.os.stat(file_path).st_mode) self.os.close(file_des) def test_open_create_mode_windows(self): self.check_windows_only() file_path = self.make_path("file1") file_des = self.os.open(file_path, os.O_WRONLY | os.O_CREAT, 0o700) self.assertTrue(self.os.path.exists(file_path)) self.assert_raises_os_error(errno.EBADF, self.os.read, file_des, 5) self.assertEqual(4, self.os.write(file_des, b"test")) self.assert_mode_equal(0o666, self.os.stat(file_path).st_mode) self.os.close(file_des) def testOpenCreateMode444Windows(self): self.check_windows_only() file_path = self.make_path("file1") file_des = self.os.open(file_path, os.O_WRONLY | os.O_CREAT, 0o442) self.assert_mode_equal(0o444, self.os.stat(file_path).st_mode) self.os.close(file_des) self.os.chmod(file_path, 0o666) def testOpenCreateMode666Windows(self): self.check_windows_only() file_path = self.make_path("file1") file_des = self.os.open(file_path, os.O_WRONLY | os.O_CREAT, 0o224) self.assert_mode_equal(0o666, self.os.stat(file_path).st_mode) self.os.close(file_des) def test_open_exclusive(self): file_path = self.make_path("file1") file_des = self.os.open(file_path, os.O_RDWR | os.O_EXCL | os.O_CREAT) self.assertTrue(self.os.path.exists(file_path)) self.os.close(file_des) def test_open_exclusive_raises_if_file_exists(self): file_path = self.make_path("file1") self.create_file(file_path, contents=b"contents") self.assert_raises_os_error( errno.EEXIST, self.os.open, file_path, os.O_RDWR | os.O_EXCL | os.O_CREAT, ) self.assert_raises_os_error( errno.EEXIST, self.os.open, file_path, os.O_RDWR | os.O_EXCL | os.O_CREAT, ) def test_open_exclusive_raises_if_symlink_exists_in_posix(self): self.check_posix_only() link_path = self.make_path("link") link_target = self.make_path("link_target") self.os.symlink(link_target, link_path) self.assert_raises_os_error( errno.EEXIST, self.os.open, link_path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC | os.O_EXCL, ) def test_open_exclusive_if_symlink_exists_works_in_windows(self): self.check_windows_only() skip_if_symlink_not_supported() link_path = self.make_path("link") link_target = self.make_path("link_target") self.os.symlink(link_target, link_path) fd = self.os.open(link_path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC | os.O_EXCL) self.os.close(fd) def test_open_directory_raises_under_windows(self): self.check_windows_only() dir_path = self.make_path("dir") self.create_dir(dir_path) self.assert_raises_os_error(errno.EACCES, self.os.open, dir_path, os.O_RDONLY) self.assert_raises_os_error(errno.EACCES, self.os.open, dir_path, os.O_WRONLY) self.assert_raises_os_error(errno.EACCES, self.os.open, dir_path, os.O_RDWR) def test_open_directory_for_writing_raises_under_posix(self): self.check_posix_only() dir_path = self.make_path("dir") self.create_dir(dir_path) self.assert_raises_os_error(errno.EISDIR, self.os.open, dir_path, os.O_WRONLY) self.assert_raises_os_error(errno.EISDIR, self.os.open, dir_path, os.O_RDWR) def test_open_directory_read_only_under_posix(self): self.check_posix_only() self.skip_real_fs() dir_path = self.make_path("dir") self.create_dir(dir_path) file_des = self.os.open(dir_path, os.O_RDONLY) self.assertEqual(3, file_des) self.os.close(file_des) def test_opening_existing_directory_in_creation_mode(self): self.check_linux_only() dir_path = self.make_path("alpha") self.os.mkdir(dir_path) self.assert_raises_os_error(errno.EISDIR, self.os.open, dir_path, os.O_CREAT) def test_writing_to_existing_directory(self): self.check_macos_only() dir_path = self.make_path("alpha") self.os.mkdir(dir_path) fd = self.os.open(dir_path, os.O_CREAT) self.assert_raises_os_error(errno.EBADF, self.os.write, fd, b"") def test_opening_existing_directory_in_write_mode(self): self.check_posix_only() dir_path = self.make_path("alpha") self.os.mkdir(dir_path) self.assert_raises_os_error(errno.EISDIR, self.os.open, dir_path, os.O_WRONLY) def test_open_mode_posix(self): self.check_posix_only() self.skip_real_fs() file_path = self.make_path("baz") file_des = self.os.open(file_path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC) stat0 = self.os.fstat(file_des) # not a really good test as this replicates the code, # but we don't know the umask at the test system self.assertEqual(0o100777 & ~self.os._umask(), stat0.st_mode) self.os.close(file_des) def test_open_mode_windows(self): self.check_windows_only() file_path = self.make_path("baz") file_des = self.os.open(file_path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC) stat0 = self.os.fstat(file_des) self.assertEqual(0o100666, stat0.st_mode) self.os.close(file_des) def test_write_read(self): file_path = self.make_path("file1") self.create_file(file_path, contents=b"orig contents") new_contents = b"1234567890abcdef" with self.open(file_path, "wb") as fh: fileno = fh.fileno() self.assertEqual(len(new_contents), self.os.write(fileno, new_contents)) self.check_contents(file_path, new_contents) with self.open(file_path, "rb") as fh: fileno = fh.fileno() self.assertEqual(b"", self.os.read(fileno, 0)) self.assertEqual(new_contents[0:2], self.os.read(fileno, 2)) self.assertEqual(new_contents[2:10], self.os.read(fileno, 8)) self.assertEqual(new_contents[10:], self.os.read(fileno, 100)) self.assertEqual(b"", self.os.read(fileno, 10)) self.assert_raises_os_error(errno.EBADF, self.os.write, fileno, new_contents) self.assert_raises_os_error(errno.EBADF, self.os.read, fileno, 10) def test_write_from_different_f_ds(self): # Regression test for #211 file_path = self.make_path("baz") fd0 = self.os.open(file_path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC) fd1 = self.os.open(file_path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC) self.os.write(fd0, b"aaaa") self.os.write(fd1, b"bb") self.assertEqual(4, self.os.path.getsize(file_path)) self.check_contents(file_path, b"bbaa") self.os.close(fd1) self.os.close(fd0) def test_write_from_different_fds_with_append(self): # Regression test for #268 file_path = self.make_path("baz") fd0 = self.os.open(file_path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC) fd1 = self.os.open(file_path, os.O_WRONLY | os.O_APPEND) self.os.write(fd0, b"aaa") self.os.write(fd1, b"bbb") self.assertEqual(6, self.os.path.getsize(file_path)) self.check_contents(file_path, b"aaabbb") self.os.close(fd1) self.os.close(fd0) def test_read_only_read_after_write(self): # Regression test for #269 self.check_posix_only() file_path = self.make_path("foo", "bar", "baz") self.create_file(file_path, contents=b"test") fd0 = self.os.open(file_path, os.O_CREAT) fd1 = self.os.open(file_path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC) self.assertEqual(b"", self.os.read(fd0, 0)) self.os.close(fd1) self.os.close(fd0) def test_read_after_closing_write_descriptor(self): # Regression test for #271 file_path = self.make_path("baz") fd0 = self.os.open(file_path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC) fd1 = self.os.open(file_path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC) fd2 = self.os.open(file_path, os.O_CREAT) self.os.write(fd1, b"abc") self.os.close(fd0) self.assertEqual(b"abc", self.os.read(fd2, 3)) self.os.close(fd2) self.os.close(fd1) def test_writing_behind_end_of_file(self): # Regression test for #273 file_path = self.make_path("baz") fd1 = self.os.open(file_path, os.O_CREAT) fd2 = self.os.open(file_path, os.O_RDWR) self.os.write(fd2, b"m") fd3 = self.os.open(file_path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC) self.assertEqual(b"", self.os.read(fd2, 1)) self.os.write(fd2, b"m") self.assertEqual(b"\x00m", self.os.read(fd1, 2)) self.os.close(fd1) self.os.close(fd2) self.os.close(fd3) def test_devnull_posix(self): self.check_posix_only() # make sure os.devnull is correctly set after changing the filesystem self.setup_fake_fs() self.assertTrue(self.os.path.exists(self.os.devnull)) def test_devnull_windows(self): self.check_windows_only() # make sure os.devnull is correctly set after changing the filesystem self.setup_fake_fs() if sys.version_info < (3, 8): self.assertFalse(self.os.path.exists(self.os.devnull)) else: self.assertTrue(self.os.path.exists(self.os.devnull)) def test_write_devnull(self): fd = self.os.open(self.os.devnull, os.O_RDWR) self.assertEqual(4, self.os.write(fd, b"test")) self.assertEqual(b"", self.os.read(fd, 4)) self.os.close(fd) fd = self.os.open(self.os.devnull, os.O_RDONLY) self.assertEqual(b"", self.os.read(fd, 4)) self.os.close(fd) def test_sendfile_with_invalid_fd(self): self.check_linux_only() self.assert_raises_os_error(errno.EBADF, self.os.sendfile, 100, 101, 0, 100) src_file_path = self.make_path("foo") dst_file_path = self.make_path("bar") self.create_file(src_file_path, "testcontent") self.create_file(dst_file_path) fd1 = self.os.open(src_file_path, os.O_RDONLY) fd2 = self.os.open(dst_file_path, os.O_RDONLY) self.assert_raises_os_error(errno.EBADF, self.os.sendfile, fd2, fd1, 0, 4) def test_sendfile_no_offset(self): self.check_linux_only() src_file_path = self.make_path("foo") dst_file_path = self.make_path("bar") self.create_file(src_file_path, "testcontent") self.create_file(dst_file_path) fd1 = self.os.open(src_file_path, os.O_RDONLY) fd2 = self.os.open(dst_file_path, os.O_RDWR) self.os.sendfile(fd2, fd1, 0, 3) self.os.close(fd2) self.os.close(fd1) with self.open(dst_file_path, encoding="utf8") as f: self.assertEqual("tes", f.read()) def test_sendfile_with_offset(self): self.check_linux_only() src_file_path = self.make_path("foo") dst_file_path = self.make_path("bar") self.create_file(src_file_path, "testcontent") self.create_file(dst_file_path) fd1 = self.os.open(src_file_path, os.O_RDONLY) fd2 = self.os.open(dst_file_path, os.O_RDWR) self.os.sendfile(fd2, fd1, 4, 4) self.os.close(fd2) self.os.close(fd1) with self.open(dst_file_path, encoding="utf8") as f: self.assertEqual("cont", f.read()) def test_sendfile_twice(self): self.check_linux_only() src_file_path = self.make_path("foo") dst_file_path = self.make_path("bar") self.create_file(src_file_path, "testcontent") self.create_file(dst_file_path) fd1 = self.os.open(src_file_path, os.O_RDONLY) fd2 = self.os.open(dst_file_path, os.O_RDWR) self.os.sendfile(fd2, fd1, 4, 4) self.os.sendfile(fd2, fd1, 4, 4) self.os.close(fd2) self.os.close(fd1) with self.open(dst_file_path, encoding="utf8") as f: self.assertEqual("contcont", f.read()) def test_sendfile_offset_none(self): self.check_linux_only() src_file_path = self.make_path("foo") dst_file_path = self.make_path("bar") self.create_file(src_file_path, "testcontent") self.create_file(dst_file_path) fd1 = self.os.open(src_file_path, os.O_RDONLY) fd2 = self.os.open(dst_file_path, os.O_RDWR) self.os.sendfile(fd2, fd1, None, 4) self.os.sendfile(fd2, fd1, None, 3) self.os.close(fd2) self.os.close(fd1) with self.open(dst_file_path, encoding="utf8") as f: self.assertEqual("testcon", f.read()) @unittest.skipIf(not TestCase.is_macos, "Testing MacOs only behavior") def test_no_sendfile_to_regular_file_under_macos(self): src_file_path = self.make_path("foo") dst_file_path = self.make_path("bar") self.create_file(src_file_path, "testcontent") self.create_file(dst_file_path) fd1 = self.os.open(src_file_path, os.O_RDONLY) fd2 = self.os.open(dst_file_path, os.O_RDWR) # raises socket operation on non-socket self.assertRaises(OSError, self.os.sendfile, fd2, fd1, 0, 3) self.os.close(fd2) self.os.close(fd1) class RealOsModuleLowLevelFileOpTest(FakeOsModuleLowLevelFileOpTest): def use_real_fs(self): return True class FakeOsModuleWalkTest(FakeOsModuleTestBase): def assertWalkResults(self, expected, top, topdown=True, followlinks=False): # as the result of walk is unsorted, we have to check against # sorted results result = list( step for step in self.os.walk(top, topdown=topdown, followlinks=followlinks) ) result = sorted(result, key=lambda lst: lst[0]) expected = sorted(expected, key=lambda lst: lst[0]) self.assertEqual(len(expected), len(result)) for entry, expected_entry in zip(result, expected): self.assertEqual(expected_entry[0], entry[0]) self.assertEqual(expected_entry[1], sorted(entry[1])) self.assertEqual(expected_entry[2], sorted(entry[2])) def ResetErrno(self): """Reset the last seen errno.""" self.last_errno = False def StoreErrno(self, os_error): """Store the last errno we saw.""" self.last_errno = os_error.errno def GetErrno(self): """Return the last errno we saw.""" return self.last_errno def test_walk_top_down(self): """Walk down ordering is correct.""" base_dir = self.make_path("foo") self.create_file(self.os.path.join(base_dir, "1.txt")) self.create_file(self.os.path.join(base_dir, "bar1", "2.txt")) self.create_file(self.os.path.join(base_dir, "bar1", "baz", "3.txt")) self.create_file(self.os.path.join(base_dir, "bar2", "4.txt")) expected = [ (base_dir, ["bar1", "bar2"], ["1.txt"]), (self.os.path.join(base_dir, "bar1"), ["baz"], ["2.txt"]), (self.os.path.join(base_dir, "bar1", "baz"), [], ["3.txt"]), (self.os.path.join(base_dir, "bar2"), [], ["4.txt"]), ] self.assertWalkResults(expected, base_dir) def test_walk_bottom_up(self): """Walk up ordering is correct.""" base_dir = self.make_path("foo") self.create_file(self.os.path.join(base_dir, "bar1", "baz", "1.txt")) self.create_file(self.os.path.join(base_dir, "bar1", "2.txt")) self.create_file(self.os.path.join(base_dir, "bar2", "3.txt")) self.create_file(self.os.path.join(base_dir, "4.txt")) expected = [ (self.os.path.join(base_dir, "bar1", "baz"), [], ["1.txt"]), (self.os.path.join(base_dir, "bar1"), ["baz"], ["2.txt"]), (self.os.path.join(base_dir, "bar2"), [], ["3.txt"]), (base_dir, ["bar1", "bar2"], ["4.txt"]), ] self.assertWalkResults(expected, self.make_path("foo"), topdown=False) def test_walk_raises_if_non_existent(self): """Raises an exception when attempting to walk non-existent directory.""" directory = self.make_path("foo", "bar") self.assertEqual(False, self.os.path.exists(directory)) generator = self.os.walk(directory) self.assertRaises(StopIteration, next, generator) def test_walk_raises_if_not_directory(self): """Raises an exception when attempting to walk a non-directory.""" filename = self.make_path("foo", "bar") self.create_file(filename) generator = self.os.walk(filename) self.assertRaises(StopIteration, next, generator) def test_walk_calls_on_error_if_non_existent(self): """Calls onerror with correct errno when walking non-existent directory.""" self.ResetErrno() directory = self.make_path("foo", "bar") self.assertEqual(False, self.os.path.exists(directory)) # Calling os.walk on a non-existent directory should trigger # a call to the onerror method. # We do not actually care what, if anything, is returned. for _ in self.os.walk(directory, onerror=self.StoreErrno): pass self.assertTrue(self.GetErrno() in (errno.ENOTDIR, errno.ENOENT)) def test_walk_calls_on_error_if_not_directory(self): """Calls onerror with correct errno when walking non-directory.""" self.ResetErrno() filename = self.make_path("foobar") self.create_file(filename) self.assertEqual(True, self.os.path.exists(filename)) # Calling `os.walk` on a file should trigger a call to the # `onerror` method. # We do not actually care what, if anything, is returned. for _ in self.os.walk(filename, onerror=self.StoreErrno): pass self.assertTrue(self.GetErrno() in (errno.ENOTDIR, errno.EACCES)) def test_walk_skips_removed_directories(self): """Caller can modify list of directories to visit while walking.""" root = self.make_path("foo") visit = "visit" no_visit = "no_visit" self.create_file(self.os.path.join(root, "bar")) self.create_file(self.os.path.join(root, visit, "1.txt")) self.create_file(self.os.path.join(root, visit, "2.txt")) self.create_file(self.os.path.join(root, no_visit, "3.txt")) self.create_file(self.os.path.join(root, no_visit, "4.txt")) generator = self.os.walk(self.make_path("foo")) root_contents = next(generator) root_contents[1].remove(no_visit) visited_visit_directory = False for root, _dirs, _files in iter(generator): self.assertEqual(False, root.endswith(self.os.path.sep + no_visit)) if root.endswith(self.os.path.sep + visit): visited_visit_directory = True self.assertEqual(True, visited_visit_directory) def test_walk_followsymlink_disabled(self): self.check_posix_only() base_dir = self.make_path("foo") link_dir = self.make_path("linked") self.create_file(self.os.path.join(link_dir, "subfile")) self.create_file(self.os.path.join(base_dir, "bar", "baz")) self.create_file(self.os.path.join(base_dir, "bar", "xyzzy", "plugh")) self.create_symlink(self.os.path.join(base_dir, "created_link"), link_dir) expected = [ (base_dir, ["bar", "created_link"], []), (self.os.path.join(base_dir, "bar"), ["xyzzy"], ["baz"]), (self.os.path.join(base_dir, "bar", "xyzzy"), [], ["plugh"]), ] self.assertWalkResults(expected, base_dir, followlinks=False) expected = [(self.os.path.join(base_dir, "created_link"), [], ["subfile"])] self.assertWalkResults( expected, self.os.path.join(base_dir, "created_link"), followlinks=False, ) def test_walk_followsymlink_enabled(self): self.check_posix_only() base_dir = self.make_path("foo") link_dir = self.make_path("linked") self.create_file(self.os.path.join(link_dir, "subfile")) self.create_file(self.os.path.join(base_dir, "bar", "baz")) self.create_file(self.os.path.join(base_dir, "bar", "xyzzy", "plugh")) self.create_symlink( self.os.path.join(base_dir, "created_link"), self.os.path.join(link_dir), ) expected = [ (base_dir, ["bar", "created_link"], []), (self.os.path.join(base_dir, "bar"), ["xyzzy"], ["baz"]), (self.os.path.join(base_dir, "bar", "xyzzy"), [], ["plugh"]), (self.os.path.join(base_dir, "created_link"), [], ["subfile"]), ] self.assertWalkResults(expected, base_dir, followlinks=True) expected = [(self.os.path.join(base_dir, "created_link"), [], ["subfile"])] self.assertWalkResults( expected, self.os.path.join(base_dir, "created_link"), followlinks=True, ) def test_walk_linked_file_in_subdir(self): # regression test for #559 (tested for link on incomplete path) self.check_posix_only() # need to have a top-level link to reproduce the bug - skip real fs self.skip_real_fs() file_path = "/foo/bar/baz" self.create_file(file_path) self.create_symlink("bar", file_path) expected = [("/foo", ["bar"], []), ("/foo/bar", [], ["baz"])] self.assertWalkResults(expected, "/foo") def test_base_dirpath(self): # regression test for #512 file_path = self.make_path("foo", "bar", "baz") self.create_file(file_path) variants = [ self.make_path("foo", "bar"), self.make_path("foo", "..", "foo", "bar"), self.make_path("foo", "..", "foo", "bar") + self.os.path.sep * 3, self.make_path("foo") + self.os.path.sep * 3 + "bar", ] for base_dir in variants: for dirpath, _dirnames, _filenames in self.os.walk(base_dir): self.assertEqual(dirpath, base_dir) file_path = self.make_path("foo", "bar", "dir", "baz") self.create_file(file_path) for base_dir in variants: for dirpath, _dirnames, _filenames in self.os.walk(base_dir): self.assertTrue(dirpath.startswith(base_dir)) class RealOsModuleWalkTest(FakeOsModuleWalkTest): def use_real_fs(self): return True class FakeOsModuleDirFdTest(FakeOsModuleTestBase): def setUp(self): super().setUp() self.check_posix_only() if not self.use_real_fs(): # in the real OS, we test the option as is, in the fake OS # we test both the supported and unsupported option self.os.supports_dir_fd.clear() self.dir_fd_path = self.make_path("foo") self.create_dir(self.dir_fd_path) self.dir_fd = self.os.open(self.dir_fd_path, os.O_RDONLY) self.fname = "baz" self.fpath = self.os.path.join(self.dir_fd_path, self.fname) self.create_file(self.fpath) def add_supported_function(self, fct): if not self.use_real_fs(): self.os.supports_dir_fd.add(fct) def test_access(self): def os_access(): return self.os.access(self.fname, self.os.F_OK, dir_fd=self.dir_fd) if self.os.access not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_access() self.add_supported_function(self.os.access) if self.os.access in self.os.supports_dir_fd: self.assertTrue(os_access()) def test_chmod(self): def os_chmod(): self.os.chmod(self.fname, 0o6543, dir_fd=self.dir_fd) if self.os.chmod not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_chmod() self.add_supported_function(self.os.chmod) if self.os.chmod in self.os.supports_dir_fd: os_chmod() st = self.os.stat(self.fpath) self.assert_mode_equal(0o6543, st.st_mode) @unittest.skipIf(not hasattr(os, "chown"), "chown not on all platforms available") def test_chown(self): def os_chown(): self.os.chown(self.fname, 100, 101, dir_fd=self.dir_fd) self.skip_real_fs() if self.os.chown not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_chown() self.add_supported_function(self.os.chown) os_chown() st = self.os.stat(self.fpath) self.assertEqual(100, st[stat.ST_UID]) self.assertEqual(101, st[stat.ST_GID]) def test_link_src_fd(self): def os_link(): self.os.link(self.fname, link_dest, src_dir_fd=self.dir_fd) link_dest = self.make_path("bat") if self.os.link not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_link() self.add_supported_function(self.os.link) if self.os.link in self.os.supports_dir_fd: os_link() self.assertTrue(self.os.path.exists(link_dest)) def test_link_dst_fd(self): def os_link(): self.os.link(self.fpath, "bat", dst_dir_fd=self.dir_fd) if self.os.link not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_link() self.add_supported_function(self.os.link) if self.os.link in self.os.supports_dir_fd: os_link() link_path = self.os.path.join(self.dir_fd_path, "bat") self.assertTrue(self.os.path.exists(link_path)) def test_symlink(self): def os_symlink(): self.os.symlink(self.fpath, "bat", dir_fd=self.dir_fd) if self.os.symlink not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_symlink() self.add_supported_function(self.os.symlink) if self.os.symlink in self.os.supports_dir_fd: os_symlink() link_path = self.os.path.join(self.dir_fd_path, "bat") self.assertTrue(self.os.path.exists(link_path)) def test_readlink(self): def os_readlink(): return self.os.readlink("lemon/tree", dir_fd=self.dir_fd) link_dir = self.os.path.join(self.dir_fd_path, "lemon") self.create_dir(link_dir) self.create_symlink(self.os.path.join(link_dir, "tree"), self.fpath) if self.os.readlink not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_readlink() self.add_supported_function(self.os.readlink) if self.os.readlink in self.os.supports_dir_fd: self.assertEqual(self.fpath, os_readlink()) def test_stat(self): def os_stat(): return self.os.stat(self.fname, dir_fd=self.dir_fd) if self.os.stat not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_stat() self.add_supported_function(self.os.stat) if self.os.stat in self.os.supports_dir_fd: self.assertEqual(os_stat().st_mode, 0o100644) def test_lstat(self): st = self.os.lstat(self.fname, dir_fd=self.dir_fd) self.assertEqual(st.st_mode, 0o100644) def test_mkdir(self): def os_mkdir(): self.os.mkdir("newdir", dir_fd=self.dir_fd) newdir_path = self.os.path.join(self.dir_fd_path, "newdir") if self.os.mkdir not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_mkdir() self.add_supported_function(self.os.mkdir) if self.os.mkdir in self.os.supports_dir_fd: os_mkdir() self.assertTrue(self.os.path.exists(newdir_path)) def test_rmdir(self): def os_rmdir(): self.os.rmdir("dir", dir_fd=self.dir_fd) dir_path = self.os.path.join(self.dir_fd_path, "dir") self.create_dir(dir_path) self.assertTrue(self.os.path.exists(dir_path)) if self.os.rmdir not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_rmdir() self.add_supported_function(self.os.rmdir) if self.os.rmdir in self.os.supports_dir_fd: os_rmdir() self.assertFalse(self.os.path.exists(dir_path)) @unittest.skipIf(not hasattr(os, "mknod"), "mknod not on all platforms available") def test_mknod(self): def os_mknod(): self.os.mknod("newdir", dir_fd=self.dir_fd) if self.os.mknod not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_mknod() self.add_supported_function(self.os.mknod) if self.os.mknod in self.os.supports_dir_fd: if self.is_macos and sys.version_info >= (3, 13) and not is_root(): self.skipTest("Needs root rights under macos") os_mknod() newdir_path = self.os.path.join(self.dir_fd_path, "newdir") self.assertTrue(self.os.path.exists(newdir_path)) def test_rename_src_fd(self): def os_rename(): self.os.rename(self.fname, new_name, src_dir_fd=self.dir_fd) new_name = self.os.path.join(self.dir_fd_path, "batz") if self.os.rename not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_rename() self.add_supported_function(self.os.rename) if self.os.rename in self.os.supports_dir_fd: os_rename() self.assertTrue(self.os.path.exists(new_name)) def test_rename_dst_fd(self): def os_rename(): self.os.rename(self.fpath, "batz", dst_dir_fd=self.dir_fd) if self.os.rename not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_rename() self.add_supported_function(self.os.rename) if self.os.rename in self.os.supports_dir_fd: os_rename() new_path = self.os.path.join(self.dir_fd_path, "batz") self.assertTrue(self.os.path.exists(new_path)) def test_replace_src_fd(self): new_name = self.os.path.join(self.dir_fd_path, "batz") self.os.replace(self.fname, new_name, src_dir_fd=self.dir_fd) self.assertTrue(self.os.path.exists(new_name)) def test_replace_dst_fd(self): self.os.replace(self.fpath, "batz", dst_dir_fd=self.dir_fd) new_path = self.os.path.join(self.dir_fd_path, "batz") self.assertTrue(self.os.path.exists(new_path)) def test_remove(self): self.os.remove(self.fname, dir_fd=self.dir_fd) self.assertFalse(self.os.path.exists(self.fpath)) def test_unlink(self): def os_unlink(): self.os.unlink(self.fname, dir_fd=self.dir_fd) if self.os.unlink not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_unlink() self.add_supported_function(self.os.unlink) if self.os.unlink in self.os.supports_dir_fd: os_unlink() self.assertFalse(self.os.path.exists(self.fpath)) def test_utime(self): def os_utime(): self.os.utime(self.fname, times=(1, 2), dir_fd=self.dir_fd) if self.os.utime not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_utime() self.add_supported_function(self.os.utime) if self.os.utime in self.os.supports_dir_fd: os_utime() st = self.os.stat(self.fpath) self.assertEqual(1, st.st_atime) self.assertEqual(2, st.st_mtime) def test_open(self): def os_open(): return self.os.open(self.fname, os.O_RDONLY, dir_fd=self.dir_fd) if self.os.open not in self.os.supports_dir_fd: with self.assertRaises(NotImplementedError): os_open() self.add_supported_function(self.os.open) if self.os.open in self.os.supports_dir_fd: self.assertLess(0, os_open()) class RealOsModuleDirFdTest(FakeOsModuleDirFdTest): def use_real_fs(self): return True class StatPropagationTest(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator="/") self.os = fake_os.FakeOsModule(self.filesystem) self.open = fake_open.FakeFileOpen(self.filesystem) def test_file_size_updated_via_close(self): """test that file size gets updated via close().""" file_dir = "xyzzy" file_path = "xyzzy/close" content = "This is a test." self.os.mkdir(file_dir) fh = self.open(file_path, "w", encoding="utf8") self.assertEqual(0, self.os.stat(file_path)[stat.ST_SIZE]) self.assertEqual("", self.filesystem.get_object(file_path).contents) fh.write(content) self.assertEqual(0, self.os.stat(file_path)[stat.ST_SIZE]) self.assertEqual("", self.filesystem.get_object(file_path).contents) fh.close() self.assertEqual(len(content), self.os.stat(file_path)[stat.ST_SIZE]) self.assertEqual(content, self.filesystem.get_object(file_path).contents) def test_file_size_not_reset_after_close(self): file_dir = "xyzzy" file_path = "xyzzy/close" self.os.mkdir(file_dir) size = 1234 # The file has size, but no content. When the file is opened for # reading, its size should be preserved. self.filesystem.create_file(file_path, st_size=size) fh = self.open(file_path, "r", encoding="utf8") fh.close() self.assertEqual(size, self.open(file_path, "r", encoding="utf8").size()) def test_file_size_after_write(self): file_path = "test_file" original_content = "abcdef" original_size = len(original_content) self.filesystem.create_file(file_path, contents=original_content) added_content = "foo bar" expected_size = original_size + len(added_content) fh = self.open(file_path, "a", encoding="utf8") fh.write(added_content) self.assertEqual(original_size, fh.size()) fh.close() self.assertEqual( expected_size, self.open(file_path, "r", encoding="utf8").size() ) def test_large_file_size_after_write(self): file_path = "test_file" original_content = "abcdef" original_size = len(original_content) self.filesystem.create_file(file_path, st_size=original_size) added_content = "foo bar" fh = self.open(file_path, "a", encoding="utf8") self.assertRaises( fake_file.FakeLargeFileIoException, lambda: fh.write(added_content), ) def test_file_size_updated_via_flush(self): """test that file size gets updated via flush().""" file_dir = "xyzzy" file_name = "flush" file_path = self.os.path.join(file_dir, file_name) content = "This might be a test." self.os.mkdir(file_dir) fh = self.open(file_path, "w", encoding="utf8") self.assertEqual(0, self.os.stat(file_path)[stat.ST_SIZE]) self.assertEqual("", self.filesystem.get_object(file_path).contents) fh.write(content) self.assertEqual(0, self.os.stat(file_path)[stat.ST_SIZE]) self.assertEqual("", self.filesystem.get_object(file_path).contents) fh.flush() self.assertEqual(len(content), self.os.stat(file_path)[stat.ST_SIZE]) self.assertEqual(content, self.filesystem.get_object(file_path).contents) fh.close() self.assertEqual(len(content), self.os.stat(file_path)[stat.ST_SIZE]) self.assertEqual(content, self.filesystem.get_object(file_path).contents) def test_file_size_truncation(self): """test that file size gets updated via open().""" file_dir = "xyzzy" file_path = "xyzzy/truncation" content = "AAA content." # pre-create file with content self.os.mkdir(file_dir) fh = self.open(file_path, "w", encoding="utf8") fh.write(content) fh.close() self.assertEqual(len(content), self.os.stat(file_path)[stat.ST_SIZE]) self.assertEqual(content, self.filesystem.get_object(file_path).contents) # test file truncation fh = self.open(file_path, "w", encoding="utf8") self.assertEqual(0, self.os.stat(file_path)[stat.ST_SIZE]) self.assertEqual("", self.filesystem.get_object(file_path).contents) fh.close() class FakeScandirTest(FakeOsModuleTestBase): FILE_SIZE = 50 LINKED_FILE_SIZE = 10 def used_scandir(self): return self.os.scandir def setUp(self): super().setUp() self.supports_symlinks = not self.is_windows or not self.use_real_fs() self.scandir = self.used_scandir() self.directory = self.make_path("xyzzy", "plugh") link_dir = self.make_path("linked", "plugh") self.linked_file_path = self.os.path.join(link_dir, "file") self.linked_dir_path = self.os.path.join(link_dir, "dir") self.rel_linked_dir_path = self.os.path.join( "..", "..", "linked", "plugh", "dir" ) self.rel_linked_file_path = self.os.path.join( "..", "..", "linked", "plugh", "file" ) self.dir_path = self.os.path.join(self.directory, "dir") self.file_path = self.os.path.join(self.directory, "file") self.file_link_path = self.os.path.join(self.directory, "link_file") self.dir_link_path = self.os.path.join(self.directory, "link_dir") self.file_rel_link_path = self.os.path.join(self.directory, "rel_link_file") self.dir_rel_link_path = self.os.path.join(self.directory, "rel_link_dir") self.create_dir(self.dir_path) self.create_file(self.file_path, contents=b"b" * self.FILE_SIZE) if self.supports_symlinks: self.create_dir(self.linked_dir_path) self.create_file( self.linked_file_path, contents=b"a" * self.LINKED_FILE_SIZE ) self.create_symlink(self.dir_link_path, self.linked_dir_path) self.create_symlink(self.file_link_path, self.linked_file_path) self.create_symlink(self.dir_rel_link_path, self.rel_linked_dir_path) self.create_symlink(self.file_rel_link_path, self.rel_linked_file_path) # Changing the working directory below is to make sure relative paths # to the files and directories created above are reasonable. # Corner-cases about relative paths are better checked in tests created # for that purpose. # # WARNING: This is self.pretest_cwd and not self.cwd as the latter is # used by superclass RealFsTestCase. self.pretest_cwd = self.os.getcwd() self.os.chdir(self.base_path) self.dir_entries = list(self.do_scandir()) self.dir_entries.sort(key=lambda entry: entry.name) def tearDown(self): self.os.chdir(self.pretest_cwd) super().tearDown() def do_scandir(self): """Hook to override how scandir is called.""" return self.scandir(self.directory) def scandir_path(self): """Hook to override the expected scandir() path in DirEntry.path.""" return self.directory def test_paths(self): sorted_names = ["dir", "file"] if self.supports_symlinks: sorted_names.extend( ["link_dir", "link_file", "rel_link_dir", "rel_link_file"] ) self.assertEqual(len(sorted_names), len(self.dir_entries)) self.assertEqual(sorted_names, [entry.name for entry in self.dir_entries]) sorted_paths = [ self.os.path.join(self.scandir_path(), name) for name in sorted_names ] self.assertEqual(sorted_paths, [entry.path for entry in self.dir_entries]) def test_isfile(self): self.assertFalse(self.dir_entries[0].is_file()) self.assertTrue(self.dir_entries[1].is_file()) if self.supports_symlinks: self.assertFalse(self.dir_entries[2].is_file()) self.assertFalse(self.dir_entries[2].is_file(follow_symlinks=False)) self.assertTrue(self.dir_entries[3].is_file()) self.assertFalse(self.dir_entries[3].is_file(follow_symlinks=False)) self.assertFalse(self.dir_entries[4].is_file()) self.assertFalse(self.dir_entries[4].is_file(follow_symlinks=False)) self.assertTrue(self.dir_entries[5].is_file()) self.assertFalse(self.dir_entries[5].is_file(follow_symlinks=False)) def test_isdir(self): self.assertTrue(self.dir_entries[0].is_dir()) self.assertFalse(self.dir_entries[1].is_dir()) if self.supports_symlinks: self.assertTrue(self.dir_entries[2].is_dir()) self.assertFalse(self.dir_entries[2].is_dir(follow_symlinks=False)) self.assertFalse(self.dir_entries[3].is_dir()) self.assertFalse(self.dir_entries[3].is_dir(follow_symlinks=False)) self.assertTrue(self.dir_entries[4].is_dir()) self.assertFalse(self.dir_entries[4].is_dir(follow_symlinks=False)) self.assertFalse(self.dir_entries[5].is_dir()) self.assertFalse(self.dir_entries[5].is_dir(follow_symlinks=False)) def test_is_link(self): if self.supports_symlinks: self.assertFalse(self.dir_entries[0].is_symlink()) self.assertFalse(self.dir_entries[1].is_symlink()) self.assertTrue(self.dir_entries[2].is_symlink()) self.assertTrue(self.dir_entries[3].is_symlink()) self.assertTrue(self.dir_entries[4].is_symlink()) self.assertTrue(self.dir_entries[5].is_symlink()) def test_path_links_not_resolved(self): # regression test for #350 skip_if_symlink_not_supported() dir_path = self.make_path("A", "B", "C") self.os.makedirs(self.os.path.join(dir_path, "D")) link_path = self.make_path("A", "C") self.os.symlink(dir_path, link_path) self.assertEqual( [self.os.path.join(link_path, "D")], [f.path for f in self.scandir(link_path)], ) def test_inode(self): if self.use_real_fs(): if self.is_windows: self.skipTest("inode seems not to work in scandir module under Windows") if IN_DOCKER: self.skipTest("inode seems not to work in a Docker container") self.assertEqual( self.os.stat(self.dir_path).st_ino, self.dir_entries[0].inode() ) self.assertEqual( self.os.stat(self.file_path).st_ino, self.dir_entries[1].inode() ) if self.supports_symlinks: self.assertEqual( self.os.lstat(self.dir_link_path).st_ino, self.dir_entries[2].inode(), ) self.assertEqual( self.os.lstat(self.file_link_path).st_ino, self.dir_entries[3].inode(), ) self.assertEqual( self.os.lstat(self.dir_rel_link_path).st_ino, self.dir_entries[4].inode(), ) self.assertEqual( self.os.lstat(self.file_rel_link_path).st_ino, self.dir_entries[5].inode(), ) def test_scandir_none(self): result1 = [entry.path for entry in self.scandir(None)] result2 = [entry.path for entry in self.scandir(".")] self.assertEqual(result1, result2) def test_scandir_stat_nlink(self): # regression test for #350 stat_nlink = self.os.stat(self.file_path).st_nlink self.assertEqual(1, stat_nlink) dir_iter = self.scandir(self.directory) for item in dir_iter: if item.path == self.file_path: scandir_stat_nlink = item.stat().st_nlink if self.is_windows_fs: self.assertEqual(0, scandir_stat_nlink) else: self.assertEqual(1, scandir_stat_nlink) self.assertEqual(1, self.os.stat(self.file_path).st_nlink) @unittest.skipIf(not hasattr(os, "O_DIRECTORY"), "opening directory not supported") def test_scandir_with_fd(self): # regression test for #723 temp_dir = self.make_path("tmp", "dir") self.create_dir(temp_dir) self.create_file(self.os.path.join(temp_dir, "file1")) self.create_file(self.os.path.join(temp_dir, "file2")) self.create_dir(self.os.path.join(temp_dir, "subdir")) self.os.chdir(temp_dir) fd = self.os.open(temp_dir, flags=os.O_RDONLY | os.O_DIRECTORY) children = [dir_entry.name for dir_entry in self.os.scandir(fd)] assert sorted(children) == ["file1", "file2", "subdir"] def test_file_removed_during_scandir(self): # regression test for #1051 dir_path = self.make_path("wls") file1_path = self.os.path.join(dir_path, "1.log") self.create_file(file1_path) file2_path = self.os.path.join(dir_path, "2.log") self.create_file(file2_path) with self.os.scandir(dir_path) as it: for entry in it: if entry.is_file(): self.os.remove(entry.path) assert not self.os.path.exists(file1_path) assert not self.os.path.exists(file2_path) def check_stat( self, absolute_symlink_expected_size, relative_symlink_expected_size ): self.assertEqual(self.FILE_SIZE, self.dir_entries[1].stat().st_size) if not self.is_windows_fs or sys.version_info < (3, 12): # behavior of st_ctime changed in 3.12, to be adapted later self.assertEqual( int(self.os.stat(self.dir_path).st_ctime), int(self.dir_entries[0].stat().st_ctime), ) if self.supports_symlinks: self.assertEqual(self.LINKED_FILE_SIZE, self.dir_entries[3].stat().st_size) self.assertEqual( absolute_symlink_expected_size, self.dir_entries[3].stat(follow_symlinks=False).st_size, ) self.assertEqual( int(self.os.stat(self.linked_dir_path).st_mtime), int(self.dir_entries[2].stat().st_mtime), ) self.assertEqual(self.LINKED_FILE_SIZE, self.dir_entries[5].stat().st_size) self.assertEqual( relative_symlink_expected_size, self.dir_entries[5].stat(follow_symlinks=False).st_size, ) self.assertEqual( int(self.os.stat(self.linked_dir_path).st_mtime), int(self.dir_entries[4].stat().st_mtime), ) @unittest.skipIf(TestCase.is_windows, "POSIX specific behavior") def test_stat_posix(self): self.check_stat(len(self.linked_file_path), len(self.rel_linked_file_path)) @unittest.skipIf(not TestCase.is_windows, "Windows specific behavior") def test_stat_windows(self): self.check_stat(0, 0) def test_index_access_to_stat_times_returns_int(self): if not self.is_windows_fs or sys.version_info < (3, 12): # behavior of st_ctime changed in 3.12, to be adapted later self.assertEqual( self.os.stat(self.dir_path)[stat.ST_CTIME], int(self.dir_entries[0].stat().st_ctime), ) if self.supports_symlinks: self.assertEqual( self.os.stat(self.linked_dir_path)[stat.ST_MTIME], int(self.dir_entries[2].stat().st_mtime), ) self.assertEqual( self.os.stat(self.linked_dir_path)[stat.ST_MTIME], int(self.dir_entries[4].stat().st_mtime), ) def test_stat_ino_dev(self): if self.supports_symlinks: file_stat = self.os.stat(self.linked_file_path) self.assertEqual(file_stat.st_ino, self.dir_entries[3].stat().st_ino) self.assertEqual(file_stat.st_dev, self.dir_entries[3].stat().st_dev) self.assertEqual(file_stat.st_ino, self.dir_entries[5].stat().st_ino) self.assertEqual(file_stat.st_dev, self.dir_entries[5].stat().st_dev) def test_path_like(self): self.assertTrue(isinstance(self.dir_entries[0], os.PathLike)) self.assertEqual( self.os.path.join(self.scandir_path(), "dir"), os.fspath(self.dir_entries[0]), ) self.assertEqual( self.os.path.join(self.scandir_path(), "file"), os.fspath(self.dir_entries[1]), ) def test_non_existing_dir(self): # behaves differently in different systems, so we skip the real fs test self.skip_real_fs() self.assert_raises_os_error(errno.ENOENT, self.scandir, "non_existing/fake_dir") class RealScandirTest(FakeScandirTest): def use_real_fs(self): return True class FakeScandirRelTest(FakeScandirTest): def scandir_path(self): # When scandir is called with a relative path, that relative path is # used in the path attribute of the DirEntry objects. return self.os.path.relpath(self.directory) def do_scandir(self): return self.scandir(self.os.path.relpath(self.directory)) class RealScandirRelTest(FakeScandirRelTest): def use_real_fs(self): return True @unittest.skipIf(TestCase.is_windows, "dir_fd not supported for os.scandir in Windows") class FakeScandirFdTest(FakeScandirTest): def tearDown(self): self.os.close(self.dir_fd) super().tearDown() def scandir_path(self): # When scandir is called with a filedescriptor, only the name of the # entry is returned in the path attribute of the DirEntry objects. return "" def do_scandir(self): self.dir_fd = self.os.open(self.directory, os.O_RDONLY) return self.scandir(self.dir_fd) class RealScandirFdTest(FakeScandirFdTest): def use_real_fs(self): return True class FakeScandirFdRelTest(FakeScandirFdTest): def do_scandir(self): self.dir_fd = self.os.open(self.os.path.relpath(self.directory), os.O_RDONLY) return self.scandir(self.dir_fd) class RealScandirFdRelTest(FakeScandirFdRelTest): def use_real_fs(self): return True class FakeExtendedAttributeTest(FakeOsModuleTestBase): def setUp(self): super().setUp() self.check_linux_only() self.dir_path = self.make_path("foo") self.file_path = self.os.path.join(self.dir_path, "bar") self.create_file(self.file_path) def test_empty_xattr(self): self.assertEqual([], self.os.listxattr(self.dir_path)) self.assertEqual([], self.os.listxattr(self.file_path)) def test_getxattr_raises_for_non_existing_file(self): with self.assertRaises(FileNotFoundError): self.os.getxattr("bogus_path", "test") def test_getxattr_raises_for_non_existing_attribute(self): with self.assertRaises(OSError) as cm: self.os.getxattr(self.file_path, "bogus") self.assertEqual(errno.ENODATA, cm.exception.errno) def test_setxattr(self): with self.assertRaises(TypeError): self.os.setxattr(self.file_path, "test", "value") with self.assertRaises(FileExistsError): self.os.setxattr(self.file_path, "test", b"value", self.os.XATTR_REPLACE) self.os.setxattr(self.file_path, "test", b"value") self.assertEqual(b"value", self.os.getxattr(self.file_path, "test")) with self.assertRaises(OSError) as cm: self.os.setxattr(self.file_path, "test", b"value", self.os.XATTR_CREATE) self.assertEqual(errno.ENODATA, cm.exception.errno) def test_removeattr(self): self.os.removexattr(self.file_path, "test") self.assertEqual([], self.os.listxattr(self.file_path)) self.os.setxattr(self.file_path, b"test", b"value") self.assertEqual(["test"], self.os.listxattr(self.file_path)) self.assertEqual(b"value", self.os.getxattr(self.file_path, "test")) self.os.removexattr(self.file_path, "test") self.assertEqual([], self.os.listxattr(self.file_path)) with self.assertRaises(OSError) as cm: self.os.getxattr(self.file_path, "test") self.assertEqual(errno.ENODATA, cm.exception.errno) def test_default_path(self): self.os.chdir(self.dir_path) self.os.setxattr(self.dir_path, b"test", b"value") self.assertEqual(["test"], self.os.listxattr()) self.assertEqual(b"value", self.os.getxattr(self.dir_path, "test")) class FakeOsUnreadableDirTest(FakeOsModuleTestBase): def setUp(self): if self.use_real_fs(): # unreadable dirs in Windows are only simulated # and cannot be created in the real OS using file system # functions only self.check_posix_only() super().setUp() self.dir_path = self.make_path("some_dir") self.file_path = self.os.path.join(self.dir_path, "some_file") self.create_file(self.file_path) self.chmod(self.dir_path, 0o000) def chmod(self, path, mode): if self.is_windows_fs: self.filesystem.chmod(path, mode, force_unix_mode=True) else: self.os.chmod(path, mode) def test_getuid(self): self.skip_real_fs() # won't change user in real fs self.check_posix_only() uid = self.os.getuid() set_uid(uid + 10) self.assertEqual(uid + 10, self.os.getuid()) self.assertEqual(uid + 10, get_uid()) reset_ids() self.assertEqual(uid, self.os.getuid()) def test_getgid(self): self.skip_real_fs() # won't change group in real fs self.check_posix_only() gid = self.os.getgid() set_gid(gid + 10) self.assertEqual(gid + 10, self.os.getgid()) self.assertEqual(gid + 10, get_gid()) reset_ids() self.assertEqual(gid, self.os.getgid()) def test_listdir_unreadable_dir(self): if not is_root(): self.assert_raises_os_error(errno.EACCES, self.os.listdir, self.dir_path) else: self.assertEqual(["some_file"], self.os.listdir(self.dir_path)) def test_listdir_user_readable_dir(self): self.chmod(self.dir_path, 0o600) self.assertEqual(["some_file"], self.os.listdir(self.dir_path)) self.chmod(self.dir_path, 0o000) def test_listdir_user_readable_dir_from_other_user(self): self.skip_real_fs() # won't change user in real fs self.check_posix_only() user_id = get_uid() set_uid(user_id + 1) dir_path = "/dir1" self.create_dir(dir_path, perm=0o600) self.assertTrue(self.os.path.exists(dir_path)) reset_ids() if not is_root(): with self.assertRaises(PermissionError): self.os.listdir(dir_path) else: self.assertEqual([], self.os.listdir(dir_path)) def test_listdir_group_readable_dir_from_other_user(self): self.skip_real_fs() # won't change user in real fs self.check_posix_only() set_uid(get_uid() + 1) dir_path = "/dir1" self.create_dir(dir_path, perm=0o660) self.assertTrue(self.os.path.exists(dir_path)) reset_ids() self.assertEqual([], self.os.listdir(dir_path)) def test_listdir_group_readable_dir_from_other_group(self): self.skip_real_fs() # won't change user in real fs self.check_posix_only() group_id = self.os.getgid() set_gid(group_id + 1) dir_path = "/dir1" self.create_dir(dir_path, perm=0o060) self.assertTrue(self.os.path.exists(dir_path)) set_gid(group_id) if not is_root(): with self.assertRaises(PermissionError): self.os.listdir(dir_path) else: self.assertEqual([], self.os.listdir(dir_path)) def test_listdir_other_readable_dir_from_other_group(self): self.check_posix_only() self.skip_real_fs() # won't change user in real fs dir_path = self.make_path("dir1") self.create_dir(dir_path, 0o004) set_uid(get_uid() + 1) set_gid(get_gid() + 1) self.assertTrue(self.os.path.exists(dir_path)) self.assertEqual([], self.os.listdir(dir_path)) reset_ids() def test_stat_unreadable_dir(self): self.assertEqual(0, self.os.stat(self.dir_path).st_mode & 0o666) def test_chmod_unreadable_dir(self): self.chmod(self.dir_path, 0o666) self.assertEqual(0o666, self.os.stat(self.dir_path).st_mode & 0o666) self.chmod(self.dir_path, 0o000) self.assertEqual(0, self.os.stat(self.dir_path).st_mode & 0o666) def test_stat_file_in_unreadable_dir(self): if not is_root(): self.assert_raises_os_error(errno.EACCES, self.os.stat, self.file_path) else: self.assertEqual(0, self.os.stat(self.file_path).st_size) def test_remove_unreadable_dir(self): self.check_posix_only() dir_path = self.make_path("dir1") self.create_dir(dir_path, perm=0o000) self.assertTrue(self.os.path.exists(dir_path)) self.os.rmdir(dir_path) self.assertFalse(self.os.path.exists(dir_path)) def test_remove_unreadable_dir_from_other_user(self): self.check_posix_only() self.skip_real_fs() # won't change user in real fs set_uid(get_uid() + 1) dir_path = "/dir1" self.create_dir(dir_path, perm=0o000) self.assertTrue(self.os.path.exists(dir_path)) reset_ids() if not is_root(): with self.assertRaises(PermissionError): self.os.rmdir(dir_path) self.assertTrue(self.os.path.exists(dir_path)) else: self.os.rmdir(dir_path) self.assertFalse(self.os.path.exists(dir_path)) class RealOsUnreadableDirTest(FakeOsUnreadableDirTest): def use_real_fs(self): return True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fake_pathlib_test.py0000644000175100001660000020173414764107375021760 0ustar00runnerdocker# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Unittests for fake_pathlib. As most of fake_pathlib is a wrapper around fake_filesystem methods, the tests are there mostly to ensure basic functionality. Note that many of the tests are directly taken from examples in the python docs. """ import contextlib import errno import os import pathlib import stat import sys import unittest from collections import namedtuple from unittest import mock from unittest.mock import patch from pyfakefs import fake_pathlib, fake_filesystem, fake_filesystem_unittest, fake_os from pyfakefs.fake_filesystem import OSType from pyfakefs.helpers import IS_PYPY, is_root from pyfakefs.tests.skipped_pathlib import ( check_exists_pathlib, read_bytes_pathlib, read_pathlib, read_text_pathlib, ) from pyfakefs.tests.test_utils import RealFsTestMixin, skip_if_symlink_not_supported is_windows = sys.platform == "win32" class RealPathlibTestCase(fake_filesystem_unittest.TestCase, RealFsTestMixin): is_windows = sys.platform == "win32" def __init__(self, methodName="runTest"): fake_filesystem_unittest.TestCase.__init__(self, methodName) RealFsTestMixin.__init__(self) def used_pathlib(self): return pathlib def setUp(self): RealFsTestMixin.setUp(self) self.filesystem = None self.real_os = os if not self.use_real_fs(): self.setUpPyfakefs() self.filesystem = self.fs self.create_basepath() self.pathlib = self.used_pathlib() self.path = self.pathlib.Path self.os = os self.open = open def use_real_fs(self): return False class FakePathlibInitializationTest(RealPathlibTestCase): def test_initialization_type(self): """Make sure tests for class type will work""" path = self.path("/test") if is_windows: self.assertTrue(isinstance(path, self.pathlib.WindowsPath)) self.assertTrue(isinstance(path, self.pathlib.PureWindowsPath)) self.assertTrue(self.pathlib.PurePosixPath()) # in fake fs, we allow to use the other OS implementation if self.use_real_fs(): with self.assertRaises(NotImplementedError): self.pathlib.PosixPath() else: self.assertTrue(self.pathlib.PosixPath()) else: self.assertTrue(isinstance(path, self.pathlib.PosixPath)) self.assertTrue(isinstance(path, self.pathlib.PurePosixPath)) self.assertTrue(self.pathlib.PureWindowsPath()) if self.use_real_fs(): with self.assertRaises(NotImplementedError): self.pathlib.WindowsPath() else: self.assertTrue(self.pathlib.WindowsPath()) def test_init_with_segments(self): """Basic initialization tests - taken from pathlib.Path documentation. """ self.assertEqual(self.path("/", "foo", "bar", "baz"), self.path("/foo/bar/baz")) self.assertEqual(self.path(), self.path(".")) self.assertEqual( self.path(self.path("foo"), self.path("bar")), self.path("foo/bar") ) self.assertEqual( self.path("/etc") / "init.d" / "reboot", self.path("/etc/init.d/reboot"), ) def test_init_collapse(self): """Tests for collapsing path during initialization. Taken from pathlib.PurePath documentation. """ self.assertEqual(self.path("foo//bar"), self.path("foo/bar")) self.assertEqual(self.path("foo/./bar"), self.path("foo/bar")) self.assertNotEqual(self.path("foo/../bar"), self.path("foo/bar")) self.assertEqual(self.path("/etc", "/usr", "lib64"), self.path("/usr/lib64")) def test_path_parts(self): sep = self.os.path.sep path = self.path(sep + self.os.path.join("foo", "bar", "setup.py")) self.assertEqual(path.parts, (sep, "foo", "bar", "setup.py")) self.assertEqual(path.drive, "") self.assertEqual(path.root, sep) self.assertEqual(path.anchor, sep) self.assertEqual(path.name, "setup.py") self.assertEqual(path.stem, "setup") self.assertEqual(path.suffix, ".py") self.assertEqual(path.parent, self.path(sep + self.os.path.join("foo", "bar"))) self.assertEqual( path.parents[0], self.path(sep + self.os.path.join("foo", "bar")) ) self.assertEqual(path.parents[1], self.path(sep + "foo")) self.assertEqual(path.parents[2], self.path(sep)) @unittest.skipIf(is_windows, "POSIX specific behavior") def test_is_absolute_posix(self): self.assertTrue(self.path("/a/b").is_absolute()) self.assertFalse(self.path("a/b").is_absolute()) self.assertFalse(self.path("d:/b").is_absolute()) @unittest.skipIf(not is_windows, "Windows specific behavior") def test_is_absolute_windows(self): self.assertFalse(self.path("/a/b").is_absolute()) self.assertFalse(self.path("a/b").is_absolute()) self.assertTrue(self.path("d:/b").is_absolute()) class RealPathlibInitializationTest(FakePathlibInitializationTest): def use_real_fs(self): return True @unittest.skipIf(not is_windows, "Windows specific behavior") class FakePathlibInitializationWithDriveTest(RealPathlibTestCase): def test_init_with_segments(self): """Basic initialization tests - taken from pathlib.Path documentation""" self.assertEqual( self.path("c:/", "foo", "bar", "baz"), self.path("c:/foo/bar/baz") ) self.assertEqual(self.path(), self.path(".")) self.assertEqual( self.path(self.path("foo"), self.path("bar")), self.path("foo/bar") ) self.assertEqual( self.path("c:/Users") / "john" / "data", self.path("c:/Users/john/data"), ) def test_init_collapse(self): """Tests for collapsing path during initialization. Taken from pathlib.PurePath documentation. """ self.assertEqual(self.path("c:/Windows", "d:bar"), self.path("d:bar")) self.assertEqual( self.path("c:/Windows", "/Program Files"), self.path("c:/Program Files"), ) def test_path_parts(self): path = self.path(self.os.path.join("d:", "python scripts", "setup.py")) self.assertEqual(path.parts, ("d:", "python scripts", "setup.py")) self.assertEqual(path.drive, "d:") self.assertEqual(path.root, "") self.assertEqual(path.anchor, "d:") self.assertEqual(path.name, "setup.py") self.assertEqual(path.stem, "setup") self.assertEqual(path.suffix, ".py") self.assertEqual( path.parent, self.path(self.os.path.join("d:", "python scripts")) ) self.assertEqual( path.parents[0], self.path(self.os.path.join("d:", "python scripts")), ) self.assertEqual(path.parents[1], self.path("d:")) @unittest.skipIf(not is_windows, "Windows-specific behavior") def test_is_absolute(self): self.assertTrue(self.path("c:/a/b").is_absolute()) self.assertFalse(self.path("/a/b").is_absolute()) self.assertFalse(self.path("c:").is_absolute()) self.assertTrue(self.path("//some/share").is_absolute()) class RealPathlibInitializationWithDriveTest(FakePathlibInitializationWithDriveTest): def use_real_fs(self): return True class FakePathlibPurePathTest(RealPathlibTestCase): """Tests functionality present in PurePath class.""" def test_is_reserved_posix(self): self.check_posix_only() with ( contextlib.nullcontext() if sys.version_info < (3, 13) else self.assertWarns(DeprecationWarning) ): self.assertFalse(self.path("/dev").is_reserved()) self.assertFalse(self.path("/").is_reserved()) self.assertFalse(self.path("COM1").is_reserved()) self.assertFalse(self.path("nul.txt").is_reserved()) @unittest.skipIf(not is_windows, "Windows specific behavior") def test_is_reserved_windows(self): self.check_windows_only() with ( contextlib.nullcontext() if sys.version_info < (3, 13) else self.assertWarns(DeprecationWarning) ): self.assertFalse(self.path("/dev").is_reserved()) self.assertFalse(self.path("/").is_reserved()) self.assertTrue(self.path("COM1").is_reserved()) self.assertTrue(self.path("nul.txt").is_reserved()) def test_joinpath(self): self.assertEqual(self.path("/etc").joinpath("passwd"), self.path("/etc/passwd")) self.assertEqual( self.path("/etc").joinpath(self.path("passwd")), self.path("/etc/passwd"), ) self.assertEqual( self.path("/foo").joinpath("bar", "baz"), self.path("/foo/bar/baz") ) def test_joinpath_drive(self): self.check_windows_only() self.assertEqual( self.path("c:").joinpath("/Program Files"), self.path("c:/Program Files"), ) def test_match(self): self.assertTrue(self.path("a/b.py").match("*.py")) self.assertTrue(self.path("/a/b/c.py").match("b/*.py")) self.assertFalse(self.path("/a/b/c.py").match("a/*.py")) self.assertTrue(self.path("/a.py").match("/*.py")) self.assertFalse(self.path("a/b.py").match("/*.py")) def test_relative_to(self): self.assertEqual( self.path("/etc/passwd").relative_to("/"), self.path("etc/passwd") ) self.assertEqual( self.path("/etc/passwd").relative_to("/"), self.path("etc/passwd") ) with self.assertRaises(ValueError): self.path("passwd").relative_to("/usr") @unittest.skipIf(sys.version_info < (3, 9), "is_relative_to new in Python 3.9") def test_is_relative_to(self): path = self.path("/etc/passwd") self.assertTrue(path.is_relative_to("/etc")) self.assertFalse(path.is_relative_to("/src")) def test_with_name(self): self.check_windows_only() self.assertEqual( self.path("c:/Downloads/pathlib.tar.gz").with_name("setup.py"), self.path("c:/Downloads/setup.py"), ) with self.assertRaises(ValueError): self.path("c:/").with_name("setup.py") def test_with_suffix(self): self.assertEqual( self.path("c:/Downloads/pathlib.tar.gz").with_suffix(".bz2"), self.path("c:/Downloads/pathlib.tar.bz2"), ) self.assertEqual( self.path("README").with_suffix(".txt"), self.path("README.txt") ) class RealPathlibPurePathTest(FakePathlibPurePathTest): def use_real_fs(self): return True class FakePathlibPurePosixPathTest(RealPathlibTestCase): def setUp(self): super().setUp() self.path = self.pathlib.PurePosixPath def test_is_reserved(self): with ( contextlib.nullcontext() if sys.version_info < (3, 13) else self.assertWarns(DeprecationWarning) ): self.assertFalse(self.path("/dev").is_reserved()) self.assertFalse(self.path("/").is_reserved()) self.assertFalse(self.path("COM1").is_reserved()) self.assertFalse(self.path("nul.txt").is_reserved()) def test_joinpath(self): self.assertEqual(self.path("/etc").joinpath("passwd"), self.path("/etc/passwd")) self.assertEqual( self.path("/etc").joinpath(self.path("passwd")), self.path("/etc/passwd"), ) self.assertEqual( self.path("/foo").joinpath("bar", "baz"), self.path("/foo/bar/baz") ) self.assertEqual( self.path("c:").joinpath("/Program Files"), self.path("/Program Files"), ) def test_match(self): self.assertTrue(self.path("a/b.py").match("*.py")) self.assertTrue(self.path("/a/b/c.py").match("b/*.py")) self.assertFalse(self.path("/a/b/c.py").match("a/*.py")) self.assertTrue(self.path("/a.py").match("/*.py")) self.assertFalse(self.path("a/b.py").match("/*.py")) def test_relative_to(self): self.assertEqual( self.path("/etc/passwd").relative_to("/"), self.path("etc/passwd") ) self.assertEqual( self.path("/etc/passwd").relative_to("/"), self.path("etc/passwd") ) with self.assertRaises(ValueError): self.path("passwd").relative_to("/usr") @unittest.skipIf(sys.version_info < (3, 9), "is_relative_to new in Python 3.9") def test_is_relative_to(self): path = self.path("/etc/passwd") self.assertTrue(path.is_relative_to("/etc")) self.assertFalse(path.is_relative_to("/src")) def test_with_name(self): self.assertEqual( self.path("c:/Downloads/pathlib.tar.gz").with_name("setup.py"), self.path("c:/Downloads/setup.py"), ) self.assertEqual(self.path("c:/").with_name("setup.py"), self.path("setup.py")) def test_with_suffix(self): self.assertEqual( self.path("c:/Downloads/pathlib.tar.gz").with_suffix(".bz2"), self.path("c:/Downloads/pathlib.tar.bz2"), ) self.assertEqual( self.path("README").with_suffix(".txt"), self.path("README.txt") ) def test_to_string(self): self.assertEqual(str(self.path("/usr/bin/ls")), "/usr/bin/ls") self.assertEqual(str(self.path("usr") / "bin" / "ls"), "usr/bin/ls") class RealPathlibPurePosixPathTest(FakePathlibPurePosixPathTest): def use_real_fs(self): return True class FakePathlibPureWindowsPathTest(RealPathlibTestCase): def setUp(self): super().setUp() self.path = self.pathlib.PureWindowsPath def test_is_reserved(self): with ( contextlib.nullcontext() if sys.version_info < (3, 13) else self.assertWarns(DeprecationWarning) ): self.assertFalse(self.path("/dev").is_reserved()) self.assertFalse(self.path("/").is_reserved()) self.assertTrue(self.path("COM1").is_reserved()) self.assertTrue(self.path("nul.txt").is_reserved()) def test_joinpath(self): self.assertEqual(self.path("/etc").joinpath("passwd"), self.path("/etc/passwd")) self.assertEqual( self.path("/etc").joinpath(self.path("passwd")), self.path("/etc/passwd"), ) self.assertEqual( self.path("/foo").joinpath("bar", "baz"), self.path("/foo/bar/baz") ) self.assertEqual( self.path("c:").joinpath("/Program Files"), self.path("c:/Program Files"), ) def test_match(self): self.assertTrue(self.path("a/b.py").match("*.py")) self.assertTrue(self.path("/a/b/c.py").match("b/*.py")) self.assertFalse(self.path("/a/b/c.py").match("a/*.py")) self.assertTrue(self.path("/a.py").match("/*.py")) self.assertFalse(self.path("a/b.py").match("/*.py")) def test_relative_to(self): self.assertEqual( self.path("/etc/passwd").relative_to("/"), self.path("etc/passwd") ) self.assertEqual( self.path("/etc/passwd").relative_to("/"), self.path("etc/passwd") ) with self.assertRaises(ValueError): self.path("passwd").relative_to("/usr") @unittest.skipIf(sys.version_info < (3, 9), "is_relative_to new in Python 3.9") def test_is_relative_to(self): path = self.path("/etc/passwd") self.assertTrue(path.is_relative_to("/etc")) self.assertFalse(path.is_relative_to("/src")) def test_with_name(self): self.assertEqual( self.path("c:/Downloads/pathlib.tar.gz").with_name("setup.py"), self.path("c:/Downloads/setup.py"), ) with self.assertRaises(ValueError): self.path("c:/").with_name("setup.py") def test_with_suffix(self): self.assertEqual( self.path("c:/Downloads/pathlib.tar.gz").with_suffix(".bz2"), self.path("c:/Downloads/pathlib.tar.bz2"), ) self.assertEqual( self.path("README").with_suffix(".txt"), self.path("README.txt") ) def test_to_string(self): self.assertEqual(str(self.path("/usr/bin/ls")), "\\usr\\bin\\ls") self.assertEqual( str(self.path("c:/Windows/System32/ntoskrnl.exe")), "c:\\Windows\\System32\\ntoskrnl.exe", ) self.assertEqual(str(self.path("usr") / "bin" / "ls"), "usr\\bin\\ls") self.assertEqual( str(self.path("C:/") / "Windows" / "System32" / "ntoskrnl.exe"), "C:\\Windows\\System32\\ntoskrnl.exe", ) class RealPathlibPureWindowsPathTest(FakePathlibPureWindowsPathTest): def use_real_fs(self): return True class FakePathlibFileObjectPropertyTest(RealPathlibTestCase): def setUp(self): super().setUp() self.umask = self.os.umask(0o022) self.file_path = self.make_path("home", "jane", "test.py") self.create_file(self.file_path, contents=b"a" * 100) self.create_dir(self.make_path("home", "john")) try: skip_if_symlink_not_supported() except unittest.SkipTest: return self.create_symlink(self.make_path("john"), self.make_path("home", "john")) self.file_link_path = self.make_path("test.py") self.create_symlink(self.file_link_path, self.file_path) self.create_symlink( self.make_path("broken_dir_link"), self.make_path("home", "none") ) self.create_symlink( self.make_path("broken_file_link"), self.make_path("home", "none", "test.py"), ) def tearDown(self): self.os.umask(self.umask) def test_exists(self): skip_if_symlink_not_supported() self.assertTrue(self.path(self.file_path).exists()) self.assertTrue(self.path(self.make_path("home", "jane")).exists()) self.assertFalse(self.path(self.make_path("home", "jane", "test")).exists()) self.assertTrue(self.path(self.make_path("john")).exists()) self.assertTrue(self.path(self.file_link_path).exists()) self.assertFalse(self.path(self.make_path("broken_dir_link")).exists()) self.assertFalse(self.path(self.make_path("broken_file_link")).exists()) def test_is_dir(self): skip_if_symlink_not_supported() self.assertFalse(self.path(self.file_path).is_dir()) self.assertTrue(self.path(self.make_path("home/jane")).is_dir()) self.assertTrue(self.path(self.make_path("john")).is_dir()) self.assertFalse(self.path(self.file_link_path).is_dir()) self.assertFalse(self.path(self.make_path("broken_dir_link")).is_dir()) self.assertFalse(self.path(self.make_path("broken_file_link")).is_dir()) def test_is_file(self): skip_if_symlink_not_supported() self.assertTrue(self.path(self.make_path("home/jane/test.py")).is_file()) self.assertFalse(self.path(self.make_path("home/jane")).is_file()) self.assertFalse(self.path(self.make_path("john")).is_file()) self.assertTrue(self.path(self.file_link_path).is_file()) self.assertFalse(self.path(self.make_path("broken_dir_link")).is_file()) self.assertFalse(self.path(self.make_path("broken_file_link")).is_file()) def test_is_symlink(self): skip_if_symlink_not_supported() self.assertFalse(self.path(self.make_path("home/jane/test.py")).is_symlink()) self.assertFalse(self.path(self.make_path("home/jane")).is_symlink()) self.assertTrue(self.path(self.make_path("john")).is_symlink()) self.assertTrue(self.path(self.file_link_path).is_symlink()) self.assertTrue(self.path(self.make_path("broken_dir_link")).is_symlink()) self.assertTrue(self.path(self.make_path("broken_file_link")).is_symlink()) def test_stat(self): skip_if_symlink_not_supported() file_stat = self.os.stat(self.file_path) stat_result = self.path(self.file_link_path).stat() self.assertFalse(stat_result.st_mode & stat.S_IFDIR) self.assertTrue(stat_result.st_mode & stat.S_IFREG) self.assertEqual(stat_result.st_ino, file_stat.st_ino) self.assertEqual(stat_result.st_size, 100) self.assertEqual(stat_result.st_mtime, file_stat.st_mtime) self.assertEqual(stat_result[stat.ST_MTIME], int(file_stat.st_mtime)) def check_lstat(self, expected_size): skip_if_symlink_not_supported() link_stat = self.os.lstat(self.file_link_path) stat_result = self.path(self.file_link_path).lstat() self.assertTrue(stat_result.st_mode & stat.S_IFREG) self.assertTrue(stat_result.st_mode & stat.S_IFLNK) self.assertEqual(stat_result.st_ino, link_stat.st_ino) self.assertEqual(stat_result.st_size, expected_size) self.assertEqual(stat_result.st_mtime, link_stat.st_mtime) @unittest.skipIf(is_windows, "POSIX specific behavior") def test_lstat_posix(self): self.check_lstat(len(self.file_path)) @unittest.skipIf(not is_windows, "Windows specific behavior") def test_lstat_windows(self): skip_if_symlink_not_supported() self.check_lstat(0) @unittest.skipIf(is_windows, "POSIX specific behavior") def test_chmod(self): self.check_posix_only() file_stat = self.os.stat(self.file_path) self.assertEqual(file_stat.st_mode, stat.S_IFREG | 0o644) link_stat = self.os.lstat(self.file_link_path) # we get stat.S_IFLNK | 0o755 under MacOs mode = 0o755 if self.is_macos else 0o777 self.assertEqual(link_stat.st_mode, stat.S_IFLNK | mode) def test_lchmod(self): skip_if_symlink_not_supported() file_stat = self.os.stat(self.file_path) link_stat = self.os.lstat(self.file_link_path) if not hasattr(self.real_os, "lchmod"): with self.assertRaises(NotImplementedError): self.path(self.file_link_path).lchmod(0o444) else: self.path(self.file_link_path).lchmod(0o444) mode = 0o666 if is_windows else 0o644 self.assertEqual(file_stat.st_mode, stat.S_IFREG | mode) # the exact mode depends on OS and Python version mode_mask = 0o600 if self.is_windows_fs else 0o700 self.assertEqual(link_stat.st_mode & 0o777700, stat.S_IFLNK | mode_mask) @unittest.skipIf( sys.version_info < (3, 10), "follow_symlinks argument new in Python 3.10", ) def test_chmod_no_followsymlinks(self): skip_if_symlink_not_supported() file_stat = self.os.stat(self.file_path) link_stat = self.os.lstat(self.file_link_path) if self.os.chmod not in self.os.supports_follow_symlinks or IS_PYPY: with self.assertRaises(NotImplementedError): self.path(self.file_link_path).chmod(0o444, follow_symlinks=False) else: self.path(self.file_link_path).chmod(0o444, follow_symlinks=False) mode = 0o666 if is_windows else 0o644 self.assertEqual(file_stat.st_mode, stat.S_IFREG | mode) # the exact mode depends on OS and Python version mode_mask = 0o600 if self.is_windows_fs else 0o700 self.assertEqual(link_stat.st_mode & 0o777700, stat.S_IFLNK | mode_mask) def test_resolve(self): self.create_dir(self.make_path("antoine", "docs")) self.create_file(self.make_path("antoine", "setup.py")) self.os.chdir(self.make_path("antoine")) # use real path to handle symlink /var to /private/var in macOS self.assert_equal_paths( self.path().resolve(), self.path(self.os.path.realpath(self.make_path("antoine"))), ) self.assert_equal_paths( self.path(self.os.path.join("docs", "..", "setup.py")).resolve(), self.path(self.os.path.realpath(self.make_path("antoine", "setup.py"))), ) def test_stat_file_in_unreadable_dir(self): self.check_posix_only() dir_path = self.make_path("some_dir") file_path = self.os.path.join(dir_path, "some_file") self.create_file(file_path) self.os.chmod(dir_path, 0o000) if not is_root(): self.assert_raises_os_error(errno.EACCES, self.path(file_path).stat) else: self.assertEqual(0, self.path(file_path).stat().st_size) def test_iterdir_in_unreadable_dir(self): self.check_posix_only() dir_path = self.make_path("some_dir") file_path = self.os.path.join(dir_path, "some_file") self.create_file(file_path) self.os.chmod(dir_path, 0o000) if not is_root(): if sys.version_info >= (3, 13): self.assert_raises_os_error(errno.EACCES, self.path(dir_path).iterdir) else: it = self.path(dir_path).iterdir() self.assert_raises_os_error(errno.EACCES, list, it) else: it = self.path(dir_path).iterdir() path = str(list(it)[0]) self.assertTrue(path.endswith("some_file")) def test_iterdir_and_glob_without_exe_permission(self): # regression test for #960 self.check_posix_only() self.skip_root() directory = self.path(self.make_path("testdir")) file_path = directory / "file.txt" self.create_file(file_path, contents="hey", perm=0o777) directory.chmod(0o655) # rw-r-xr-x # We cannot create any files in the directory, because that requires # searching it another_file = self.path(self.make_path("file.txt")) self.create_file(another_file, contents="hey") with self.assertRaises(PermissionError): self.os.link(another_file, directory / "link.txt") # We can enumerate the directory using iterdir and glob: assert len(list(directory.iterdir())) == 1 assert list(directory.iterdir())[0] == file_path assert len(list(directory.glob("*.txt"))) == 1 assert list(directory.glob("*.txt"))[0] == file_path # We cannot read files inside the directory, # even if we have read access to the file with self.assertRaises(PermissionError): file_path.stat() with self.assertRaises(PermissionError): file_path.read_text(encoding="utf8") def test_iterdir_impossible_without_read_permission(self): # regression test for #960 self.check_posix_only() self.skip_root() directory = self.path(self.make_path("testdir")) file_path = directory / "file.txt" self.create_file(file_path, contents="hey", perm=0o777) directory.chmod(0o355) # -wxr-xr-x # We cannot enumerate the directory using iterdir: with self.assertRaises(PermissionError): list(directory.iterdir()) # glob does not find the file assert len(list(directory.glob("*.txt"))) == 0 # we can access the file if we know the file name assert file_path.stat().st_mode & 0o777 == 0o755 assert file_path.read_text(encoding="utf8") == "hey" def test_resolve_nonexisting_file(self): path = self.path(self.make_path("/path", "to", "file", "this can not exist")) self.assertEqual(path, path.resolve()) def test_cwd(self): dir_path = self.make_path("jane") self.create_dir(dir_path) self.os.chdir(dir_path) self.assert_equal_paths( self.path.cwd(), self.path(self.os.path.realpath(dir_path)) ) @unittest.skipIf( sys.platform != "win32" or sys.version_info < (3, 8), "Windows specific test" ) @patch.dict(os.environ, {"USERPROFILE": r"C:\Users\John"}) def test_expanduser_windows(self): self.assertEqual( self.path("~").expanduser(), self.path("C:/Users/John"), ) @unittest.skipIf(sys.platform == "win32", "Posix specific test") @patch.dict(os.environ, {"HOME": "/home/john"}) def test_expanduser_posix(self): self.assertEqual(self.path("~").expanduser(), self.path("/home/john")) @unittest.skipIf( sys.platform != "win32" or sys.version_info < (3, 8), "Windows specific test" ) @patch.dict(os.environ, {"USERPROFILE": r"C:\Users\John"}) def test_home_windows(self): self.assertEqual( self.path(self.path("C:/Users/John")), self.path.home(), ) @unittest.skipIf(sys.platform == "win32", "Posix specific test") @patch.dict(os.environ, {"HOME": "/home/john"}) def test_home_posix(self): self.assertEqual(self.path("/home/john"), self.path.home()) class RealPathlibFileObjectPropertyTest(FakePathlibFileObjectPropertyTest): def use_real_fs(self): return True class FakePathlibPathFileOperationTest(RealPathlibTestCase): """Tests methods related to file and directory handling.""" def test_exists(self): skip_if_symlink_not_supported() self.create_file(self.make_path("home", "jane", "test.py")) self.create_dir(self.make_path("home", "john")) self.create_symlink(self.make_path("john"), self.make_path("home", "john")) self.create_symlink(self.make_path("none"), self.make_path("home", "none")) self.assertTrue(self.path(self.make_path("home", "jane", "test.py")).exists()) self.assertTrue(self.path(self.make_path("home", "jane")).exists()) self.assertTrue(self.path(self.make_path("john")).exists()) self.assertFalse(self.path(self.make_path("none")).exists()) self.assertFalse(self.path(self.make_path("home", "jane", "test")).exists()) def test_open(self): self.create_dir(self.make_path("foo")) with self.assertRaises(OSError): self.path(self.make_path("foo", "bar.txt")).open(encoding="utf8") self.path(self.make_path("foo", "bar.txt")).open("w", encoding="utf8").close() self.assertTrue(self.os.path.exists(self.make_path("foo", "bar.txt"))) def test_read_text(self): self.create_file(self.make_path("text_file"), contents="foo") file_path = self.path(self.make_path("text_file")) self.assertEqual(file_path.read_text(encoding="utf8"), "foo") @unittest.skipIf( sys.version_info < (3, 12), "is_junction method new in Python 3.12", ) def test_is_junction(self): self.create_file(self.make_path("text_file"), contents="foo") file_path = self.path(self.make_path("text_file")) self.assertFalse(file_path.is_junction()) def test_read_text_with_encoding(self): self.create_file( self.make_path("text_file"), contents="ерунда", encoding="cyrillic" ) file_path = self.path(self.make_path("text_file")) self.assertEqual(file_path.read_text(encoding="cyrillic"), "ерунда") def test_write_text(self): path_name = self.make_path("text_file") file_path = self.path(path_name) file_path.write_text("foo", encoding="utf8") self.assertTrue(self.os.path.exists(path_name)) self.check_contents(path_name, "foo") def test_write_text_with_encoding(self): path_name = self.make_path("text_file") file_path = self.path(path_name) file_path.write_text("ανοησίες", encoding="greek") self.assertTrue(self.os.path.exists(path_name)) self.check_contents(path_name, "ανοησίες".encode("greek")) @unittest.skipIf(sys.version_info < (3, 10), "newline argument new in Python 3.10") def test_write_with_newline_arg(self): path = self.path(self.make_path("some_file")) path.write_text("1\r\n2\n3\r4", newline="", encoding="utf8") self.check_contents(path, b"1\r\n2\n3\r4") path.write_text("1\r\n2\n3\r4", newline="\n", encoding="utf8") self.check_contents(path, b"1\r\n2\n3\r4") path.write_text("1\r\n2\n3\r4", newline="\r\n", encoding="utf8") self.check_contents(path, b"1\r\r\n2\r\n3\r4") path.write_text("1\r\n2\n3\r4", newline="\r", encoding="utf8") self.check_contents(path, b"1\r\r2\r3\r4") def test_read_bytes(self): path_name = self.make_path("binary_file") self.create_file(path_name, contents=b"Binary file contents") file_path = self.path(path_name) self.assertEqual(file_path.read_bytes(), b"Binary file contents") def test_write_bytes(self): path_name = self.make_path("binary_file") file_path = self.path(path_name) file_path.write_bytes(b"Binary file contents") self.assertTrue(self.os.path.exists(path_name)) self.check_contents(path_name, b"Binary file contents") def test_rename(self): file_name = self.make_path("foo", "bar.txt") self.create_file(file_name, contents="test") new_file_name = self.make_path("foo", "baz.txt") self.path(file_name).rename(new_file_name) self.assertFalse(self.os.path.exists(file_name)) self.check_contents(new_file_name, "test") def test_replace(self): self.create_file(self.make_path("foo", "bar.txt"), contents="test") self.create_file(self.make_path("bar", "old.txt"), contents="replaced") self.path(self.make_path("bar", "old.txt")).replace( self.make_path("foo", "bar.txt") ) self.assertFalse(self.os.path.exists(self.make_path("bar", "old.txt"))) self.check_contents(self.make_path("foo", "bar.txt"), "replaced") def test_unlink(self): file_path = self.make_path("foo", "bar.txt") self.create_file(file_path, contents="test") self.assertTrue(self.os.path.exists(file_path)) self.path(file_path).unlink() self.assertFalse(self.os.path.exists(file_path)) def test_touch_non_existing(self): self.create_dir(self.make_path("foo")) file_name = self.make_path("foo", "bar.txt") self.path(file_name).touch(mode=0o444) self.check_contents(file_name, "") self.assertTrue(self.os.stat(file_name).st_mode, stat.S_IFREG | 0o444) self.os.chmod(file_name, mode=0o666) def test_touch_existing(self): file_name = self.make_path("foo", "bar.txt") self.create_file(file_name, contents="test") file_path = self.path(file_name) self.assert_raises_os_error(errno.EEXIST, file_path.touch, exist_ok=False) file_path.touch() self.check_contents(file_name, "test") def test_samefile(self): file_name = self.make_path("foo", "bar.txt") self.create_file(file_name) file_name2 = self.make_path("foo", "baz.txt") self.create_file(file_name2) with self.assertRaises(OSError): self.path(self.make_path("foo", "other")).samefile( self.make_path("foo", "other.txt") ) path = self.path(file_name) other_name = self.make_path("foo", "other.txt") with self.assertRaises(OSError): path.samefile(other_name) with self.assertRaises(OSError): path.samefile(self.path(other_name)) self.assertFalse(path.samefile(file_name2)) self.assertFalse(path.samefile(self.path(file_name2))) self.assertTrue(path.samefile(self.make_path("foo", "..", "foo", "bar.txt"))) self.assertTrue( path.samefile(self.path(self.make_path("foo", "..", "foo", "bar.txt"))) ) def test_symlink_to(self): skip_if_symlink_not_supported() file_name = self.make_path("foo", "bar.txt") self.create_file(file_name) link_name = self.make_path("link_to_bar") path = self.path(link_name) path.symlink_to(file_name) self.assertTrue(self.os.path.exists(link_name)) self.assertTrue(path.is_symlink()) @unittest.skipIf(sys.version_info < (3, 8), "link_to new in Python 3.8") @unittest.skipIf(sys.version_info >= (3, 12), "link_to removed in Python 3.12") def test_link_to(self): skip_if_symlink_not_supported() file_name = self.make_path("foo", "bar.txt") self.create_file(file_name) self.assertEqual(1, self.os.stat(file_name).st_nlink) link_name = self.make_path("link_to_bar") path = self.path(file_name) path.link_to(link_name) self.assertTrue(self.os.path.exists(link_name)) self.assertFalse(path.is_symlink()) self.assertEqual(2, self.os.stat(file_name).st_nlink) @unittest.skipIf(sys.version_info < (3, 10), "hardlink_to new in Python 3.10") def test_hardlink_to(self): file_name = self.make_path("foo", "bar.txt") self.create_file(file_name) self.assertEqual(1, self.os.stat(file_name).st_nlink) link_path = self.path(self.make_path("link_to_bar")) path = self.path(file_name) link_path.hardlink_to(path) self.assertTrue(self.os.path.exists(link_path)) self.assertFalse(path.is_symlink()) self.assertEqual(2, self.os.stat(file_name).st_nlink) @unittest.skipIf(sys.version_info < (3, 9), "readlink new in Python 3.9") def test_readlink(self): skip_if_symlink_not_supported() link_path = self.make_path("foo", "bar", "baz") target = self.make_path("tarJAY") self.create_symlink(link_path, target) path = self.path(link_path) self.assert_equal_paths(path.readlink(), self.path(target)) def test_mkdir(self): dir_name = self.make_path("foo", "bar") self.assert_raises_os_error(errno.ENOENT, self.path(dir_name).mkdir) self.path(dir_name).mkdir(parents=True) self.assertTrue(self.os.path.exists(dir_name)) self.assert_raises_os_error(errno.EEXIST, self.path(dir_name).mkdir) def test_mkdir_exist_ok(self): dir_name = self.make_path("foo", "bar") self.create_dir(dir_name) self.path(dir_name).mkdir(exist_ok=True) file_name = self.os.path.join(dir_name, "baz") self.create_file(file_name) self.assert_raises_os_error( errno.EEXIST, self.path(file_name).mkdir, exist_ok=True ) @unittest.skipIf(not is_windows, "Windows specific behavior") def test_mkdir_with_automount_unc_path(self): self.skip_real_fs() self.path(r"\\test\unc\foo").mkdir(parents=True) self.assertTrue(self.path(r"\\test\unc\foo").exists()) @unittest.skipIf(not is_windows, "Windows specific behavior") def test_mkdir_with_automount_drive(self): self.skip_real_fs() self.path(r"d:\foo\bar").mkdir(parents=True) self.assertTrue(self.path(r"d:\foo\bar").exists()) def test_rmdir(self): dir_name = self.make_path("foo", "bar") self.create_dir(dir_name) self.path(dir_name).rmdir() self.assertFalse(self.os.path.exists(dir_name)) self.assertTrue(self.os.path.exists(self.make_path("foo"))) self.create_file(self.make_path("foo", "baz")) with self.assertRaises(OSError): self.path(self.make_path("foo")).rmdir() self.assertTrue(self.os.path.exists(self.make_path("foo"))) def test_iterdir(self): self.create_file(self.make_path("foo", "bar", "file1")) self.create_file(self.make_path("foo", "bar", "file2")) self.create_file(self.make_path("foo", "bar", "file3")) path = self.path(self.make_path("foo", "bar")) contents = [entry for entry in path.iterdir()] self.assertEqual(3, len(contents)) self.assertIn(self.path(self.make_path("foo", "bar", "file2")), contents) def test_glob(self): self.create_file(self.make_path("foo", "setup.py")) self.create_file(self.make_path("foo", "all_tests.py")) self.create_file(self.make_path("foo", "README.md")) self.create_file(self.make_path("foo", "setup.pyc")) path = self.path(self.make_path("foo")) self.assertEqual( [ self.path(self.make_path("foo", "all_tests.py")), self.path(self.make_path("foo", "setup.py")), ], sorted(path.glob("*.py")), ) def test_glob_case_windows(self): self.check_windows_only() self.create_file(self.make_path("foo", "setup.py")) self.create_file(self.make_path("foo", "all_tests.PY")) self.create_file(self.make_path("foo", "README.md")) self.create_file(self.make_path("foo", "example.Py")) path = self.path(self.make_path("foo")) self.assertEqual( [ self.path(self.make_path("foo", "all_tests.PY")), self.path(self.make_path("foo", "example.Py")), self.path(self.make_path("foo", "setup.py")), ], sorted(path.glob("*.py")), ) def test_glob_case_posix(self): self.check_posix_only() if sys.platform == "win32" and sys.version_info < (3, 12): self.skipTest(reason="Ignoring inconsistent path delimiters") self.create_file(self.make_path("foo", "setup.py")) self.create_file(self.make_path("foo", "all_tests.PY")) self.create_file(self.make_path("foo", "README.md")) self.create_file(self.make_path("foo", "example.Py")) path = self.path(self.make_path("foo")) self.assertEqual( sorted(path.glob("*.py")), [self.path(self.make_path("foo", "setup.py"))], ) class RealPathlibPathFileOperationTest(FakePathlibPathFileOperationTest): def use_real_fs(self): return True class FakePathlibUsageInOsFunctionsTest(RealPathlibTestCase): """Test that many `os` / `os.path` functions accept a path-like object. The functionality of these functions is tested elsewhere, we just check that they accept a fake path object as an argument. """ def test_join(self): dir1 = "foo" dir2 = "bar" dir3 = self.os.path.join(dir1, dir2) self.assertEqual(dir3, self.os.path.join(self.path(dir1), dir2)) self.assertEqual(dir3, self.os.path.join(dir1, self.path(dir2))) self.assertEqual(dir3, self.os.path.join(self.path(dir1), self.path(dir2))) def test_normcase(self): dir1 = self.make_path("Foo", "Bar", "Baz") self.assertEqual( self.os.path.normcase(dir1), self.os.path.normcase(self.path(dir1)) ) def test_normpath(self): dir1 = self.make_path("foo", "bar", "..", "baz") self.assertEqual( self.os.path.normpath(dir1), self.os.path.normpath(self.path(dir1)) ) def test_realpath(self): dir1 = self.make_path("foo", "bar", "..", "baz") self.assertEqual( self.os.path.realpath(dir1), self.os.path.realpath(self.path(dir1)) ) def test_relpath(self): path_foo = self.make_path("path", "to", "foo") path_bar = self.make_path("path", "to", "bar") rel_path = self.os.path.relpath(path_foo, path_bar) self.assertEqual(rel_path, self.os.path.relpath(self.path(path_foo), path_bar)) self.assertEqual(rel_path, self.os.path.relpath(path_foo, self.path(path_bar))) self.assertEqual( rel_path, self.os.path.relpath(self.path(path_foo), self.path(path_bar)), ) def test_split(self): dir1 = self.make_path("Foo", "Bar", "Baz") self.assertEqual(self.os.path.split(dir1), self.os.path.split(self.path(dir1))) def test_splitdrive(self): dir1 = self.make_path("C:", "Foo", "Bar", "Baz") self.assertEqual( self.os.path.splitdrive(dir1), self.os.path.splitdrive(self.path(dir1)), ) def test_abspath(self): dir1 = self.make_path("foo", "bar", "..", "baz") self.assertEqual( self.os.path.abspath(dir1), self.os.path.abspath(self.path(dir1)) ) def test_exists(self): dir1 = self.make_path("foo", "bar", "..", "baz") self.assertEqual( self.os.path.exists(dir1), self.os.path.exists(self.path(dir1)) ) def test_lexists(self): dir1 = self.make_path("foo", "bar", "..", "baz") self.assertEqual( self.os.path.lexists(dir1), self.os.path.lexists(self.path(dir1)) ) def test_expanduser(self): dir1 = self.os.path.join("~", "foo") self.assertEqual( self.os.path.expanduser(dir1), self.os.path.expanduser(self.path(dir1)), ) def test_getmtime(self): self.skip_real_fs() dir1 = self.make_path("foo", "bar1.txt") path_obj = self.filesystem.create_file(dir1) path_obj._st_mtime = 24 self.assertEqual( self.os.path.getmtime(dir1), self.os.path.getmtime(self.path(dir1)) ) def test_getctime(self): self.skip_real_fs() dir1 = self.make_path("foo", "bar1.txt") path_obj = self.filesystem.create_file(dir1) path_obj.st_ctime = 42 self.assertEqual( self.os.path.getctime(dir1), self.os.path.getctime(self.path(dir1)) ) def test_getatime(self): self.skip_real_fs() dir1 = self.make_path("foo", "bar1.txt") path_obj = self.filesystem.create_file(dir1) path_obj.st_atime = 11 self.assertEqual( self.os.path.getatime(dir1), self.os.path.getatime(self.path(dir1)) ) def test_getsize(self): path = self.make_path("foo", "bar", "baz") self.create_file(path, contents="1234567") self.assertEqual( self.os.path.getsize(path), self.os.path.getsize(self.path(path)) ) def test_isabs(self): path = self.make_path("foo", "bar", "..", "baz") self.assertEqual(self.os.path.isabs(path), self.os.path.isabs(self.path(path))) def test_isfile(self): path = self.make_path("foo", "bar", "baz") self.create_file(path) self.assertEqual( self.os.path.isfile(path), self.os.path.isfile(self.path(path)) ) def test_isfile_not_readable(self): path = self.make_path("foo", "bar", "baz") self.create_file(path, perm=0) self.assertEqual( self.os.path.isfile(path), self.os.path.isfile(self.path(path)) ) def test_islink(self): path = self.make_path("foo", "bar", "baz") self.create_file(path) self.assertEqual( self.os.path.islink(path), self.os.path.islink(self.path(path)) ) def test_isdir(self): path = self.make_path("foo", "bar", "baz") self.create_file(path) self.assertEqual(self.os.path.isdir(path), self.os.path.isdir(self.path(path))) def test_ismount(self): path = self.os.path.sep self.assertEqual( self.os.path.ismount(path), self.os.path.ismount(self.path(path)) ) def test_access(self): path = self.make_path("foo", "bar", "baz") self.create_file(path, contents="1234567") self.assertEqual( self.os.access(path, os.R_OK), self.os.access(self.path(path), os.R_OK), ) def test_chdir(self): path = self.make_path("foo", "bar", "baz") self.create_dir(path) self.os.chdir(self.path(path)) # use real path to handle symlink /var to /private/var in MacOs self.assert_equal_paths(self.os.path.realpath(path), self.os.getcwd()) def test_chmod(self): path = self.make_path("some_file") self.create_file(path) self.os.chmod(self.path(path), 0o444) self.assertEqual(stat.S_IMODE(0o444), stat.S_IMODE(self.os.stat(path).st_mode)) self.os.chmod(self.path(path), 0o666) def test_link(self): skip_if_symlink_not_supported() file1_path = self.make_path("test_file1") file2_path = self.make_path("test_file2") self.create_file(file1_path) self.os.link(self.path(file1_path), file2_path) self.assertTrue(self.os.path.exists(file2_path)) self.os.unlink(file2_path) self.os.link(self.path(file1_path), self.path(file2_path)) self.assertTrue(self.os.path.exists(file2_path)) self.os.unlink(file2_path) self.os.link(file1_path, self.path(file2_path)) self.assertTrue(self.os.path.exists(file2_path)) def test_listdir(self): path = self.make_path("foo", "bar") self.create_dir(path) self.create_file(path + "baz.txt") self.assertEqual(self.os.listdir(path), self.os.listdir(self.path(path))) def test_mkdir(self): path = self.make_path("foo") self.os.mkdir(self.path(path)) self.assertTrue(self.os.path.exists(path)) def test_makedirs(self): path = self.make_path("foo", "bar") self.os.makedirs(self.path(path)) self.assertTrue(self.os.path.exists(path)) @unittest.skipIf( is_windows and sys.version_info < (3, 8), "os.readlink does not to support path-like objects " "under Windows before Python 3.8", ) def test_readlink(self): skip_if_symlink_not_supported() link_path = self.make_path("foo", "bar", "baz") target = self.make_path("tarJAY") self.create_symlink(link_path, target) self.assert_equal_paths(self.os.readlink(self.path(link_path)), target) @unittest.skipIf( is_windows and sys.version_info < (3, 8), "os.readlink does not to support path-like objects " "under Windows before Python 3.8", ) def test_readlink_bytes(self): skip_if_symlink_not_supported() link_path = self.make_path(b"foo", b"bar", b"baz") target = self.make_path(b"tarJAY") self.create_symlink(link_path, target) self.assert_equal_paths(self.os.readlink(self.path(link_path)), target) def test_remove(self): path = self.make_path("test.txt") self.create_file(path) self.os.remove(self.path(path)) self.assertFalse(self.os.path.exists(path)) def test_rename(self): path1 = self.make_path("test1.txt") path2 = self.make_path("test2.txt") self.create_file(path1) self.os.rename(self.path(path1), path2) self.assertTrue(self.os.path.exists(path2)) self.os.rename(self.path(path2), self.path(path1)) self.assertTrue(self.os.path.exists(path1)) def test_replace(self): path1 = self.make_path("test1.txt") path2 = self.make_path("test2.txt") self.create_file(path1) self.os.replace(self.path(path1), path2) self.assertTrue(self.os.path.exists(path2)) self.os.replace(self.path(path2), self.path(path1)) self.assertTrue(self.os.path.exists(path1)) def test_rmdir(self): path = self.make_path("foo", "bar") self.create_dir(path) self.os.rmdir(self.path(path)) self.assertFalse(self.os.path.exists(path)) def test_scandir(self): directory = self.make_path("xyzzy", "plugh") self.create_dir(directory) self.create_file(self.os.path.join(directory, "test.txt")) dir_entries = [entry for entry in self.os.scandir(self.path(directory))] self.assertEqual(1, len(dir_entries)) def test_symlink(self): skip_if_symlink_not_supported() file_path = self.make_path("test_file1") link_path = self.make_path("link") self.create_file(file_path) self.os.symlink(self.path(file_path), link_path) self.assertTrue(self.os.path.exists(link_path)) self.os.remove(link_path) self.os.symlink(self.path(file_path), self.path(link_path)) self.assertTrue(self.os.path.exists(link_path)) def test_stat(self): path = self.make_path("foo", "bar", "baz") self.create_file(path, contents="1234567") self.assertEqual(self.os.stat(path), self.path(path).stat()) @unittest.skipIf(sys.version_info < (3, 10), "New in Python 3.10") def test_stat_follow_symlinks(self): self.check_posix_only() directory = self.make_path("foo") base_name = "bar" file_path = self.path(self.os.path.join(directory, base_name)) link_path = self.path(self.os.path.join(directory, "link")) contents = "contents" self.create_file(file_path, contents=contents) self.create_symlink(link_path, base_name) self.assertEqual( len(contents), link_path.stat(follow_symlinks=True)[stat.ST_SIZE] ) self.assertEqual( len(base_name), link_path.stat(follow_symlinks=False)[stat.ST_SIZE] ) def test_utime(self): path = self.make_path("some_file") self.create_file(path, contents="test") self.os.utime(self.path(path), times=(1, 2)) st = self.os.stat(path) self.assertEqual(1, st.st_atime) self.assertEqual(2, st.st_mtime) def test_truncate(self): path = self.make_path("some_file") self.create_file(path, contents="test_test") self.os.truncate(self.path(path), length=4) st = self.os.stat(path) self.assertEqual(4, st.st_size) @unittest.skipIf(sys.platform == "win32", "no pwd and grp modules in Windows") def test_owner_and_group_posix(self): path = self.make_path("some_file") self.create_file(path) self.assertTrue(self.path(path).owner()) self.assertTrue(self.path(path).group()) @unittest.skipIf(sys.platform == "win32", "no pwd and grp modules in Windows") def test_changed_owner_and_group(self): def fake_getpwuid(uid): if uid == 42: user_struct = namedtuple("user", "pw_name, pw_uid") user_struct.pw_name = "NewUser" return user_struct raise KeyError def fake_getgrgid(uid): if uid == 5: group_struct = namedtuple("group", "gr_name, gr_gid") group_struct.gr_name = "NewGroup" return group_struct raise KeyError self.skip_real_fs() path = self.make_path("some_file") self.create_file(path) self.os.chown(path, 42, 5) with mock.patch("pwd.getpwuid", fake_getpwuid): with mock.patch("grp.getgrgid", fake_getgrgid): self.assertEqual("NewUser", self.path(path).owner()) self.assertEqual("NewGroup", self.path(path).group()) def test_owner_and_group_windows(self): self.check_windows_only() path = self.make_path("some_file") self.create_file(path) with self.assertRaises(NotImplementedError): self.path(path).owner() with self.assertRaises(NotImplementedError): self.path(path).group() def test_walk(self): """Regression test for #915 - walk results shall be strings.""" base_dir = self.make_path("foo") base_path = self.path(base_dir) self.create_dir(base_path) self.create_file(base_path / "1.txt") self.create_file(base_path / "bar" / "2.txt") result = list(step for step in self.os.walk(base_path)) assert len(result) == 2 assert result[0] == (base_dir, ["bar"], ["1.txt"]) assert result[1] == (self.os.path.join(base_dir, "bar"), [], ["2.txt"]) class RealPathlibUsageInOsFunctionsTest(FakePathlibUsageInOsFunctionsTest): def use_real_fs(self): return True class FakeFilesystemPathLikeObjectTest(unittest.TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem() self.pathlib = fake_pathlib.FakePathlibModule(self.filesystem) self.os = fake_os.FakeOsModule(self.filesystem) def test_create_dir_with_pathlib_path(self): dir_path_string = "foo/bar/baz" dir_path = self.pathlib.Path(dir_path_string) self.filesystem.create_dir(dir_path) self.assertTrue(self.os.path.exists(dir_path_string)) self.assertEqual( stat.S_IFDIR, self.os.stat(dir_path_string).st_mode & stat.S_IFDIR ) def test_create_file_with_pathlib_path(self): file_path_string = "foo/bar/baz" file_path = self.pathlib.Path(file_path_string) self.filesystem.create_file(file_path) self.assertTrue(self.os.path.exists(file_path_string)) self.assertEqual( stat.S_IFREG, self.os.stat(file_path_string).st_mode & stat.S_IFREG ) def test_create_symlink_with_pathlib_path(self): file_path = self.pathlib.Path("foo/bar/baz") link_path_string = "foo/link" link_path = self.pathlib.Path(link_path_string) self.filesystem.create_symlink(link_path, file_path) self.assertTrue(self.os.path.lexists(link_path_string)) self.assertEqual( stat.S_IFLNK, self.os.lstat(link_path_string).st_mode & stat.S_IFLNK, ) def test_add_existing_real_file_with_pathlib_path(self): real_file_path_string = os.path.abspath(__file__) real_file_path = self.pathlib.Path(real_file_path_string) self.filesystem.add_real_file(real_file_path) fake_filepath_string = real_file_path_string.replace(os.sep, self.os.sep) self.assertTrue(self.os.path.exists(fake_filepath_string)) self.assertEqual( stat.S_IFREG, self.os.stat(fake_filepath_string).st_mode & stat.S_IFREG, ) def test_add_existing_real_directory_with_pathlib_path(self): real_dirpath_string = os.path.dirname(os.path.abspath(__file__)) real_dir_path = self.pathlib.Path(real_dirpath_string) self.filesystem.add_real_directory(real_dir_path) fake_dirpath_string = real_dirpath_string.replace(os.sep, self.os.sep) self.assertTrue(self.os.path.exists(fake_dirpath_string)) self.assertEqual( stat.S_IFDIR, self.os.stat(fake_dirpath_string).st_mode & stat.S_IFDIR, ) class FakeFilesystemChmodTest(fake_filesystem_unittest.TestCase): def setUp(self) -> None: self.setUpPyfakefs() @unittest.skipIf(sys.platform != "win32", "Windows specific test") def test_is_file_for_unreadable_dir_windows(self): self.fs.os = OSType.WINDOWS if is_root(): self.skipTest("Test only valid for non-root user") path = pathlib.Path("/foo/bar") self.fs.create_file(path) # normal chmod does not really set the mode to 0 self.fs.chmod("/foo", 0o000) self.assertTrue(path.is_file()) # but it does in forced UNIX mode self.fs.chmod("/foo", 0o000, force_unix_mode=True) with self.assertRaises(PermissionError): path.is_file() class FakePathlibModulePurePathTest(fake_filesystem_unittest.TestCase): def test_windows_pure_path_parsing_backslash(self): path = r"C:\Windows\cmd.exe" pure_result = pathlib.PureWindowsPath(path).stem self.assertEqual("cmd", pure_result) self.setUpPyfakefs() self.assertEqual( pure_result, fake_pathlib.FakePathlibModule.PureWindowsPath(path).stem ) self.assertEqual(pure_result, pathlib.PureWindowsPath(path).stem) def test_windows_pure_path_parsing_forward_slash(self): path = r"C:/Windows/cmd.exe" pure_result = pathlib.PureWindowsPath(path).stem self.assertEqual("cmd", pure_result) self.setUpPyfakefs() self.assertEqual( pure_result, fake_pathlib.FakePathlibModule.PureWindowsPath(path).stem ) self.assertEqual(pure_result, pathlib.PureWindowsPath(path).stem) def test_posix_pure_path_parsing(self): path = r"/bin/bash" pure_result = pathlib.PurePosixPath(path).stem self.assertEqual("bash", pure_result) self.setUpPyfakefs() self.assertEqual( pure_result, fake_pathlib.FakePathlibModule.PurePosixPath(path).stem ) self.assertEqual(pathlib.PurePosixPath(path).stem, pure_result) def test_windows_pure_path_str_backslash(self): path = r"C:\Windows\cmd.exe" pure_result = str(pathlib.PureWindowsPath(path)) self.assertEqual(r"C:\Windows\cmd.exe", pure_result) self.setUpPyfakefs() self.assertEqual( pure_result, str(fake_pathlib.FakePathlibModule.PureWindowsPath(path)) ) self.assertEqual(str(pathlib.PureWindowsPath(path)), pure_result) def test_windows_pure_path_str_forward_slash(self): path = "C:/Windows/cmd.exe" pure_result_win = str(pathlib.PureWindowsPath(path)) self.assertEqual(r"C:\Windows\cmd.exe", pure_result_win) pure_result_posix_stem = str(pathlib.PurePosixPath(path).stem) self.assertEqual("cmd", pure_result_posix_stem) self.setUpPyfakefs() self.assertEqual( pure_result_win, str(fake_pathlib.FakePathlibModule.PureWindowsPath(path)) ) self.assertEqual(pure_result_win, str(pathlib.PureWindowsPath(path))) self.assertEqual(pure_result_posix_stem, pathlib.PurePosixPath(path).stem) def test_posix_pure_path_str_backslash(self): path = r"\bin\bash" pure_result = str(pathlib.PurePosixPath(path)) self.assertEqual(r"\bin\bash", pure_result) self.setUpPyfakefs() self.assertEqual( pure_result, str(fake_pathlib.FakePathlibModule.PurePosixPath(path)) ) self.assertEqual(pure_result, str(pathlib.PurePosixPath(path))) def test_posix_pure_path_str_forward_slash(self): path = "/bin/bash" pure_result = str(pathlib.PurePosixPath(path)) self.assertEqual(r"/bin/bash", pure_result) self.setUpPyfakefs() self.assertEqual( pure_result, str(fake_pathlib.FakePathlibModule.PurePosixPath(path)) ) self.assertEqual(pure_result, str(pathlib.PurePosixPath(path))) def check_posix_pure_path_is_absolute(self, path, expected_result): pure_result = pathlib.PurePosixPath(path).is_absolute() self.assertEqual(expected_result, pure_result) self.setUpPyfakefs() self.assertEqual( pure_result, fake_pathlib.FakePathlibModule.PurePosixPath(path).is_absolute(), ) self.assertEqual(pure_result, pathlib.PurePosixPath(path).is_absolute()) def test_posix_pure_path_is_absolute_for_absolute_path(self): self.check_posix_pure_path_is_absolute("/bin/bash", expected_result=True) def test_posix_pure_path_is_absolute_for_local_path(self): self.check_posix_pure_path_is_absolute("bin/bash", expected_result=False) def test_posix_pure_path_is_absolute_for_relative_path(self): self.check_posix_pure_path_is_absolute("../bin/bash", expected_result=False) def check_windows_pure_path_is_absolute(self, path, expected_result): pure_result = pathlib.PureWindowsPath(path).is_absolute() self.assertEqual(expected_result, pure_result) self.setUpPyfakefs() self.assertEqual( pure_result, fake_pathlib.FakePathlibModule.PureWindowsPath(path).is_absolute(), ) self.assertEqual(pure_result, pathlib.PureWindowsPath(path).is_absolute()) def test_windows_pure_path_is_absolute_for_absolute_path(self): self.check_windows_pure_path_is_absolute("C:/Windows/cmd.exe", True) def test_windows_pure_path_is_absolute_for_local_path(self): self.check_windows_pure_path_is_absolute("./cmd.exe", expected_result=False) def test_windows_pure_path_is_absolute_for_relative_path(self): self.check_windows_pure_path_is_absolute("../cmd.exe", expected_result=False) class FakePathlibModulePurePathTestWindows(FakePathlibModulePurePathTest): def setUpPyfakefs(self, **kwargs): super().setUpPyfakefs(**kwargs) self.fs.os = OSType.WINDOWS class FakePathlibModulePurePathTestMacos(FakePathlibModulePurePathTest): def setUpPyfakefs(self, **kwargs): super().setUpPyfakefs(**kwargs) self.fs.os = OSType.MACOS class FakePathlibModulePurePathTestLinux(FakePathlibModulePurePathTest): def setUpPyfakefs(self, **kwargs): super().setUpPyfakefs(**kwargs) self.fs.os = OSType.LINUX class SkipPathlibTest(fake_filesystem_unittest.TestCase): def setUp(self): self.setUpPyfakefs(additional_skip_names=["skipped_pathlib"]) def test_open_in_skipped_module(self): # regression test for #1012 contents = read_pathlib("skipped_pathlib.py") self.assertTrue(contents.startswith("# Licensed under the Apache License")) def test_read_text_in_skipped_module(self): # regression test for #1012 contents = read_text_pathlib("skipped_pathlib.py") self.assertTrue(contents.startswith("# Licensed under the Apache License")) def test_read_bytes_in_skipped_module(self): # regression test for #1012 contents = read_bytes_pathlib("skipped_pathlib.py") self.assertTrue(contents.startswith(b"# Licensed under the Apache License")) @unittest.skipIf( IS_PYPY and sys.version_info < (3, 8), "Ignoring error in outdated version" ) def test_exists(self): self.assertTrue(check_exists_pathlib()) if __name__ == "__main__": unittest.main(verbosity=2) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fake_stat_time_test.py0000644000175100001660000005352114764107375022325 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unit tests for file timestamp updates.""" import time import unittest from collections import namedtuple from pyfakefs.tests.test_utils import RealFsTestCase FileTime = namedtuple("FileTime", "st_ctime, st_atime, st_mtime") class FakeStatTestBase(RealFsTestCase): def setUp(self): super().setUp() # we disable the tests for MacOS to avoid very long builds due # to the 1s time resolution - we know that the functionality is # similar to Linux self.check_linux_and_windows() self.file_path = self.make_path("some_file") # MacOS has a timestamp resolution of 1 second self.sleep_time = 1.1 if self.is_macos else 0.01 self.mode = "" def stat_time(self, path): stat = self.os.stat(path) if self.use_real_fs(): # sleep a bit so in the next call the time has changed time.sleep(self.sleep_time) else: # calling time.time() advances mocked time time.time() return FileTime( st_ctime=stat.st_ctime, st_atime=stat.st_atime, st_mtime=stat.st_mtime, ) def assertLessExceptWindows(self, time1, time2): if self.is_windows_fs: self.assertLessEqual(time1, time2) else: self.assertLess(time1, time2) def assertLessExceptPosix(self, time1, time2): if self.is_windows_fs: self.assertLess(time1, time2) else: self.assertEqual(time1, time2) def open_close_new_file(self): with self.mock_time(): with self.open(self.file_path, self.mode, encoding="utf8"): created = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return created, closed def open_write_close_new_file(self): with self.mock_time(): with self.open(self.file_path, self.mode, encoding="utf8") as f: created = self.stat_time(self.file_path) f.write("foo") written = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return created, written, closed def open_close(self): with self.mock_time(): self.create_file(self.file_path) before = self.stat_time(self.file_path) with self.open(self.file_path, self.mode, encoding="utf8"): opened = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return before, opened, closed def open_write_close(self): with self.mock_time(): self.create_file(self.file_path) before = self.stat_time(self.file_path) with self.open(self.file_path, self.mode, encoding="utf8") as f: opened = self.stat_time(self.file_path) f.write("foo") written = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return before, opened, written, closed def open_flush_close(self): with self.mock_time(): self.create_file(self.file_path) before = self.stat_time(self.file_path) with self.open(self.file_path, self.mode, encoding="utf8") as f: opened = self.stat_time(self.file_path) f.flush() flushed = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return before, opened, flushed, closed def open_write_flush(self): with self.mock_time(): self.create_file(self.file_path) before = self.stat_time(self.file_path) with self.open(self.file_path, self.mode, encoding="utf8") as f: opened = self.stat_time(self.file_path) f.write("foo") written = self.stat_time(self.file_path) f.flush() flushed = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return before, opened, written, flushed, closed def open_read_flush(self): with self.mock_time(): self.create_file(self.file_path) before = self.stat_time(self.file_path) with self.open(self.file_path, "r", encoding="utf8") as f: opened = self.stat_time(self.file_path) f.read() read = self.stat_time(self.file_path) f.flush() flushed = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return before, opened, read, flushed, closed def open_read_close_new_file(self): with self.mock_time(): with self.open(self.file_path, self.mode, encoding="utf8") as f: created = self.stat_time(self.file_path) f.read() read = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return created, read, closed def open_read_close(self): with self.mock_time(): self.create_file(self.file_path) before = self.stat_time(self.file_path) with self.open(self.file_path, self.mode, encoding="utf8") as f: opened = self.stat_time(self.file_path) f.read() read = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return before, opened, read, closed def check_open_close_new_file(self): """ When a file is created on opening and closed again, no timestamps are updated on close. """ created, closed = self.open_close_new_file() self.assertEqual(created.st_ctime, closed.st_ctime) self.assertEqual(created.st_atime, closed.st_atime) self.assertEqual(created.st_mtime, closed.st_mtime) def check_open_write_close_new_file(self): """ When a file is created on opening, st_ctime is updated under Posix, and st_mtime is updated on close. """ created, written, closed = self.open_write_close_new_file() self.assertEqual(created.st_ctime, written.st_ctime) self.assertLessExceptWindows(written.st_ctime, closed.st_ctime) self.assertEqual(created.st_atime, written.st_atime) self.assertLessEqual(written.st_atime, closed.st_atime) self.assertEqual(created.st_mtime, written.st_mtime) self.assertLess(written.st_mtime, closed.st_mtime) def check_open_close_w_mode(self): """ When an existing file is opened with 'w' or 'w+' mode, st_ctime (Posix only) and st_mtime are updated on open (truncating), but not on close. """ before, opened, closed = self.open_close() self.assertLessExceptWindows(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, closed.st_ctime) self.assertLessEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, closed.st_atime) self.assertLess(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, closed.st_mtime) def check_open_close_non_w_mode(self): """ When an existing file is opened with any mode other than 'w' or 'w+', no timestamps are updated. """ before, opened, closed = self.open_close() self.assertEqual(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, closed.st_ctime) self.assertEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, closed.st_atime) self.assertEqual(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, closed.st_mtime) def check_open_write_close_w_mode(self): """ When an existing file is opened with 'w' or 'w+' mode and is then written to, st_ctime (Posix only) and st_mtime are updated on open (truncating) and again on close (flush), but not when written to. """ before, opened, written, closed = self.open_write_close() self.assertLessExceptWindows(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, written.st_ctime) self.assertLessExceptWindows(written.st_ctime, closed.st_ctime) self.assertLessEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, written.st_atime) self.assertLessEqual(written.st_atime, closed.st_atime) self.assertLess(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, written.st_mtime) self.assertLess(written.st_mtime, closed.st_mtime) def check_open_flush_close_w_mode(self): """ When an existing file is opened with 'w' or 'w+' mode (truncating), st_ctime (Posix only) and st_mtime are updated. No updates are done on flush or close. """ before, opened, flushed, closed = self.open_flush_close() self.assertLessExceptWindows(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, flushed.st_ctime) self.assertEqual(flushed.st_ctime, closed.st_ctime) self.assertLessEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, flushed.st_atime) self.assertEqual(flushed.st_atime, closed.st_atime) self.assertLess(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, flushed.st_mtime) self.assertEqual(flushed.st_mtime, closed.st_mtime) def check_open_flush_close_non_w_mode(self): """ When an existing file is opened with any mode other than 'w' or 'w+', flushed and closed, no timestamps are updated. """ before, opened, flushed, closed = self.open_flush_close() self.assertEqual(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, flushed.st_ctime) self.assertEqual(flushed.st_ctime, closed.st_ctime) self.assertEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, flushed.st_atime) self.assertEqual(flushed.st_atime, closed.st_atime) self.assertEqual(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, flushed.st_mtime) self.assertEqual(flushed.st_mtime, closed.st_mtime) def check_open_read_close_non_w_mode(self): """ Reading from a file opened with 'r', 'r+', or 'a+' mode updates st_atime under Posix. """ before, opened, read, closed = self.open_read_close() self.assertEqual(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, read.st_ctime) self.assertEqual(read.st_ctime, closed.st_ctime) self.assertEqual(before.st_atime, opened.st_atime) self.assertLessEqual(opened.st_atime, read.st_atime) self.assertEqual(read.st_atime, closed.st_atime) self.assertEqual(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, read.st_mtime) self.assertEqual(read.st_mtime, closed.st_mtime) def check_open_read_close_new_file(self): """ When a file is created with 'w+' or 'a+' mode and then read from, st_atime is updated under Posix. """ created, read, closed = self.open_read_close_new_file() self.assertEqual(created.st_ctime, read.st_ctime) self.assertEqual(read.st_ctime, closed.st_ctime) self.assertLessEqual(created.st_atime, read.st_atime) self.assertEqual(read.st_atime, closed.st_atime) self.assertEqual(created.st_mtime, read.st_mtime) self.assertEqual(read.st_mtime, closed.st_mtime) def check_open_write_close_non_w_mode(self): """ When an existing file is opened with 'a', 'a+' or 'r+' mode and is then written to, st_ctime (Posix only) and st_mtime are updated close (flush), but not on opening or when written to. """ before, opened, written, closed = self.open_write_close() self.assertEqual(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, written.st_ctime) self.assertLessExceptWindows(written.st_ctime, closed.st_ctime) self.assertEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, written.st_atime) self.assertLessEqual(written.st_atime, closed.st_atime) self.assertEqual(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, written.st_mtime) self.assertLess(written.st_mtime, closed.st_mtime) def check_open_write_flush_close_w_mode(self): """ When an existing file is opened with 'w' or 'w+' mode and is then written to, st_ctime (Posix only) and st_mtime are updated on open (truncating). Under Posix, st_mtime is updated on flush, under Windows, on close instead. """ before, opened, written, flushed, closed = self.open_write_flush() self.assertLessEqual(before.st_ctime, opened.st_ctime) self.assertLessEqual(written.st_ctime, flushed.st_ctime) self.assertEqual(opened.st_ctime, written.st_ctime) self.assertEqual(flushed.st_ctime, closed.st_ctime) self.assertLessEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, written.st_atime) self.assertLessEqual(written.st_atime, flushed.st_atime) self.assertLessEqual(flushed.st_atime, closed.st_atime) self.assertLess(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, written.st_mtime) self.assertLessExceptWindows(written.st_mtime, flushed.st_mtime) self.assertLessEqual(flushed.st_mtime, closed.st_mtime) def check_open_write_flush_close_non_w_mode(self): """ When an existing file is opened with 'a', 'a+' or 'r+' mode and is then written to, st_ctime and st_mtime are updated on flush under Posix. Under Windows, only st_mtime is updated on close instead. """ before, opened, written, flushed, closed = self.open_write_flush() self.assertEqual(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, written.st_ctime) self.assertLessExceptWindows(written.st_ctime, flushed.st_ctime) self.assertEqual(flushed.st_ctime, closed.st_ctime) self.assertEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, written.st_atime) self.assertLessEqual(written.st_atime, flushed.st_atime) self.assertLessEqual(flushed.st_atime, closed.st_atime) self.assertEqual(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, written.st_mtime) self.assertLessExceptWindows(written.st_mtime, flushed.st_mtime) self.assertLessEqual(flushed.st_mtime, closed.st_mtime) class TestFakeModeW(FakeStatTestBase): def setUp(self): super().setUp() self.mode = "w" def test_open_close_new_file(self): self.check_open_close_new_file() def test_open_write_close_new_file(self): self.check_open_write_close_new_file() def test_open_close(self): self.check_open_close_w_mode() def test_open_write_close(self): self.check_open_write_close_w_mode() def test_open_flush_close(self): self.check_open_flush_close_w_mode() def test_open_write_flush_close(self): self.check_open_write_flush_close_w_mode() def test_read_raises(self): with self.open(self.file_path, "w", encoding="utf8") as f: with self.assertRaises(OSError): f.read() class TestRealModeW(TestFakeModeW): def use_real_fs(self): return True class TestFakeModeWPlus(FakeStatTestBase): def setUp(self): super().setUp() self.mode = "w+" def test_open_close_new_file(self): self.check_open_close_new_file() def test_open_write_close_new_file(self): self.check_open_write_close_new_file() def test_open_read_close_new_file(self): self.check_open_read_close_new_file() def test_open_close(self): self.check_open_close_w_mode() def test_open_write_close(self): self.check_open_write_close_w_mode() def test_open_read_close(self): """ When an existing file is opened with 'w+' mode and is then written to, st_ctime (Posix only) and st_mtime are updated on open (truncating) and again on close (flush). Under Posix, st_atime is updated on read. """ before, opened, read, closed = self.open_read_close() self.assertLessExceptWindows(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, read.st_ctime) self.assertEqual(read.st_ctime, closed.st_ctime) self.assertLessEqual(before.st_atime, opened.st_atime) self.assertLessEqual(opened.st_atime, read.st_atime) self.assertEqual(read.st_atime, closed.st_atime) self.assertLess(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, read.st_mtime) self.assertEqual(read.st_mtime, closed.st_mtime) def test_open_flush_close(self): self.check_open_flush_close_w_mode() def test_open_write_flush_close(self): self.check_open_write_flush_close_w_mode() class TestRealModeWPlus(TestFakeModeWPlus): def use_real_fs(self): return True class TestFakeModeA(FakeStatTestBase): def setUp(self): super().setUp() self.mode = "a" def test_open_close_new_file(self): self.check_open_close_new_file() def test_open_write_close_new_file(self): self.check_open_write_close_new_file() def test_open_close(self): self.check_open_close_non_w_mode() def test_open_write_close(self): self.check_open_write_close_non_w_mode() def test_open_flush_close(self): self.check_open_flush_close_non_w_mode() def test_open_write_flush_close(self): self.check_open_write_flush_close_non_w_mode() def test_read_raises(self): with self.open(self.file_path, "a", encoding="utf8") as f: with self.assertRaises(OSError): f.read() class TestRealModeA(TestFakeModeA): def use_real_fs(self): return True class TestFakeModeAPlus(FakeStatTestBase): def setUp(self): super().setUp() self.mode = "a+" def test_open_close_new_file(self): self.check_open_close_new_file() def test_open_write_close_new_file(self): self.check_open_write_close_new_file() def test_open_read_close_new_file(self): self.check_open_read_close_new_file() def test_open_close(self): self.check_open_close_non_w_mode() def test_open_write_close(self): self.check_open_write_close_non_w_mode() def test_open_read_close(self): self.check_open_read_close_non_w_mode() def test_open_flush_close(self): self.check_open_flush_close_non_w_mode() def test_open_write_flush_close(self): self.check_open_write_flush_close_non_w_mode() class TestRealModeAPlus(TestFakeModeAPlus): def use_real_fs(self): return True class TestFakeModeR(FakeStatTestBase): def setUp(self): super().setUp() self.mode = "r" def test_open_close(self): self.check_open_close_non_w_mode() def test_open_read_close(self): self.check_open_read_close_non_w_mode() def test_open_flush_close(self): self.check_open_flush_close_non_w_mode() def test_open_read_flush_close(self): """ When an existing file is opened with 'r' mode, read, flushed and closed, st_atime is updated after reading under Posix. """ before, opened, read, flushed, closed = self.open_read_flush() self.assertEqual(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, read.st_ctime) self.assertEqual(read.st_ctime, flushed.st_ctime) self.assertEqual(flushed.st_ctime, closed.st_ctime) self.assertEqual(before.st_atime, opened.st_atime) self.assertLessEqual(opened.st_atime, read.st_atime) self.assertEqual(read.st_atime, flushed.st_atime) self.assertEqual(flushed.st_atime, closed.st_atime) self.assertEqual(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, read.st_mtime) self.assertEqual(read.st_mtime, flushed.st_mtime) self.assertEqual(flushed.st_mtime, closed.st_mtime) def test_open_not_existing_raises(self): with self.assertRaises(OSError): with self.open(self.file_path, "r"): pass class TestRealModeR(TestFakeModeR): def use_real_fs(self): return True class TestFakeModeRPlus(FakeStatTestBase): def setUp(self): super().setUp() self.mode = "r+" def test_open_close(self): self.check_open_close_non_w_mode() def test_open_write_close(self): self.check_open_write_close_non_w_mode() def test_open_read_close(self): self.check_open_read_close_non_w_mode() def test_open_flush_close(self): self.check_open_flush_close_non_w_mode() def test_open_write_flush_close(self): self.check_open_write_flush_close_non_w_mode() def test_open_not_existing_raises(self): with self.assertRaises(OSError): with self.open(self.file_path, "r+"): pass class TestRealModeRPlus(TestFakeModeRPlus): def use_real_fs(self): return True if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fake_tempfile_test.py0000644000175100001660000001014714764107375022136 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Tests that ensure that the `tempfile` module works with `fake_filesystem` if using `Patcher` (via `fake_filesystem_unittest`). """ import os import stat import tempfile import unittest from pyfakefs import fake_filesystem_unittest class FakeTempfileModuleTest(fake_filesystem_unittest.TestCase): """Test the 'tempfile' module with the fake file system.""" def setUp(self): self.setUpPyfakefs() def test_named_temporary_file(self): obj = tempfile.NamedTemporaryFile() self.assertTrue(self.fs.get_object(obj.name)) obj.close() with self.assertRaises(OSError): self.fs.get_object(obj.name) def test_named_temporary_file_no_delete(self): obj = tempfile.NamedTemporaryFile(delete=False) obj.write(b"foo") obj.close() file_obj = self.fs.get_object(obj.name) contents = file_obj.contents self.assertEqual("foo", contents) obj = tempfile.NamedTemporaryFile(mode="w", encoding="utf8", delete=False) obj.write("foo") obj.close() file_obj = self.fs.get_object(obj.name) self.assertEqual("foo", file_obj.contents) def test_mkstemp(self): next_fd = len(self.fs.open_files) temporary = tempfile.mkstemp() self.assertEqual(2, len(temporary)) self.assertTrue( temporary[1].startswith(os.path.join(tempfile.gettempdir(), "tmp")) ) self.assertEqual(next_fd, temporary[0]) self.assertTrue(self.fs.exists(temporary[1])) mode = 0o666 if self.fs.is_windows_fs else 0o600 self.assertEqual(self.fs.get_object(temporary[1]).st_mode, stat.S_IFREG | mode) fh = os.fdopen(temporary[0], "w+b") self.assertEqual(temporary[0], fh.fileno()) def test_mkstemp_dir(self): """test tempfile.mkstemp(dir=).""" # expect fail: /dir does not exist with self.assertRaises(OSError): tempfile.mkstemp(dir="/dir") # expect pass: /dir exists self.fs.create_dir("/dir") next_fd = len(self.fs.open_files) temporary = tempfile.mkstemp(dir="/dir") self.assertEqual(2, len(temporary)) self.assertEqual(next_fd, temporary[0]) self.assertTrue( temporary[1].startswith(os.path.join(self.fs.root_dir_name, "dir", "tmp")) ) self.assertTrue(self.fs.exists(temporary[1])) mode = 0o666 if self.fs.is_windows_fs else 0o600 self.assertEqual(self.fs.get_object(temporary[1]).st_mode, stat.S_IFREG | mode) def test_mkdtemp(self): dirname = tempfile.mkdtemp() self.assertTrue(dirname) self.assertTrue(self.fs.exists(dirname)) self.assertEqual(self.fs.get_object(dirname).st_mode, stat.S_IFDIR | 0o700) def test_temporary_directory(self): with tempfile.TemporaryDirectory() as tmpdir: self.assertTrue(tmpdir) self.assertTrue(self.fs.exists(tmpdir)) self.assertEqual(self.fs.get_object(tmpdir).st_mode, stat.S_IFDIR | 0o700) def test_temporary_file(self): with tempfile.TemporaryFile() as f: f.write(b"test") f.seek(0) self.assertEqual(b"test", f.read()) def test_temporay_file_with_dir(self): with self.assertRaises(FileNotFoundError): tempfile.TemporaryFile(dir="/parent") os.mkdir("/parent") with tempfile.TemporaryFile() as f: f.write(b"test") f.seek(0) self.assertEqual(b"test", f.read()) if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741721344.967527 pyfakefs-5.8.0/pyfakefs/tests/fixtures/0000755000175100001660000000000014764107401017566 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fixtures/__init__.py0000644000175100001660000000000014764107375021677 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fixtures/config_module.py0000644000175100001660000000004514764107375022763 0ustar00runnerdockerconfigurable_value = "another value" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fixtures/deprecated_property.py0000644000175100001660000000203014764107375024211 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Used for testing suppression of deprecation warnings while iterating over modules. The code is modeled after code in xmlbuilder.py in Python 3.6. See issue #542. """ import warnings class DeprecatedProperty: def __get__(self, instance, cls): warnings.warn("async is deprecated", DeprecationWarning, stacklevel=2) warnings.warn("async will be replaced", FutureWarning, stacklevel=2) return instance class DeprecationTest: locals()["async"] = DeprecatedProperty() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/fixtures/module_with_attributes.py0000644000175100001660000000233314764107375024741 0ustar00runnerdocker# Copyright 2017 John McGehee # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """This module is for testing pyfakefs :py:class:`fake_filesystem_unittest.Patcher`. It defines attributes that have the same names as file modules, sudh as 'io` and `path`. Since these are not modules, :py:class:`fake_filesystem_unittest.Patcher` should not patch them. Whenever a new module is added to :py:meth:`fake_filesystem_unittest.Patcher._findModules`, the corresponding attribute should be added here and in the test :py:class:`fake_filesystem_unittest_test.TestAttributesWithFakeModuleNames`. """ os = "os attribute value" path = "path attribute value" pathlib = "pathlib attribute value" shutil = "shutil attribute value" io = "io attribute value" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/import_as_example.py0000644000175100001660000000624314764107375022016 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Example module that is used for testing modules that import file system modules to be patched under another name. """ import os as my_os import pathlib import sys from builtins import open as bltn_open from io import open as io_open from os import path from os import stat from os import stat as my_stat from os.path import exists, isfile, isdir, islink from os.path import exists as my_exists from pathlib import Path def check_if_exists1(filepath): # test patching module imported under other name return my_os.path.exists(filepath) def check_if_exists2(filepath): # tests patching path imported from os return path.exists(filepath) def check_if_exists3(filepath): # tests patching Path imported from pathlib return Path(filepath).exists() def check_if_exists4(filepath, file_exists=my_os.path.exists): return file_exists(filepath) def check_if_exists5(filepath): # tests patching `exists` imported from os.path return exists(filepath) def check_if_exists6(filepath): # tests patching `exists` imported from os.path as other name return my_exists(filepath) def check_if_exists7(filepath): # tests patching pathlib return pathlib.Path(filepath).exists() def check_if_isfile(filepath): # tests patching `isfile` imported from os.path return isfile(filepath) def check_if_isdir(filepath): # tests patching `isdir` imported from os.path return isdir(filepath) def check_if_islink(filepath): # tests patching `islink` imported from os.path return islink(filepath) def file_stat1(filepath): # tests patching `stat` imported from os return stat(filepath) def file_stat2(filepath): # tests patching `stat` imported from os as other name return my_stat(filepath) def system_stat(filepath): if sys.platform == "win32": from nt import stat as system_stat else: from posix import stat as system_stat return system_stat(filepath) def file_contents1(filepath): with bltn_open(filepath, encoding="utf8") as f: return f.read() def file_contents2(filepath): with io_open(filepath, encoding="utf8") as f: return f.read() def exists_this_file(): """Returns True in real fs only""" return exists(__file__) def open_this_file(): """Works only in real fs""" with open(__file__, encoding="utf8"): pass def return_this_file_path(): """Works only in real fs""" return Path(__file__) class TestDefaultArg: def check_if_exists(self, filepath, file_exists=my_os.path.exists): # this is a similar case as in the tempfile implementation under Posix return file_exists(filepath) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/logsio.py0000644000175100001660000000150014764107375017571 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Example module that is used for a regression test where a module with a name ending with "io" was skipped from patching (see #569). """ def file_contents(path): """Return the contents of the given path as byte array.""" with open(path, "rb") as f: return f.read() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/mox3_stubout_example.py0000644000175100001660000000157514764107375022477 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Example module that is used for testing the functionality of :py:class`pyfakefs.mox_stubout.StubOutForTesting`. """ import datetime import math import os def check_if_exists(filepath): return os.path.exists(filepath) def fabs(x): return math.fabs(x) def tomorrow(): return datetime.date.today() + datetime.timedelta(days=1) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/mox3_stubout_test.py0000644000175100001660000001233014764107375022012 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unit tests for mox3_stubout.""" import datetime import math import os import unittest from os import path from pyfakefs import mox3_stubout from pyfakefs.tests import mox3_stubout_example class NoPanicMath: real_math = math @staticmethod def fabs(_x): return 42 def __getattr__(self, name): """Forwards any unfaked calls to the standard module.""" return getattr(self.real_math, name) class ExistingPath: real_path = path @staticmethod def exists(_path): return True def __getattr__(self, name): """Forwards any unfaked calls to the standard module.""" return getattr(self.real_path, name) class GroundhogDate(datetime.date): @classmethod def today(cls): return datetime.date(1993, 2, 2) class StubOutForTestingTest(unittest.TestCase): def setUp(self): super().setUp() self.stubber = mox3_stubout.StubOutForTesting() def test_stubout_method_with_set(self): non_existing_path = "non_existing_path" self.assertFalse(mox3_stubout_example.check_if_exists(non_existing_path)) self.stubber.set(os.path, "exists", lambda x: True) self.assertTrue(mox3_stubout_example.check_if_exists(non_existing_path)) self.stubber.unset_all() self.assertFalse(mox3_stubout_example.check_if_exists(non_existing_path)) def test_stubout_class_with_set(self): self.assertGreater(mox3_stubout_example.tomorrow().year, 2000) self.stubber.set(datetime, "date", GroundhogDate) self.assertEqual(mox3_stubout_example.tomorrow(), datetime.date(1993, 2, 3)) self.stubber.unset_all() self.assertGreater(mox3_stubout_example.tomorrow().year, 2000) def test_stubout_module_with_set(self): self.assertEqual(10, mox3_stubout_example.fabs(-10)) self.stubber.set(mox3_stubout_example, "math", NoPanicMath) self.assertEqual(42, mox3_stubout_example.fabs(-10)) self.stubber.unset_all() self.assertEqual(10, mox3_stubout_example.fabs(-10)) def test_set_raise_if_unknown_attribute(self): self.assertRaises( AttributeError, self.stubber.set, os.path, "exists_not", lambda x: True, ) self.assertRaises( AttributeError, self.stubber.set, datetime, "tomorrow", GroundhogDate, ) self.assertRaises( AttributeError, self.stubber.set, mox3_stubout_example, "math1", NoPanicMath, ) def test_stubout_method_with_smart_set(self): non_existing_path = "non_existing_path" self.stubber.smart_set(os.path, "exists", lambda x: True) self.assertTrue(mox3_stubout_example.check_if_exists(non_existing_path)) self.stubber.smart_unset_all() self.assertFalse(mox3_stubout_example.check_if_exists(non_existing_path)) def test_stubout_class_with_smart_set(self): self.stubber.smart_set(datetime, "date", GroundhogDate) self.assertEqual(mox3_stubout_example.tomorrow(), datetime.date(1993, 2, 3)) self.stubber.smart_unset_all() self.assertGreater(mox3_stubout_example.tomorrow().year, 2000) def test_stubout_module_with_smart_set(self): self.assertEqual(10, mox3_stubout_example.fabs(-10)) self.stubber.smart_set(mox3_stubout_example, "math", NoPanicMath) self.assertEqual(42, mox3_stubout_example.fabs(-10)) self.stubber.smart_unset_all() self.assertEqual(10, mox3_stubout_example.fabs(-10)) def test_stubout_submodule_with_smart_set(self): # this one does not work with Set non_existing_path = "non_existing_path" self.assertFalse(mox3_stubout_example.check_if_exists(non_existing_path)) self.stubber.smart_set(os, "path", ExistingPath) self.assertTrue(mox3_stubout_example.check_if_exists(non_existing_path)) self.stubber.smart_unset_all() self.assertFalse(mox3_stubout_example.check_if_exists(non_existing_path)) def test_smart_set_raise_if_unknown_attribute(self): self.assertRaises( AttributeError, self.stubber.smart_set, os.path, "exists_not", lambda x: True, ) self.assertRaises( AttributeError, self.stubber.smart_set, datetime, "tomorrow", GroundhogDate, ) self.assertRaises( AttributeError, self.stubber.smart_set, mox3_stubout_example, "math1", NoPanicMath, ) if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/patched_packages_test.py0000644000175100001660000000501214764107375022604 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Provides patches for some commonly used modules that enable them to work with pyfakefs. """ import os import sys import unittest from pyfakefs import fake_filesystem_unittest from pyfakefs.helpers import IS_PYPY try: import pandas as pd except ImportError: pd = None try: import xlrd except ImportError: xlrd = None try: import openpyxl except ImportError: openpyxl = None @unittest.skipIf( IS_PYPY and sys.version_info < (3, 8), "Has a problem with older PyPy versions" ) class TestPatchedPackages(fake_filesystem_unittest.TestCase): def setUp(self): self.setUpPyfakefs() if pd is not None: def test_read_csv(self): path = "/foo/bar.csv" self.fs.create_file(path, contents="1,2,3,4") df = pd.read_csv(path) assert (df.columns == ["1", "2", "3", "4"]).all() def test_read_table(self): path = "/foo/bar.csv" self.fs.create_file(path, contents="1|2|3|4") df = pd.read_table(path, delimiter="|") assert (df.columns == ["1", "2", "3", "4"]).all() if pd is not None and xlrd is not None: def test_read_excel(self): path = "/foo/bar.xlsx" src_path = os.path.dirname(os.path.abspath(__file__)) src_path = os.path.join(src_path, "fixtures", "excel_test.xlsx") # map the file into another location to be sure that # the real fs is not used self.fs.add_real_file(src_path, target_path=path) df = pd.read_excel(path) assert (df.columns == [1, 2, 3, 4]).all() if pd is not None and openpyxl is not None: def test_write_excel(self): self.fs.create_dir("/foo") path = "/foo/bar.xlsx" df = pd.DataFrame([[0, 1, 2, 3]]) with pd.ExcelWriter(path) as writer: df.to_excel(writer) df = pd.read_excel(path) assert (df.columns == ["Unnamed: 0", 0, 1, 2, 3]).all() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/performance_test.py0000644000175100001660000000507614764107375021651 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Shall provide tests to check performance overhead of pyfakefs.""" import os import time import unittest from pyfakefs.fake_filesystem_unittest import TestCase from pyfakefs.helpers import IS_PYPY if os.environ.get("TEST_PERFORMANCE"): class SetupPerformanceTest(TestCase): @classmethod def setUpClass(cls) -> None: cls.start_time = time.time() @classmethod def tearDownClass(cls) -> None: cls.elapsed_time = time.time() - cls.start_time print( "Elapsed time per test for cached setup: {:.3f} ms".format( cls.elapsed_time * 10 ) ) def setUp(self) -> None: self.setUpPyfakefs() class SetupNoCachePerformanceTest(TestCase): @classmethod def setUpClass(cls) -> None: cls.start_time = time.time() @classmethod def tearDownClass(cls) -> None: cls.elapsed_time = time.time() - cls.start_time print( "Elapsed time per test for uncached setup: {:.3f} ms".format( cls.elapsed_time * 10 ) ) def setUp(self) -> None: self.setUpPyfakefs(use_cache=False) @unittest.skipIf(IS_PYPY, "PyPy times are not comparable") class TimePerformanceTest(TestCase): """Make sure performance degradation in setup is noticed. The numbers are related to the CI builds and may fail in local builds. """ def test_cached_time(self): self.assertLess(SetupPerformanceTest.elapsed_time, 0.18) def test_uncached_time(self): self.assertLess(SetupNoCachePerformanceTest.elapsed_time, 4) def test_setup(self): pass for n in range(100): test_name = "test_" + str(n) setattr(SetupPerformanceTest, test_name, test_setup) test_name = "test_nocache" + str(n) setattr(SetupNoCachePerformanceTest, test_name, test_setup) if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/skipped_pathlib.py0000644000175100001660000000221214764107375021440 0ustar00runnerdocker# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Provides functions for testing additional_skip_names functionality. """ import os from pathlib import Path def read_pathlib(file_name): return (Path(__file__).parent / file_name).open("r").read() def read_text_pathlib(file_name): return (Path(__file__).parent / file_name).read_text() def read_bytes_pathlib(file_name): return (Path(__file__).parent / file_name).read_bytes() def check_exists_pathlib(): return os.path.exists(__file__) and Path(__file__).exists() def read_open(file_name): with open(os.path.join(os.path.dirname(__file__), file_name)) as f: return f.read() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyfakefs/tests/test_utils.py0000644000175100001660000004007214764107375020503 0ustar00runnerdocker# Copyright 2009 Google Inc. All Rights Reserved. # Copyright 2015-2017 John McGehee # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Disable attribute errors - attributes not be found in mixin (shall be cleaned up...) # pytype: disable=attribute-error """Common helper classes used in tests, or as test class base.""" import os import platform import shutil import stat import sys import tempfile import unittest from contextlib import contextmanager from unittest import mock from pyfakefs import fake_filesystem, fake_open, fake_os from pyfakefs.helpers import is_byte_string, to_string, is_root class DummyTime: """Mock replacement for time.time. Increases returned time on access.""" def __init__(self, curr_time, increment): self.current_time = curr_time self.increment = increment def __call__(self, *args, **kwargs): current_time = self.current_time self.current_time += self.increment return current_time class DummyMock: def start(self): pass def stop(self): pass def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): pass def time_mock(start=200, step=20): return mock.patch("pyfakefs.helpers.now", DummyTime(start, step)) class TestCase(unittest.TestCase): """Test base class with some convenience methods and attributes""" is_windows = sys.platform == "win32" is_cygwin = sys.platform == "cygwin" is_macos = sys.platform == "darwin" def assert_mode_equal(self, expected, actual): return self.assertEqual(stat.S_IMODE(expected), stat.S_IMODE(actual)) @contextmanager def raises_os_error(self, subtype): try: yield self.fail("No exception was raised, OSError expected") except OSError as exc: if isinstance(subtype, list): self.assertIn(exc.errno, subtype) else: self.assertEqual(subtype, exc.errno) def skip_if_symlink_not_supported(): """If called at test start, tests are skipped if symlinks are not supported.""" if TestCase.is_windows and not is_root(): raise unittest.SkipTest("Symlinks under Windows need admin privileges") class RealFsTestMixin: """Test mixin to allow tests to run both in the fake filesystem and in the real filesystem. To run tests in the real filesystem, a new test class can be derived from the test class testing the fake filesystem which overwrites `use_real_fs()` to return `True`. All tests in the real file system operate inside the local temp path. In order to make a test able to run in the real FS, it must not use the fake filesystem functions directly. For access to `os` and `open`, the respective attributes must be used, which point either to the native or to the fake modules. A few convenience methods allow to compose paths, create files and directories. """ def __init__(self): self.filesystem = None self.open = open self.os = os self.base_path = None def setUp(self): if not os.environ.get("TEST_REAL_FS"): self.skip_real_fs() if self.use_real_fs(): self.base_path = tempfile.mkdtemp() def tearDown(self): if self.use_real_fs(): self.os.chdir(os.path.dirname(self.base_path)) shutil.rmtree(self.base_path, ignore_errors=True) os.chdir(self.cwd) @property def is_windows_fs(self): return TestCase.is_windows def set_windows_fs(self, value): if self.filesystem is not None: self.filesystem._is_windows_fs = value if value: self.filesystem._is_macos = False self.create_basepath() @property def is_macos(self): return TestCase.is_macos @property def is_pypy(self): return platform.python_implementation() == "PyPy" def use_real_fs(self): """Return True if the real file system shall be tested.""" return False def setUpFileSystem(self): pass def path_separator(self): """Can be overwritten to use a specific separator in the fake filesystem.""" if self.use_real_fs(): return os.path.sep return "/" def check_windows_only(self): """If called at test start, the real FS test is executed only under Windows, and the fake filesystem test emulates a Windows system. """ if self.use_real_fs(): if not TestCase.is_windows: raise unittest.SkipTest("Testing Windows specific functionality") else: self.set_windows_fs(True) def check_linux_only(self): """If called at test start, the real FS test is executed only under Linux, and the fake filesystem test emulates a Linux system. """ if self.use_real_fs(): if TestCase.is_macos or TestCase.is_windows: raise unittest.SkipTest("Testing Linux specific functionality") else: self.set_windows_fs(False) self.filesystem._is_macos = False def check_macos_only(self): """If called at test start, the real FS test is executed only under MacOS, and the fake filesystem test emulates a MacOS system. """ if self.use_real_fs(): if not TestCase.is_macos: raise unittest.SkipTest("Testing MacOS specific functionality") else: self.set_windows_fs(False) self.filesystem._is_macos = True def check_linux_and_windows(self): """If called at test start, the real FS test is executed only under Linux and Windows, and the fake filesystem test emulates a Linux system under MacOS. """ if self.use_real_fs(): if TestCase.is_macos: raise unittest.SkipTest("Testing non-MacOs functionality") else: self.filesystem._is_macos = False def check_case_insensitive_fs(self): """If called at test start, the real FS test is executed only in a case-insensitive FS (e.g. Windows or MacOS), and the fake filesystem test emulates a case-insensitive FS under the running OS. """ if self.use_real_fs(): if not TestCase.is_macos and not TestCase.is_windows: raise unittest.SkipTest( "Testing case insensitive specific functionality" ) else: self.filesystem.is_case_sensitive = False def check_case_sensitive_fs(self): """If called at test start, the real FS test is executed only in a case-sensitive FS (e.g. under Linux), and the fake file system test emulates a case-sensitive FS under the running OS. """ if self.use_real_fs(): if TestCase.is_macos or TestCase.is_windows: raise unittest.SkipTest("Testing case sensitive specific functionality") else: self.filesystem.is_case_sensitive = True def check_posix_only(self): """If called at test start, the real FS test is executed only under Linux and MacOS, and the fake filesystem test emulates a Linux system under Windows. """ if self.use_real_fs(): if TestCase.is_windows: raise unittest.SkipTest("Testing Posix specific functionality") else: self.set_windows_fs(False) @staticmethod def skip_root(): """Skips the test if run as root.""" if is_root(): raise unittest.SkipTest("Test only valid for non-root user") def skip_real_fs(self): """If called at test start, no real FS test is executed.""" if self.use_real_fs(): raise unittest.SkipTest("Only tests fake FS") def skip_real_fs_failure( self, skip_windows=True, skip_posix=True, skip_macos=True, skip_linux=True, ): """If called at test start, no real FS test is executed for the given conditions. This is used to mark tests that do not pass correctly under certain systems and shall eventually be fixed. """ if True: if self.use_real_fs() and ( TestCase.is_windows and skip_windows or not TestCase.is_windows and skip_macos and skip_linux or TestCase.is_macos and skip_macos or not TestCase.is_windows and not TestCase.is_macos and skip_linux or not TestCase.is_windows and skip_posix ): raise unittest.SkipTest( "Skipping because FakeFS does not match real FS" ) def make_path(self, *args): """Create a path with the given component(s). A base path is prepended to the path which represents a temporary directory in the real FS, and a fixed path in the fake filesystem. Always use to compose absolute paths for tests also running in the real FS. """ if isinstance(args[0], (list, tuple)): path = self.base_path for arg in args[0]: path = self.os.path.join(path, to_string(arg)) return path args = [to_string(arg) for arg in args] return self.os.path.join(self.base_path, *args) def create_dir(self, dir_path, perm=0o777, apply_umask=True): """Create the directory at `dir_path`, including subdirectories. `dir_path` shall be composed using `make_path()`. """ if not dir_path: return existing_path = dir_path components = [] while existing_path and not self.os.path.exists(existing_path): existing_path, component = self.os.path.split(existing_path) if not component and existing_path: # existing path is a drive or UNC root if not self.os.path.exists(existing_path): self.filesystem.add_mount_point(existing_path) break components.insert(0, component) for component in components: existing_path = self.os.path.join(existing_path, component) self.os.mkdir(existing_path) self.os.chmod(existing_path, 0o777) if apply_umask: umask = self.os.umask(0o022) perm &= ~umask self.os.umask(umask) self.os.chmod(dir_path, perm) def create_file( self, file_path, contents=None, encoding=None, perm=0o666, apply_umask=True ): """Create the given file at `file_path` with optional contents, including subdirectories. `file_path` shall be composed using `make_path()`. """ self.create_dir(self.os.path.dirname(file_path)) mode = "wb" if encoding is not None or is_byte_string(contents) else "w" kwargs = {"mode": mode} if encoding is not None and contents is not None: contents = contents.encode(encoding) if mode == "w": kwargs["encoding"] = "utf8" with self.open(file_path, **kwargs) as f: if contents is not None: f.write(contents) if apply_umask: umask = self.os.umask(0o022) perm &= ~umask self.os.umask(umask) self.os.chmod(file_path, perm) def create_symlink(self, link_path, target_path): """Create the path at `link_path`, and a symlink to this path at `target_path`. `link_path` shall be composed using `make_path()`. """ self.create_dir(self.os.path.dirname(link_path)) self.os.symlink(target_path, link_path) def check_contents(self, file_path, contents): """Compare `contents` with the contents of the file at `file_path`. Asserts equality. """ mode = "rb" if is_byte_string(contents) else "r" kwargs = {"mode": mode} if mode == "r": kwargs["encoding"] = "utf8" with self.open(file_path, **kwargs) as f: self.assertEqual(contents, f.read()) def create_basepath(self): """Create the path used as base path in `make_path`.""" if self.filesystem is not None: old_base_path = self.base_path self.base_path = self.filesystem.path_separator + "basepath" if self.filesystem.is_windows_fs: self.base_path = "C:" + self.base_path if old_base_path != self.base_path: if old_base_path is not None: self.filesystem.reset() if not self.filesystem.exists(self.base_path): self.filesystem.create_dir(self.base_path) if old_base_path is not None: self.setUpFileSystem() def assert_equal_paths(self, actual, expected): if self.is_windows: actual = str(actual).replace("\\\\?\\", "") expected = str(expected).replace("\\\\?\\", "") if os.name == "nt" and self.use_real_fs(): # work around a problem that the user name, but not the full # path is shown as the short name self.assertEqual( self.path_with_short_username(actual), self.path_with_short_username(expected), ) else: self.assertEqual(actual, expected) elif self.is_macos: self.assertEqual( str(actual).replace("/private/var/", "/var/"), str(expected).replace("/private/var/", "/var/"), ) else: self.assertEqual(actual, expected) @staticmethod def path_with_short_username(path): components = path.split(os.sep) if len(components) >= 3: components[2] = components[2][:6].upper() + "~1" return os.sep.join(components) def mock_time(self, start=200, step=20): if not self.use_real_fs(): return time_mock(start, step) return DummyMock() def assert_raises_os_error(self, subtype, expression, *args, **kwargs): """Asserts that a specific subtype of OSError is raised.""" try: expression(*args, **kwargs) self.fail("No exception was raised, OSError expected") except OSError as exc: if isinstance(subtype, list): self.assertIn(exc.errno, subtype) else: self.assertEqual(subtype, exc.errno) class RealFsTestCase(TestCase, RealFsTestMixin): """Can be used as base class for tests also running in the real file system.""" def __init__(self, methodName="runTest"): TestCase.__init__(self, methodName) RealFsTestMixin.__init__(self) def setUp(self): RealFsTestMixin.setUp(self) self.cwd = os.getcwd() if not self.use_real_fs(): self.filesystem = fake_filesystem.FakeFilesystem( path_separator=self.path_separator() ) self.setup_fake_fs() self.setUpFileSystem() def setup_fake_fs(self): if not self.use_real_fs(): self.open = fake_open.FakeFileOpen(self.filesystem) self.os = fake_os.FakeOsModule(self.filesystem) self.create_basepath() def tearDown(self): RealFsTestMixin.tearDown(self) @property def is_windows_fs(self): if self.use_real_fs(): return self.is_windows return self.filesystem.is_windows_fs @property def is_macos(self): if self.use_real_fs(): return TestCase.is_macos return self.filesystem.is_macos ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741721344.967527 pyfakefs-5.8.0/pyfakefs.egg-info/0000755000175100001660000000000014764107401016245 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721344.0 pyfakefs-5.8.0/pyfakefs.egg-info/PKG-INFO0000644000175100001660000001713114764107400017344 0ustar00runnerdockerMetadata-Version: 2.2 Name: pyfakefs Version: 5.8.0 Summary: pyfakefs implements a fake file system that mocks the Python file system modules. Home-page: https://github.com/pytest-dev/pyfakefs Author: Google Author-email: google-pyfakefs@google.com Maintainer: John McGehee Maintainer-email: pyfakefs@johnnado.com License: http://www.apache.org/licenses/LICENSE-2.0 Keywords: testing,test,file,os,shutil,pathlib,mocking,unittest,pytest,fakes,filesystem Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Operating System :: POSIX Classifier: Operating System :: MacOS Classifier: Operating System :: Microsoft :: Windows Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Testing Classifier: Topic :: System :: Filesystems Classifier: Framework :: Pytest Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: COPYING # pyfakefs [![PyPI version](https://badge.fury.io/py/pyfakefs.svg)](https://badge.fury.io/py/pyfakefs) [![Python version](https://img.shields.io/pypi/pyversions/pyfakefs.svg)](https://img.shields.io/pypi/pyversions/pyfakefs.svg) ![Testsuite](https://github.com/pytest-dev/pyfakefs/workflows/Testsuite/badge.svg) [![Documentation Status](https://readthedocs.org/projects/pytest-pyfakefs/badge/?version=latest)](https://pytest-pyfakefs.readthedocs.io/en/latest/?badge=latest) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/pytest-dev/pyfakefs/main.svg)](https://results.pre-commit.ci/latest/github/pytest-dev/pyfakefs/main) pyfakefs implements a fake file system that mocks the Python file system modules. Using pyfakefs, your tests operate on a fake file system in memory without touching the real disk. The software under test requires no modification to work with pyfakefs. Pyfakefs creates a new empty in-memory file system at each test start, which replaces the real filesystem during the test. Think of pyfakefs as making a per-test temporary directory, except for an entire file system. There are several means to achieve this: by using the `fs` fixture if running pytest, by using `fake_filesystem_unittest.TestCase` as a base class if using unittest, by using a `fake_filesystem_unittest.Patcher` instance as a context manager, or by using the `patchfs` decorator. pyfakefs works with current versions of Linux, Windows and macOS. ## Documentation This document provides a general overview for pyfakefs. There is more: * The documentation at **Read the Docs**: * The [Release documentation](https://pytest-pyfakefs.readthedocs.io/en/stable) contains usage documentation for pyfakefs and a description of the most relevant classes, methods and functions for the last version released on PyPI * The [Development documentation](https://pytest-pyfakefs.readthedocs.io/en/latest) contains the same documentation for the current main branch * The [Release 3.7 documentation](https://pytest-pyfakefs.readthedocs.io/en/v3.7.2/) contains usage documentation for the last version of pyfakefs supporting Python 2.7 * The [Release Notes](https://github.com/pytest-dev/pyfakefs/blob/main/CHANGES.md) show a list of changes in the latest versions ## Usage The simplest method to use pyfakefs is using the `fs` fixture with `pytest`. Refer to the [usage documentation](https://pytest-pyfakefs.readthedocs.io/en/latest/usage.html) for information on other test scenarios, test customization and using convenience functions. ## Features Apart from automatically mocking most file-system functions, pyfakefs provides some additional features: - mapping files and directories from the real file system into the fake filesystem - configuration and tracking of the file system size - pause and resume of patching to be able to use the real file system inside a test step - (limited) emulation of other OSes (Linux, macOS or Windows) - configuration to behave as if running as a non-root user while running under root ## Compatibility pyfakefs works with CPython 3.7 and above, on Linux, Windows and macOS, and with PyPy3. pyfakefs works with [pytest](http://doc.pytest.org) version 6.2.5 or above, though a current version is recommended. pyfakefs will not work with Python libraries that use C libraries to access the file system. This is because pyfakefs cannot patch the underlying C libraries' file access functions--the C libraries will always access the real file system. Refer to the [documentation](https://pytest-pyfakefs.readthedocs.io/en/latest/intro.html#limitations) for more information about the limitations of pyfakefs. ## Development ### Continuous integration pyfakefs is currently automatically tested on Linux, macOS and Windows, with Python 3.7 to 3.13, and with PyPy3 on Linux, using [GitHub Actions](https://github.com/pytest-dev/pyfakefs/actions). ### Running pyfakefs unit tests #### On the command line pyfakefs unit tests can be run using `pytest` (all tests) or `unittest` (all tests except `pytest`-specific ones): ```bash $ cd pyfakefs/ $ export PYTHONPATH=$PWD $ python -m pytest pyfakefs $ python -m pyfakefs.tests.all_tests ``` Similar scripts are called by `tox` and Github Actions. `tox` can be used to run tests locally against supported python versions: ```bash $ tox ``` #### In a Docker container The `Dockerfile` at the repository root will run the tests on the latest Ubuntu version. Build the container: ```bash cd pyfakefs/ docker build -t pyfakefs . ``` Run the unit tests in the container: ```bash docker run -t pyfakefs ``` ### Contributing to pyfakefs We always welcome contributions to the library. Check out the [Contributing Guide](https://github.com/pytest-dev/pyfakefs/blob/main/CONTRIBUTING.md) for more information. ## History pyfakefs.py was initially developed at Google by Mike Bland as a modest fake implementation of core Python modules. It was introduced to all of Google in September 2006. Since then, it has been enhanced to extend its functionality and usefulness. At last count, pyfakefs was used in over 20,000 Python tests at Google. Google released pyfakefs to the public in 2011 as Google Code project [pyfakefs](http://code.google.com/p/pyfakefs/): * Fork [jmcgeheeiv-pyfakefs](http://code.google.com/p/jmcgeheeiv-pyfakefs/) added [direct support for unittest and doctest](../../wiki/Automatically-find-and-patch-file-functions-and-modules) * Fork [shiffdane-jmcgeheeiv-pyfakefs](http://code.google.com/p/shiffdane-jmcgeheeiv-pyfakefs/) added further corrections After the [shutdown of Google Code](http://google-opensource.blogspot.com/2015/03/farewell-to-google-code.html) was announced, [John McGehee](https://github.com/jmcgeheeiv) merged all three Google Code projects together [here on GitHub](https://github.com/pytest-dev/pyfakefs) where an enthusiastic community actively supports, maintains and extends pyfakefs. In 2022, the repository has been transferred to [pytest-dev](https://github.com/pytest-dev) to ensure continuous maintenance. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721344.0 pyfakefs-5.8.0/pyfakefs.egg-info/SOURCES.txt0000644000175100001660000000530014764107400020126 0ustar00runnerdockerCHANGES.md CONTRIBUTING.md COPYING MANIFEST.in README.md extra_requirements.txt legacy_requirements.txt mypy.ini pyproject.toml requirements.txt requirements_dev.txt setup.cfg setup.py tox.ini pyfakefs/__init__.py pyfakefs/_version.py pyfakefs/fake_file.py pyfakefs/fake_filesystem.py pyfakefs/fake_filesystem_shutil.py pyfakefs/fake_filesystem_unittest.py pyfakefs/fake_io.py pyfakefs/fake_legacy_modules.py pyfakefs/fake_open.py pyfakefs/fake_os.py pyfakefs/fake_path.py pyfakefs/fake_pathlib.py pyfakefs/fake_scandir.py pyfakefs/helpers.py pyfakefs/legacy_packages.py pyfakefs/mox3_stubout.py pyfakefs/patched_packages.py pyfakefs/py.typed pyfakefs/pytest_plugin.py pyfakefs.egg-info/PKG-INFO pyfakefs.egg-info/SOURCES.txt pyfakefs.egg-info/dependency_links.txt pyfakefs.egg-info/entry_points.txt pyfakefs.egg-info/top_level.txt pyfakefs/pytest_session_tests/__init__.py pyfakefs/pytest_session_tests/test_patch_on_setup.py pyfakefs/pytest_tests/__init__.py pyfakefs/pytest_tests/conftest.py pyfakefs/pytest_tests/example.py pyfakefs/pytest_tests/fake_fcntl_test.py pyfakefs/pytest_tests/io.py pyfakefs/pytest_tests/lib_using_pathlib.py pyfakefs/pytest_tests/local_import.py pyfakefs/pytest_tests/pytest_check_failed_plugin_test.py pyfakefs/pytest_tests/pytest_doctest_test.py pyfakefs/pytest_tests/pytest_fixture_param_test.py pyfakefs/pytest_tests/pytest_fixture_test.py pyfakefs/pytest_tests/pytest_module_fixture_test.py pyfakefs/pytest_tests/pytest_plugin_failing_helper.py pyfakefs/pytest_tests/pytest_plugin_test.py pyfakefs/pytest_tests/pytest_reload_pandas_test.py pyfakefs/pytest_tests/test_reload_local_import.py pyfakefs/pytest_tests/unhashable.py pyfakefs/tests/__init__.py pyfakefs/tests/all_tests.py pyfakefs/tests/all_tests_without_extra_packages.py pyfakefs/tests/dynamic_patch_test.py pyfakefs/tests/example.py pyfakefs/tests/example_test.py pyfakefs/tests/fake_filesystem_glob_test.py pyfakefs/tests/fake_filesystem_shutil_test.py pyfakefs/tests/fake_filesystem_test.py pyfakefs/tests/fake_filesystem_unittest_test.py pyfakefs/tests/fake_filesystem_vs_real_test.py pyfakefs/tests/fake_legacy_modules_test.py pyfakefs/tests/fake_open_test.py pyfakefs/tests/fake_os_test.py pyfakefs/tests/fake_pathlib_test.py pyfakefs/tests/fake_stat_time_test.py pyfakefs/tests/fake_tempfile_test.py pyfakefs/tests/import_as_example.py pyfakefs/tests/logsio.py pyfakefs/tests/mox3_stubout_example.py pyfakefs/tests/mox3_stubout_test.py pyfakefs/tests/patched_packages_test.py pyfakefs/tests/performance_test.py pyfakefs/tests/skipped_pathlib.py pyfakefs/tests/test_utils.py pyfakefs/tests/fixtures/__init__.py pyfakefs/tests/fixtures/config_module.py pyfakefs/tests/fixtures/deprecated_property.py pyfakefs/tests/fixtures/module_with_attributes.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721344.0 pyfakefs-5.8.0/pyfakefs.egg-info/dependency_links.txt0000644000175100001660000000000114764107400022312 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721344.0 pyfakefs-5.8.0/pyfakefs.egg-info/entry_points.txt0000644000175100001660000000006214764107400021540 0ustar00runnerdocker[pytest11] pytest_fakefs = pyfakefs.pytest_plugin ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721344.0 pyfakefs-5.8.0/pyfakefs.egg-info/top_level.txt0000644000175100001660000000001114764107400020766 0ustar00runnerdockerpyfakefs ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/pyproject.toml0000644000175100001660000000012114764107375015663 0ustar00runnerdocker[build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/requirements.txt0000644000175100001660000000001614764107375016236 0ustar00runnerdockerpytest>=6.2.5 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/requirements_dev.txt0000644000175100001660000000002214764107375017071 0ustar00runnerdockerpre-commit==4.0.1 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1741721344.9685268 pyfakefs-5.8.0/setup.cfg0000644000175100001660000000343314764107401014567 0ustar00runnerdocker[metadata] name = pyfakefs version = attr: pyfakefs.__version__ author = Google author_email = google-pyfakefs@google.com maintainer = John McGehee maintainer_email = pyfakefs@johnnado.com license = http://www.apache.org/licenses/LICENSE-2.0 description = pyfakefs implements a fake file system that mocks the Python file system modules. long_description = file: README.md long_description_content_type = text/markdown keywords = testing test file os shutil pathlib mocking unittest pytest fakes filesystem url = https://github.com/pytest-dev/pyfakefs classifiers = Development Status :: 5 - Production/Stable Environment :: Console Intended Audience :: Developers License :: OSI Approved :: Apache Software License Programming Language :: Python :: 3 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Programming Language :: Python :: 3.13 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Operating System :: POSIX Operating System :: MacOS Operating System :: Microsoft :: Windows Topic :: Software Development :: Libraries Topic :: Software Development :: Libraries :: Python Modules Topic :: Software Development :: Testing Topic :: System :: Filesystems Framework :: Pytest [bdist_wheel] universal = 0 [options] packages = find: install_requires = python_requires = >=3.7 test_suite = pyfakefs.tests include_package_data = True [options.packages.find] exclude = docs [options.package_data] pyfakefs = py.typed [options.entry_points] pytest11 = pytest_fakefs = pyfakefs.pytest_plugin [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/setup.py0000644000175100001660000000010514764107375014463 0ustar00runnerdockerfrom setuptools import setup if __name__ == "__main__": setup() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741721341.0 pyfakefs-5.8.0/tox.ini0000644000175100001660000000052614764107375014273 0ustar00runnerdocker[tox] envlist = py{37,38,39,310,311,312,313} pypy{37,39,310} [testenv] deps = -rrequirements.txt -rextra_requirements.txt passenv = HOME,USERPROFILE commands= python -m pyfakefs.tests.all_tests python -m pyfakefs.tests.all_tests_without_extra_packages python -m pytest pyfakefs/pytest_tests/pytest_plugin_test.py