diff options
author | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-03-05 04:19:43 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-03-05 04:19:43 +0000 |
commit | b3e480f35d19f5a04bec59be1bb92f5f023b38ac (patch) | |
tree | 9b3d6367f664e2fceb8dd436509a4e8b1ad360bb | |
parent | c2735ebbddcf5e4c314188c0930967a3d9428c33 (diff) | |
parent | dbd4f42b65cd97eccffd1b034a95555a5e2a4825 (diff) | |
download | pyfakefs-b3e480f35d19f5a04bec59be1bb92f5f023b38ac.tar.gz |
Initial merge with upstream am: dbd4f42b65
Change-Id: I6745a4133d366acdced3b6d2d09208828550def9
84 files changed, 25302 insertions, 0 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..6f02963 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,24 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. +Please provide a stack trace if available. + +**How To Reproduce** +Please provide a unit test or a minimal code snippet that reproduces the +problem. + +**Your enviroment** +Please run the following and paste the output. +```bash +python -c "import platform; print(platform.platform())" +python -c "import sys; print('Python', sys.version)" +python -c "from pyfakefs.fake_filesystem import __version__; print('pyfakefs', __version__)" +``` diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..75dac2d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A description of any alternative solutions or features you've considered. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f83e65a --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +*.pyc +*.egg-info/ +.tox/ + +# Eclipse +.settings +.project +.pydevproject + +# PyCharm +.idea/ + +# pytest +.cache/ + +# autodoc created by sphinx +gh-pages/ + +# Distribution creation +dist/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b01f1f7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,148 @@ +# Perform continuous integration testing with Travis CI. +# +# Copyright 2015 John McGehee. 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. + +language: python + +before_script: + - ./.travis/install.sh + +jobs: + include: + - stage: flake8 + script: ./.travis/run_flake.sh + + - stage: test + script: + - ./.travis/run_tests.sh + - ./.travis/run_pytest_plugin_test.sh + python: 3.5.9 + env: + - PYTHON=py35 + - PY_VERSION=3.5.9 + + - stage: test + script: + - ./.travis/run_tests.sh + - ./.travis/run_pytest_fixture_test.sh + - ./.travis/run_pytest_fixture_param_test.sh + - ./.travis/run_pytest_plugin_test.sh + python: 3.6.9 + env: + - PYTHON=py36 + - PY_VERSION=3.6.9 + + - stage: test + script: + - ./.travis/run_tests.sh + - ./.travis/run_pytest_fixture_test.sh + - ./.travis/run_pytest_fixture_param_test.sh + - ./.travis/run_pytest_plugin_test.sh + python: 3.7.5 + dist: xenial + sudo: true + env: + - PYTHON=py37 + - PY_VERSION=3.7.5 + + - stage: test + script: + - ./.travis/run_tests.sh + - ./.travis/run_pytest_fixture_test.sh + - ./.travis/run_pytest_fixture_param_test.sh + - ./.travis/run_pytest_plugin_test.sh + python: 3.8.1 + dist: xenial + sudo: true + env: + - PYTHON=py38 + - PY_VERSION=3.8.1 + + - stage: test + script: + - ./.travis/run_tests.sh + - ./.travis/run_pytest_plugin_test.sh + python: pypy3.5-7.0.0 + dist: xenial + sudo: true + env: PYTHON=pypy3 + + - stage: test + script: + - ./.travis/run_tests.sh + - ./.travis/run_pytest_fixture_test.sh + - ./.travis/run_pytest_fixture_param_test.sh + - ./.travis/run_pytest_plugin_test.sh + os: osx + language: generic + env: + - PYTHON=py36 + - PY_VERSION=3.6.9 + + - stage: test + script: + - ./.travis/run_tests.sh + - ./.travis/run_pytest_fixture_test.sh + - ./.travis/run_pytest_fixture_param_test.sh + - ./.travis/run_pytest_plugin_test.sh + os: osx + language: generic + env: + - PYTHON=py37 + - PY_VERSION=3.7.6 + + - stage: test + script: + - ./.travis/run_tests.sh + - ./.travis/run_pytest_fixture_test.sh + - ./.travis/run_pytest_fixture_param_test.sh + - ./.travis/run_pytest_plugin_test.sh + os: osx + language: generic + env: + - PYTHON=py38 + - PY_VERSION=3.8.1 + + - stage: test + script: + - ./.travis/docker_tests.sh + language: minimal + env: + - VM=Docker + - DOCKERFILE=ubuntu + + - stage: test + script: + - ./.travis/docker_tests.sh + language: minimal + env: + - VM=Docker + - DOCKERFILE=centos + + - stage: test + script: + - ./.travis/docker_tests.sh + language: minimal + env: + - VM=Docker + - DOCKERFILE=fedora + + - stage: test + script: + - ./.travis/docker_tests.sh + language: minimal + env: + - VM=Docker + - DOCKERFILE=debian diff --git a/.travis/docker_tests.sh b/.travis/docker_tests.sh new file mode 100755 index 0000000..8f5f7e7 --- /dev/null +++ b/.travis/docker_tests.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +if [[ $VM == 'Docker' ]]; then + echo "Running tests in Docker image '$DOCKERFILE'" + echo "=============================" + export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi) + export REPO_SLUG=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_REPO_SLUG; else echo $TRAVIS_PULL_REQUEST_SLUG; fi) + docker build -t pyfakefs -f .travis/dockerfiles/Dockerfile_$DOCKERFILE . --build-arg github_repo=$REPO_SLUG --build-arg github_branch=$BRANCH + docker run -t pyfakefs +fi diff --git a/.travis/dockerfiles/Dockerfile_centos b/.travis/dockerfiles/Dockerfile_centos new file mode 100644 index 0000000..f22fb63 --- /dev/null +++ b/.travis/dockerfiles/Dockerfile_centos @@ -0,0 +1,41 @@ +# Copyright 2018 John McGehee. 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. + +FROM centos:7 +MAINTAINER jmcgeheeiv@users.noreply.github.com + +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +ARG github_repo=jmcgeheeiv/pyfakefs +ARG github_branch=master + +RUN yum install -y python3-pip unzip wget + +RUN useradd -u 1000 pyfakefs + +RUN mkdir -p work \ + && wget https://github.com/$github_repo/archive/$github_branch.zip \ + && unzip $github_branch.zip -d work +RUN WORK_DIR=`ls -d work/*`; mv $WORK_DIR work/pyfakefs +RUN chown -R pyfakefs:pyfakefs work/pyfakefs +WORKDIR work/pyfakefs +RUN pip3 install -r requirements.txt +RUN pip3 install -r extra_requirements.txt + +USER pyfakefs +ENV PYTHONPATH work/pyfakefs +ENV TEST_REAL_FS=1 +CMD ["python3", "-m", "pyfakefs.tests.all_tests"] diff --git a/.travis/dockerfiles/Dockerfile_debian b/.travis/dockerfiles/Dockerfile_debian new file mode 100644 index 0000000..0012e77 --- /dev/null +++ b/.travis/dockerfiles/Dockerfile_debian @@ -0,0 +1,46 @@ +# Copyright 2018 John McGehee. 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. + +FROM debian +MAINTAINER jmcgeheeiv@users.noreply.github.com + +RUN apt-get update && apt-get install -y locales +RUN locale-gen en_US.UTF-8 +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 +ARG github_repo=jmcgeheeiv/pyfakefs +ARG github_branch=master + +RUN apt-get update && apt-get install -y \ + python3-pip \ + unzip \ + wget +RUN apt-get clean + +RUN useradd -u 1000 pyfakefs + +RUN mkdir -p work \ + && wget https://github.com/$github_repo/archive/$github_branch.zip \ + && unzip $github_branch.zip -d work +RUN WORK_DIR=`ls -d work/*`; mv $WORK_DIR work/pyfakefs +RUN chown -R pyfakefs:pyfakefs work/pyfakefs +WORKDIR work/pyfakefs +RUN pip3 install -r requirements.txt +RUN pip3 install -r extra_requirements.txt + +USER pyfakefs +ENV PYTHONPATH work/pyfakefs +ENV TEST_REAL_FS=1 +CMD ["python3", "-m", "pyfakefs.tests.all_tests"] diff --git a/.travis/dockerfiles/Dockerfile_fedora b/.travis/dockerfiles/Dockerfile_fedora new file mode 100644 index 0000000..528ef6d --- /dev/null +++ b/.travis/dockerfiles/Dockerfile_fedora @@ -0,0 +1,40 @@ +# Copyright 2018 John McGehee. 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. + +FROM fedora +MAINTAINER jmcgeheeiv@users.noreply.github.com + +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 +ARG github_repo=jmcgeheeiv/pyfakefs +ARG github_branch=master + +RUN dnf install -y python3-pip unzip wget + +RUN useradd -u 1000 pyfakefs + +RUN mkdir -p work \ + && wget https://github.com/$github_repo/archive/$github_branch.zip \ + && unzip $github_branch.zip -d work +RUN WORK_DIR=`ls -d work/*`; mv $WORK_DIR work/pyfakefs +RUN chown -R pyfakefs:pyfakefs work/pyfakefs +WORKDIR work/pyfakefs +RUN pip3 install -r requirements.txt +RUN pip3 install -r extra_requirements.txt + +USER pyfakefs +ENV PYTHONPATH work/pyfakefs +ENV TEST_REAL_FS=1 +CMD ["python3", "-m", "pyfakefs.tests.all_tests"] diff --git a/.travis/dockerfiles/Dockerfile_ubuntu b/.travis/dockerfiles/Dockerfile_ubuntu new file mode 100644 index 0000000..2e235cd --- /dev/null +++ b/.travis/dockerfiles/Dockerfile_ubuntu @@ -0,0 +1,46 @@ +# Copyright 2018 John McGehee. 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. + +FROM ubuntu +MAINTAINER jmcgeheeiv@users.noreply.github.com + +RUN apt-get update && apt-get install -y locales +RUN locale-gen en_US.UTF-8 +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 +ARG github_repo=jmcgeheeiv/pyfakefs +ARG github_branch=master + +RUN apt-get update && apt-get install -y \ + python3-pip \ + unzip \ + wget +RUN apt-get clean + +RUN useradd -u 1000 pyfakefs + +RUN mkdir -p work \ + && wget https://github.com/$github_repo/archive/$github_branch.zip \ + && unzip $github_branch.zip -d work +RUN WORK_DIR=`ls -d work/*`; mv $WORK_DIR work/pyfakefs +RUN chown -R pyfakefs:pyfakefs work/pyfakefs +WORKDIR work/pyfakefs +RUN pip3 install -r requirements.txt +RUN pip3 install -r extra_requirements.txt + +USER pyfakefs +ENV PYTHONPATH work/pyfakefs +ENV TEST_REAL_FS=1 +CMD ["python3", "-m", "pyfakefs.tests.all_tests"] diff --git a/.travis/install.sh b/.travis/install.sh new file mode 100755 index 0000000..c280a7a --- /dev/null +++ b/.travis/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# script to install Python versions under MacOS, as Travis.IO +# does not have explicit Python support for MacOS +# Taken from https://github.com/pyca/cryptography and adapted. + +if [[ $TRAVIS_OS_NAME == 'osx' ]]; then + sw_vers + + # install pyenv + git clone --depth 1 https://github.com/pyenv/pyenv ~/.pyenv + PYENV_ROOT="$HOME/.pyenv" + PATH="$PYENV_ROOT/bin:$PATH" + eval "$(pyenv init -)" + + case "${PYTHON}" in + py34|py35|py36|py37|py38) + pyenv install "${PY_VERSION}" + pyenv global "${PY_VERSION}" + ;; + pypy*) + pyenv install "$PYPY_VERSION" + pyenv global "$PYPY_VERSION" + ;; + esac + pyenv rehash + python -m pip install --user virtualenv + python -m virtualenv ~/.venv + source ~/.venv/bin/activate +fi + +if [ -n "$PY_VERSION" ] +then + echo Checking Python version... + if [ "$(python --version)" != "Python ${PY_VERSION}" ] + then + echo Incorrect version - expected "${PY_VERSION}". + echo Exiting. + exit 1 + fi + echo Python version ok. +fi + +if ! [[ $VM == 'Docker' ]]; then +pip install -r requirements.txt +pip install -r extra_requirements.txt +pip install . +fi
\ No newline at end of file diff --git a/.travis/run_flake.sh b/.travis/run_flake.sh new file mode 100755 index 0000000..f1cf807 --- /dev/null +++ b/.travis/run_flake.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +pip install flake8 + +# let the build fail for any flake8 warning +flake8 . --exclude get-pip.py --max-complexity=13 --statistics diff --git a/.travis/run_pytest_fixture_param_test.sh b/.travis/run_pytest_fixture_param_test.sh new file mode 100755 index 0000000..c57de65 --- /dev/null +++ b/.travis/run_pytest_fixture_param_test.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +if [[ $TRAVIS_OS_NAME == 'osx' ]]; then + source ~/.venv/bin/activate +fi + +python -m pytest pyfakefs/pytest_tests/pytest_fixture_param_test.py diff --git a/.travis/run_pytest_fixture_test.sh b/.travis/run_pytest_fixture_test.sh new file mode 100755 index 0000000..6b0c9e7 --- /dev/null +++ b/.travis/run_pytest_fixture_test.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +if [[ $TRAVIS_OS_NAME == 'osx' ]]; then + source ~/.venv/bin/activate +fi + +python -m pytest pyfakefs/pytest_tests/pytest_fixture_test.py diff --git a/.travis/run_pytest_plugin_test.sh b/.travis/run_pytest_plugin_test.sh new file mode 100755 index 0000000..0d49ec6 --- /dev/null +++ b/.travis/run_pytest_plugin_test.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +if [[ $TRAVIS_OS_NAME == 'osx' ]]; then + source ~/.venv/bin/activate +fi + +python -m pytest pyfakefs/pytest_tests/pytest_plugin_failing_test.py > ./testresult.txt +python -m pytest pyfakefs/pytest_tests/pytest_check_failed_plugin_test.py && \ +python -m pytest pyfakefs/pytest_tests/pytest_plugin_test.py diff --git a/.travis/run_tests.sh b/.travis/run_tests.sh new file mode 100755 index 0000000..d1a0a97 --- /dev/null +++ b/.travis/run_tests.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +if [[ $TRAVIS_OS_NAME == 'osx' ]]; then + source ~/.venv/bin/activate +fi + +python --version +export TEST_REAL_FS=1 +echo ======================================================= ; \ +echo Running unit tests with extra packages as non-root user ; \ +python -m pyfakefs.tests.all_tests && \ +echo ========================================================== ; \ +echo Running unit tests without extra packages as non-root user ; \ +python -m pyfakefs.tests.all_tests_without_extra_packages && \ +echo ============================================ ; \ +echo Running tests without extra packages as root ; \ +sudo env "PATH=$PATH" python -m pyfakefs.tests.all_tests_without_extra_packages diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..a167639 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,549 @@ +# pyfakefs Release Notes +The released versions correspond to PyPi releases. + +## Version 4.1.0 (as yet unreleased) + +## [Version 4.0.2](https://pypi.python.org/pypi/pyfakefs/4.0.2) + +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 not to be able to +install them under Python 2. + +#### Fixes + * Do not build for Python 2 (see [#524](../../issues/524)) + +## [Version 4.0.1](https://pypi.python.org/pypi/pyfakefs/4.0.1) + +This as a bug fix release for a regression bug. + +#### Fixes + * Avoid exception if using `flask-restx` (see [#523](../../issues/523)) + +## [Version 4.0.0](https://pypi.python.org/pypi/pyfakefs/4.0.0) + + * 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. + +#### 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) + +This version backports some fixes from master. + +#### 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) + +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 master) + +## [Version 3.7](https://pypi.python.org/pypi/pyfakefs/3.7) + +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) + +#### 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) + +#### 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) + +Another bug-fix release that mainly fixes a regression wih Python 2 that has +been introduced in version 3.5.3. + +#### Fixes + * regression: patching build-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) + +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) + +#### 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) + +#### 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) + +#### 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) + +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) + +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) + +#### 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) + +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) + +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) + +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) + +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) + +#### 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 compatability. 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) + +#### 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) + +#### 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://jmcgeheeiv.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) + +#### 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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..228b25e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,56 @@ + +# 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 [master branch](https://github.com/jmcgeheeiv/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/jmcgeheeiv/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: + * Please use the standard [PEP-8 coding style](https://www.python.org/dev/peps/pep-0008/) + (your IDE or tools like [pep8](https://pypi.python.org/pypi/pep8) or [pylint](https://pypi.python.org/pypi/pylint) will help you) + * 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 tests on [Travis](https://travis-ci.org/jmcgeheeiv/pyfakefs) + and [AppVeyor](https://ci.appveyor.com/project/jmcgeheeiv/pyfakefs) 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/jmcgeheeiv/pyfakefs/blob/master/README.md) using [markdown syntax](https://help.github.com/articles/basic-writing-and-formatting-syntax/) + * the documentation published on [GitHub Pages](http://jmcgeheeiv.github.io/pyfakefs/), + located in the `docs` directory. + For building the documentation, you will need [sphinx](http://sphinx.pocoo.org/). + * [this file](https://github.com/jmcgeheeiv/pyfakefs/blob/master/CONTRIBUTING.md) + if you want to enhance the contributing guide itself + +Thanks for taking the time to contribute to pyfakefs! @@ -0,0 +1,175 @@ + + 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. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8018db5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,53 @@ +# Copyright 2018 John McGehee. 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. + +# Prerequisites: +# * Install Docker +# * Clone pyfakefs +# +# To build and run the container: +# +# cd pyfakefs +# docker build -t pyfakefs . +# docker run -t pyfakefs + +FROM ubuntu +MAINTAINER jmcgeheeiv@users.noreply.github.com + +# The Ubuntu base container does not specify a locale. +# pyfakefs tests require at least the Latin1 character set. +RUN apt-get update && apt-get install -y locales +RUN locale-gen en_US.UTF-8 +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +RUN apt-get update && apt-get install -y \ + python3-pip \ + unzip \ + wget +RUN apt-get clean + +RUN useradd -u 1000 pyfakefs + +RUN wget https://github.com/jmcgeheeiv/pyfakefs/archive/master.zip \ + && unzip master.zip \ + && chown -R pyfakefs:pyfakefs /pyfakefs-master +WORKDIR /pyfakefs-master +RUN pip3 install -r requirements.txt +RUN pip3 install -r extra_requirements.txt + +USER pyfakefs +ENV PYTHONPATH /pyfakefs-master +CMD ["python3", "-m", "pyfakefs.tests.all_tests"] @@ -0,0 +1 @@ +COPYING
\ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100755 index 0000000..b1abafc --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include CHANGES.md +include COPYING +include README.md diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..6ed4f3a --- /dev/null +++ b/METADATA @@ -0,0 +1,19 @@ +name: "pyfakefs" +description: + "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." + +third_party { + url { + type: HOMEPAGE + value: "http://pyfakefs.org/" + } + url { + type: GIT + value: "https://github.com/jmcgeheeiv/pyfakefs.git" + } + version: "v3.7" + last_upgrade_date { year: 2019 month: 12 day: 18 } +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_APACHE2 @@ -0,0 +1 @@ +LICENSE
\ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..da06bef --- /dev/null +++ b/README.md @@ -0,0 +1,129 @@ +# 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) + +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 works with Linux, Windows and MacOS. + +## Documentation + +This file provides general usage instructions for pyfakefs. There is more: + +* The documentation at [GitHub Pages:](http://jmcgeheeiv.github.io/pyfakefs) + * The [Release documentation](http://jmcgeheeiv.github.io/pyfakefs/release) + contains usage documentation for pyfakefs and a description of the + most relevent classes, methods and functions for the last version + released on PyPi + * The [Development documentation](http://jmcgeheeiv.github.io/pyfakefs/master) + contains the same documentation for the current master branch + * The [Release 3.7 documentation](http://jmcgeheeiv.github.io/pyfakefs/release37) + contains usage documentation for the last version of pyfakefs + supporting Python 2.7 + * The [Release 3.3 documentation](http://jmcgeheeiv.github.io/pyfakefs/release33) + contains usage documentation for the last version of pyfakefs + supporting Python 2.6, and for the old-style API (which is still + supported but not documented in the current release) +* The [Release Notes](https://github.com/jmcgeheeiv/pyfakefs/blob/master/CHANGES.md) + show a list of changes in the latest versions + +### Linking to pyfakefs + +In your own documentation, please link to pyfakefs using the canonical URL <http://pyfakefs.org>. +This URL always points to the most relevant top page for pyfakefs. + +## Usage + +pyfakefs has support for `unittest` and `pytest`, but can also be used +directly using `fake_filesystem_unittest.Patcher`. Refer to the +[usage documentation](http://jmcgeheeiv.github.io/pyfakefs/master/usage.html) +for more information on test scenarios, test customization and +using convenience functions. + + +## Compatibility +pyfakefs works with CPython 3.5 and above, on Linux, Windows and OSX +(MacOS), and with PyPy3. + +pyfakefs works with [PyTest](http://doc.pytest.org) version 2.8.6 or above. + +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. +For example, pyfakefs will not work with [`lxml`](http://lxml.de/). In this case +`lxml` must be replaced with a pure Python alternative such as +[`xml.etree.ElementTree`](https://docs.python.org/3/library/xml.etree.elementtree.html). + +## Development + +### Continuous integration + +pyfakefs is currently automatically tested: +* [![Build Status](https://travis-ci.org/jmcgeheeiv/pyfakefs.svg)](https://travis-ci.org/jmcgeheeiv/pyfakefs) + on Linux, with Python 3.5 to 3.8, using [Travis](https://travis-ci.org/jmcgeheeiv/pyfakefs) +* [![Build Status](https://travis-ci.org/jmcgeheeiv/pyfakefs.svg)](https://travis-ci.org/jmcgeheeiv/pyfakefs) + on MacOS, with Python 3.6 to 3.8, using [Travis](https://travis-ci.org/jmcgeheeiv/pyfakefs) +* [![Build status](https://ci.appveyor.com/api/projects/status/4o8j21ufuo056873/branch/master?svg=true)](https://ci.appveyor.com/project/jmcgeheeiv/pyfakefs/branch/master) + on Windows, with Python 3.5 to 3.8 using [Appveyor](https://ci.appveyor.com/project/jmcgeheeiv/pyfakefs) + +### Running pyfakefs unit tests + +#### On the command line +pyfakefs unit tests can be run using `unittest` or `pytest`: + +```bash +$ cd pyfakefs/ +$ export PYTHONPATH=$PWD + +$ 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 +``` + +These scripts are called by `tox` and Travis-CI. `tox` can be used to run tests +locally against supported python versions: + +```bash +$ tox +``` + +#### In a Docker container + +The `Dockerfile` at the top of the repository 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/jmcgeheeiv/pyfakefs/blob/master/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 is used in over 2,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/jmcgeheeiv/pyfakefs) where an enthusiastic community actively supports, maintains +and extends pyfakefs. diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..da82a82 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,24 @@ +environment: + + matrix: + - PYTHON: "C:\\Python35-x64" + - PYTHON: "C:\\Python36-x64" + - PYTHON: "C:\\Python37-x64" + - PYTHON: "C:\\Python38-x64" + +install: + - "%PYTHON%\\python.exe -m pip install -r requirements.txt" + - "%PYTHON%\\python.exe -m pip install -r extra_requirements.txt" + - "%PYTHON%\\python.exe -m pip install ." + +build: off + +test_script: + - SET TEST_REAL_FS=1 + - "%PYTHON%\\python.exe -m pyfakefs.tests.all_tests" + - "%PYTHON%\\python.exe -m pyfakefs.tests.all_tests_without_extra_packages" + - "%PYTHON%\\python.exe -m pytest pyfakefs\\pytest_tests\\pytest_plugin_test.py" + - ps: If ($env:PYTHON -Match ".*3[678]-x64") { "$env:PYTHON\\python.exe -m pytest pyfakefs\\pytest_tests\\pytest_fixture_test.py" } + - ps: If ($env:PYTHON -Match ".*3[678]-x64") { "$env:PYTHON\\python.exe -m pytest pyfakefs\\pytest_tests\\pytest_fixture_param_test.py" } + - "%PYTHON%\\python.exe -m pytest pyfakefs\\pytest_tests\\pytest_plugin_failing_test.py > testresult.txt | echo." + - "%PYTHON%\\python.exe -m pytest pyfakefs\\pytest_tests\\pytest_check_failed_plugin_test.py" diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..6d09160 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,225 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = ../gh-pages + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " epub3 to make an epub3" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + @echo " dummy to check syntax errors of document sources" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR) + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyfakefs.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyfakefs.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/pyfakefs" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyfakefs" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: epub3 +epub3: + $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 + @echo + @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +.PHONY: dummy +dummy: + $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy + @echo + @echo "Build finished. Dummy builder generates no files." diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..9d49ef4 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,40 @@ +API Notes +========= + +With ``pyfakefs 3.4``, the public API has changed to be PEP-8 conform. +The old API is deprecated, and will be removed in some future version of +pyfakefs. +You can suppress the deprecation warnings for legacy code with the following +code: + +.. code:: python + + from pyfakefs.deprecator import Deprecator + + Deprecator.show_warnings = False + +Here is a list of selected changes: + +:pyfakefs.fake_filesystem.FakeFileSystem: + + CreateFile() -> create_file() + + CreateDirectory() -> create_dir() + + CreateLink() -> create_symlink() + + GetDiskUsage() -> get_disk_usage() + + SetDiskUsage() -> set_disk_usage() + +:pyfakefs.fake_filesystem.FakeFile: + + GetSize(), SetSize() -> size (property) + + SetContents() -> set_contents() + + SetATime() -> st_atime (property) + + SetMTime() -> st_mtime (property) + + SetCTime() -> st_ctime (property) diff --git a/docs/autopatch.rst b/docs/autopatch.rst new file mode 100644 index 0000000..66d6a22 --- /dev/null +++ b/docs/autopatch.rst @@ -0,0 +1,118 @@ +.. _auto_patch: + +Automatically find and patch file functions and modules +======================================================= +The ``fake_filesystem_unittest`` module automatically finds all real file +functions and modules, and stubs them out with the fake file system functions and modules. +The pyfakefs source code contains files that demonstrate this usage model: + +- ``example.py`` is the software under test. In production, it uses the + real file system. +- ``example_test.py`` tests ``example.py``. During testing, the pyfakefs fake + file system is used by ``example_test.py`` and ``example.py`` alike. + +Software Under Test +------------------- +``example.py`` contains a few functions that manipulate files. For instance: + +.. code:: python + + 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') as f: + ... f.readlines() + ["This is test file '/test/file.txt'.\\n", 'It was created using the open() function.\\n'] + ''' + with open(path, 'w') as f: + f.write("This is test file '{}'.\n".format(path)) + f.write("It was created using the open() function.\n") + +No functional code in ``example.py`` even hints at a fake file system. In +production, ``create_file()`` invokes the real file functions ``open()`` and +``write()``. + +Unit Tests and Doctests +----------------------- +``example_test.py`` contains unit tests for ``example.py``. ``example.py`` +contains the doctests, as you can see above. + +The module ``fake_filesystem_unittest`` contains code that finds all real file +functions and modules, and stubs these out with the fake file system functions +and modules: + +.. code:: python + + import os + import unittest + from pyfakefs import fake_filesystem_unittest + # The module under test is example: + import example + +Doctests +~~~~~~~~ +``example_test.py`` defines ``load_tests()``, which runs the doctests in +``example.py``: + +.. code:: python + + 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) + + +Everything, including all imported modules and the test, is stubbed out +with the fake filesystem. Thus you can use familiar file functions like +``os.mkdir()`` as part of your test fixture and they too will operate on the +fake file system. + +Unit Test Class +~~~~~~~~~~~~~~~ +Next comes the ``unittest`` test class. This class is derived from +``fake_filesystem_unittest.TestCase``, which is in turn derived from +``unittest.TestClass``: + +.. code:: python + + class TestExample(fake_filesystem_unittest.TestCase): + + def setUp(self): + self.setUpPyfakefs() + + def tearDown(self): + # It is no longer necessary to add self.tearDownPyfakefs() + pass + + def test_create_file(self): + '''Test example.create_file()''' + # The os module has been replaced with the fake os module so all of the + # following occurs in the fake filesystem. + 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')) + + ... + + +Just add ``self.setUpPyfakefs()`` in ``setUp()``. You need add nothing to +``tearDown()``. Write your tests as usual. From ``self.setUpPyfakefs()`` to +the end of your ``tearDown()`` method, all file operations will use the fake +file system. diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..c69b951 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# pyfakefs documentation build configuration file, created by +# sphinx-quickstart on Mon Oct 31 20:05:53 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys + +sys.path.insert( + 0, os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.githubpages', # puts .nojekyll file into source + 'sphinx.ext.napoleon' # enables google style docstrings +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +# +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'pyfakefs' +copyright = '''2009 Google Inc. All Rights Reserved. +© Copyright 2014 Altera Corporation. All Rights Reserved. +© Copyright 2014-2019 John McGehee''' +author = 'John McGehee' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '4.1' +# The full version, including alpha/beta/rc tags. +release = '4.1dev' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +autoclass_content = 'both' + +autodoc_member_order = 'bysource' + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'pyfakefs_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = ['.'] + +# The name for this set of Sphinx documents. +# "<project> v<release> documentation" by default. +# +# html_title = 'pyfakefs v3.4' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +# html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = [] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +# html_domain_indices = True + +# If false, no index is generated. +# +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +html_show_sphinx = False + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pyfakefsdoc' +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'pyfakefs.tex', 'pyfakefs Documentation', + 'John McGehee', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# +# latex_use_parts = False + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# It false, will not define \strong, \code, itleref, \crossref ... but only +# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added +# packages. +# +# latex_keep_old_macro_names = True + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'pyfakefs', 'pyfakefs Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'pyfakefs', 'pyfakefs Documentation', + author, 'pyfakefs', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..c38273e --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,25 @@ +.. pyfakefs documentation master file, created by + sphinx-quickstart on Mon Oct 31 20:05:53 2016. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to the pyfakefs documentation! +====================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + intro + usage + autopatch + modules + api + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` + diff --git a/docs/intro.rst b/docs/intro.rst new file mode 100644 index 0000000..052cb19 --- /dev/null +++ b/docs/intro.rst @@ -0,0 +1,106 @@ +Introduction +============ + +`pyfakefs <https://github.com/jmcgeheeiv/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 works with CPython 3.5 and above, on Linux, Windows and OSX +(MacOS), and with PyPy3. + +pyfakefs works with `PyTest <doc.pytest.org>`__ version 2.8.6 or above. + +Installation +------------ +pyfakefs is available on `PyPi <https://pypi.python.org/pypi/pyfakefs/>`__. +The latest released version can be installed from pypi: + +.. code:: bash + + pip install pyfakefs + +The latest master can be installed from the GitHub sources: + +.. code:: bash + + pip install git+https://github.com/jmcgeheeiv/pyfakefs + +Features +-------- +- Code executed under pyfakefs works transparently on a memory-based file + system without the need of special commands. The same code that works on + the real filesystem will work on the fake filesystem if running under + pyfakefs. + +- pyfakefs provides direct support for `unittest` (via a `TestCase` base + class) and `pytest` (via a fixture), but can also be used with other test + frameworks. + +- Each pyfakefs test starts with an empty file system, but it is possible to + map files and directories from the real file system into the fake + filesystem if needed. + +- No files in the real file system are changed during the tests, even in the + case of writing to mapped real files. + +- pyfakefs keeps track of the filesystem size if configured. The file system + size can be configured arbitrarily. + +- pyfakefs defaults to the OS it is running on, but can also be configured + to test code running under another OS (Linux, MacOS or Windows). + +- pyfakefs can be configured to behave as if running as a root or as a + non-root user, independently from the actual user. + + +Limitations +----------- +- pyfakefs will not work with Python libraries (other than `os` and `io`) that + use C libraries to access the file system, because it cannot patch the + underlying C libraries' file access functions. + +- pyfakefs patches most kinds of importing file system modules automatically, + but there are still some cases where this will not work. + See :ref:`customizing_patcher` for more information and ways to work around + this. + +- pyfakefs does not retain the MRO for file objects, so you cannot rely on + checks using `isinstance` for these objects (for example, to differentiate + between binary and textual file objects). + +- pyfakefs is only tested with CPython and the newest PyPy versions, other + Python implementations will probably not work. + +- Differences in the behavior in different Linux distributions or different + MacOS or Windows versions may not be reflected in the implementation, as + well as some OS-specific low-level file system behavior. The systems used + for automatic tests in + `Travis.CI <https://travis-ci.org/jmcgeheeiv/pyfakefs>`__ and + `AppVeyor <https://ci.appveyor.com/project/jmcgeheeiv/pyfakefs>`__ are + considered as reference systems. + +History +------- +pyfakefs was initially developed at Google by +`Mike Bland <https://mike-bland.com/about.html>`__ 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 +2,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 as described in + :ref:`auto_patch` +* 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 `on +GitHub <https://github.com/jmcgeheeiv/pyfakefs>`__ where an enthusiastic +community actively maintains and extends pyfakefs. diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..3302c28 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,281 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=../gh-pages +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^<target^>` where ^<target^> is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. epub3 to make an epub3 + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + echo. dummy to check syntax errors of document sources + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR% + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyfakefs.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyfakefs.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "epub3" ( + %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +if "%1" == "dummy" ( + %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. Dummy builder generates no files. + goto end +) + +:end diff --git a/docs/modules.rst b/docs/modules.rst new file mode 100644 index 0000000..4fa020f --- /dev/null +++ b/docs/modules.rst @@ -0,0 +1,58 @@ +Public Modules and Classes +========================== +.. note:: Only public classes and methods interesting to ``pyfakefs`` + users are shown. Methods that mimic the behavior of standard Python + functions and classes that are only needed internally are not listed. + +Fake filesystem module +---------------------- +.. automodule:: pyfakefs.fake_filesystem + :members: set_uid, set_gid + +Fake filesystem classes +----------------------- +.. autoclass:: pyfakefs.fake_filesystem.FakeFilesystem + :members: add_mount_point, + get_disk_usage, set_disk_usage, + add_real_directory, add_real_file, add_real_symlink, add_real_paths, + create_dir, create_file, create_symlink, + get_object, pause, resume + +.. autoclass:: pyfakefs.fake_filesystem.FakeFile + :members: byte_contents, contents, set_contents, + path, size, is_large_file + +.. autoclass:: pyfakefs.fake_filesystem.FakeDirectory + :members: contents, ordered_dirs, size, get_entry, remove_entry + +Unittest module classes +----------------------- + +.. autoclass:: pyfakefs.fake_filesystem_unittest.TestCaseMixin + :members: fs, setUpPyfakefs, pause, resume + +.. autoclass:: pyfakefs.fake_filesystem_unittest.TestCase + +.. autoclass:: pyfakefs.fake_filesystem_unittest.Patcher + :members: setUp, tearDown, pause, resume + +.. automodule:: pyfakefs.fake_filesystem_unittest + :members: patchfs + + +Faked module classes +-------------------- + +.. autoclass:: pyfakefs.fake_filesystem.FakeOsModule + +.. autoclass:: pyfakefs.fake_filesystem.FakePathModule + +.. autoclass:: pyfakefs.fake_filesystem.FakeFileOpen + +.. autoclass:: pyfakefs.fake_filesystem.FakeIoModule + +.. autoclass:: pyfakefs.fake_filesystem_shutil.FakeShutilModule + +.. autoclass:: pyfakefs.fake_pathlib.FakePathlibModule + +.. autoclass:: pyfakefs.fake_scandir.FakeScanDirModule diff --git a/docs/pyfakefs_theme/static/pyfakefs.css b/docs/pyfakefs_theme/static/pyfakefs.css new file mode 100644 index 0000000..8c00229 --- /dev/null +++ b/docs/pyfakefs_theme/static/pyfakefs.css @@ -0,0 +1,10 @@ +@import url("nature.css"); + +.code div pre { + background-color: #F5F5F5; +} + +.highlight pre { + background-color: #F5F5F5; +} + diff --git a/docs/pyfakefs_theme/theme.conf b/docs/pyfakefs_theme/theme.conf new file mode 100644 index 0000000..59e2205 --- /dev/null +++ b/docs/pyfakefs_theme/theme.conf @@ -0,0 +1,3 @@ +[theme] +inherit = nature +stylesheet = pyfakefs.css diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..cd9a9cd --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,634 @@ +Usage +===== + +Test Scenarios +-------------- +There are several approaches to implementing tests using ``pyfakefs``. + +Patch using fake_filesystem_unittest +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you are using the Python ``unittest`` package, the easiest approach is to +use test classes derived from ``fake_filesystem_unittest.TestCase``. + +If you call ``setUpPyfakefs()`` in your ``setUp()``, ``pyfakefs`` will +automatically find all real file functions and modules, and stub these out +with the fake file system functions and modules: + +.. code:: python + + from pyfakefs.fake_filesystem_unittest import TestCase + + class ExampleTestCase(TestCase): + def setUp(self): + self.setUpPyfakefs() + + def test_create_file(self): + file_path = '/test/file.txt' + self.assertFalse(os.path.exists(file_path)) + self.fs.create_file(file_path) + self.assertTrue(os.path.exists(file_path)) + +The usage is explained in more detail in :ref:`auto_patch` and +demonstrated in the files ``example.py`` and ``example_test.py``. + +Patch using the PyTest plugin +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you use `PyTest <https://doc.pytest.org>`__, you will be interested in +the PyTest plugin in ``pyfakefs``. +This automatically patches all file system functions and modules in a +similar manner as described above. + +The PyTest plugin provides the ``fs`` fixture for use in your test. For example: + +.. code:: python + + def my_fakefs_test(fs): + # "fs" is the reference to the fake file system + fs.create_file('/var/data/xx1.txt') + assert os.path.exists('/var/data/xx1.txt') + +Patch using fake_filesystem_unittest.Patcher +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you are using other means of testing like `nose <http://nose2.readthedocs.io>`__, you can do the +patching using ``fake_filesystem_unittest.Patcher`` - the class doing the actual work +of replacing the filesystem modules with the fake modules in the first two approaches. + +The easiest way is to just use ``Patcher`` as a context manager: + +.. code:: python + + from pyfakefs.fake_filesystem_unittest import Patcher + + with Patcher() as patcher: + # access the fake_filesystem object via patcher.fs + patcher.fs.create_file('/foo/bar', contents='test') + + # the following code works on the fake filesystem + with open('/foo/bar') as f: + contents = f.read() + +You can also initialize ``Patcher`` manually: + +.. code:: python + + from pyfakefs.fake_filesystem_unittest import Patcher + + patcher = Patcher() + patcher.setUp() # called in the initialization code + ... + patcher.tearDown() # somewhere in the cleanup code + +Patch using fake_filesystem_unittest.patchfs decorator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This is basically a convenience wrapper for the previous method. +If you want to use the fake filesystem for a single function, you can write: + +.. code:: python + + from pyfakefs.fake_filesystem_unittest import patchfs + + @patchfs + def test_something(fs): + # access the fake_filesystem object via fs + fs.create_file('/foo/bar', contents='test') + +Note the argument name ``fs``, which is mandatory. + +Don't confuse this with pytest tests, where ``fs`` is the fixture name (with +the same functionality). If you use pytest, you don't need this decorator. + +You can also use this to make a single unit test use the fake fs: + +.. code:: python + + class TestSomething(unittest.TestCase): + + @patchfs + def test_something(self, fs): + fs.create_file('/foo/bar', contents='test') + +If you want to pass additional arguments to the patcher you can just +pass them to the decorator: + +.. code:: python + + @patchfs(allow_root_user=False) + def test_something(fs): + # now always called as non-root user + os.makedirs('/foo/bar') + +Patch using unittest.mock (deprecated) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +You can also use ``mock.patch()`` to patch the modules manually. This approach will +only work for the directly imported modules, therefore it is not suited for testing +larger code bases. As the other approaches are more convenient, this one is considered +deprecated and will not be described in detail. + +.. _customizing_patcher: + +Customizing Patcher and TestCase +-------------------------------- + +Both ``fake_filesystem_unittest.Patcher`` and ``fake_filesystem_unittest.TestCase`` +provide a few arguments to handle cases where patching does not work out of +the box. +In case of ``fake_filesystem_unittest.TestCase``, these arguments can either +be set in the TestCase instance initialization, or passed to ``setUpPyfakefs()``. + +.. note:: If you need these arguments in ``PyTest``, you can pass them using + ``@pytest.mark.parametrize``. Note that you have to also provide + `all Patcher arguments <http://jmcgeheeiv.github.io/pyfakefs/master/modules.html#pyfakefs.fake_filesystem_unittest.Patcher>`__ + before the needed ones, as keyword arguments cannot be used, and you have to + add ``indirect=True`` as argument. + Alternatively, you can add your own fixture with the needed parameters. + + Examples for the first approach can be found below, and in + `pytest_fixture_param_test.py <https://github.com/jmcgeheeiv/pyfakefs/blob/master/pyfakefs/pytest_tests/pytest_fixture_param_test.py>`__. + The second approach is shown in + `pytest_fixture_test.py <https://github.com/jmcgeheeiv/pyfakefs/blob/master/pyfakefs/pytest_tests/pytest_fixture_test.py>`__ + with the example fixture in `conftest.py <https://github.com/jmcgeheeiv/pyfakefs/blob/master/pyfakefs/pytest_tests/conftest.py>`__. + We advice to use this example fixture code as a template for your customized + pytest plugins. + +modules_to_reload +~~~~~~~~~~~~~~~~~ +Pyfakefs patches modules that are imported before starting the test by +finding and replacing file system modules in all loaded modules at test +initialization time. +This allows to automatically patch file system related modules that are: + +- imported directly, for example: + +.. code:: python + + import os + import pathlib.Path + +- imported as another name: + +.. code:: python + + import os as my_os + +- imported using one of these two specially handled statements: + +.. code:: python + + from os import path + from pathlib import Path + +Additionally, functions from file system related modules are patched +automatically if imported like: + +.. code:: python + + from os.path import exists + from os import stat + +This also works if importing the functions as another name: + +.. code:: python + + from os.path import exists as my_exists + from io import open as io_open + from builtins import open as bltn_open + +Initializing a default argument with a file system function is also patched +automatically: + +.. code:: python + + import os + + def check_if_exists(filepath, file_exists=os.path.exists): + return file_exists(filepath) + +There are a few cases where automatic patching does not work. We know of at +least one specific case where this is the case: + +If initializing a global variable using a file system function, the +initialization will be done using the real file system: + +.. code:: python + + from pathlib import Path + + path = Path("/example_home") + +In this case, ``path`` will hold the real file system path inside the test. + +To get these cases to work as expected under test, the respective modules +containing the code shall be added to the ``modules_to_reload`` argument (a +module list). +The passed modules will be reloaded, thus allowing pyfakefs to patch them +dynamically. All modules loaded after the initial patching described above +will be patched using this second mechanism. + +Given that the example code shown above is located in the file +``example/sut.py``, the following code will work: + +.. code:: python + + import example + + # example using unittest + class ReloadModuleTest(fake_filesystem_unittest.TestCase): + def setUp(self): + self.setUpPyfakefs(modules_to_reload=[example.sut]) + + def test_path_exists(self): + file_path = '/foo/bar' + self.fs.create_dir(file_path) + self.assertTrue(example.sut.check_if_exists(file_path)) + + # example using pytest + @pytest.mark.parametrize('fs', [[None, [example.sut]]], indirect=True) + def test_path_exists(fs): + file_path = '/foo/bar' + fs.create_dir(file_path) + assert example.sut.check_if_exists(file_path) + + # example using Patcher + def test_path_exists(): + with Patcher(modules_to_reload=[example.sut]) as patcher: + file_path = '/foo/bar' + patcher.fs.create_dir(file_path) + assert example.sut.check_if_exists(file_path) + + # example using patchfs decorator + @patchfs(modules_to_reload=[example.sut]) + def test_path_exists(fs): + file_path = '/foo/bar' + fs.create_dir(file_path) + assert example.sut.check_if_exists(file_path) + + +modules_to_patch +~~~~~~~~~~~~~~~~ +Sometimes there are file system modules in other packages that are not +patched in standard pyfakefs. To allow patching such modules, +``modules_to_patch`` can be used by adding a fake module implementation for +a module name. The argument is a dictionary of fake modules mapped to the +names to be faked. + +This mechanism is used in pyfakefs itself to patch the external modules +`pathlib2` and `scandir` if present, and the following example shows how to +fake a module in Django that uses OS file system functions: + +.. code:: python + + class FakeLocks: + """django.core.files.locks uses low level OS functions, fake it.""" + _locks_module = django.core.files.locks + + def __init__(self, fs): + """Each fake module expects the fake file system as an __init__ + parameter.""" + # fs represents the fake filesystem; for a real example, it can be + # saved here and used in the implementation + pass + + @staticmethod + def lock(f, flags): + return True + + @staticmethod + def unlock(f): + return True + + def __getattr__(self, name): + return getattr(self._locks_module, name) + + ... + # test code using Patcher + with Patcher(modules_to_patch={'django.core.files.locks': FakeLocks}): + test_django_stuff() + + # test code using unittest + class TestUsingDjango(fake_filesystem_unittest.TestCase): + def setUp(self): + self.setUpPyfakefs(modules_to_patch={'django.core.files.locks': FakeLocks}) + + def test_django_stuff(self) + ... + + # test code using pytest + @pytest.mark.parametrize('fs', [[None, None, + {'django.core.files.locks': FakeLocks}]], indirect=True) + def test_django_stuff(fs): + ... + + # test code using patchfs decorator + @patchfs(modules_to_patch={'django.core.files.locks': FakeLocks}) + def test_django_stuff(fs): + ... + +additional_skip_names +~~~~~~~~~~~~~~~~~~~~~ +This may be used to add modules that shall not be patched. This is mostly +used to avoid patching the Python file system modules themselves, but may be +helpful in some special situations, for example if a testrunner needs to access +the file system after test setup. To make this possible, the affected module +can be added to ``additional_skip_names``: + +.. code:: python + + with Patcher(additional_skip_names=['pydevd']) as patcher: + patcher.fs.create_file('foo') + +Alternatively to the module names, the modules themselves may be used: + +.. code:: python + + import pydevd + + with Patcher(additional_skip_names=[pydevd]) as patcher: + patcher.fs.create_file('foo') + +There is also the global variable ``Patcher.SKIPNAMES`` that can be extended +for that purpose, though this seldom shall be needed (except for own pytest +plugins, as shown in the example mentioned above). + +allow_root_user +~~~~~~~~~~~~~~~ +This is ``True`` by default, meaning that the user is considered a root user +if the real user is a root user (e.g. has the user ID 0). If you want to run +your tests as a non-root user regardless of the actual user rights, you may +want to set this to ``False``. + +Using convenience methods +------------------------- +While ``pyfakefs`` can be used just with the standard Python file system +functions, there are few convenience methods in ``fake_filesystem`` that can +help you setting up your tests. The methods can be accessed via the +``fake_filesystem`` instance in your tests: ``Patcher.fs``, the ``fs`` +fixture in PyTest, or ``TestCase.fs``. + +File creation helpers +~~~~~~~~~~~~~~~~~~~~~ +To create files, directories or symlinks together with all the directories +in the path, you may use ``create_file()``, ``create_dir()`` and +``create_symlink()``, respectively. + +``create_file()`` also allows you to set the file mode and the file contents +together with the encoding if needed. Alternatively, you can define a file +size without contents - in this case, you will not be able to perform +standard I\O operations on the file (may be used to "fill up" the file system +with large files). + +.. code:: python + + from pyfakefs.fake_filesystem_unittest import TestCase + + class ExampleTestCase(TestCase): + def setUp(self): + self.setUpPyfakefs() + + def test_create_file(self): + file_path = '/foo/bar/test.txt' + self.fs.create_file(file_path, contents = 'test') + with open(file_path) as f: + self.assertEqual('test', f.read()) + +``create_dir()`` behaves like ``os.makedirs()``. + +Access to files in the real file system +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you want to have read access to real files or directories, you can map +them into the fake file system using ``add_real_file()``, +``add_real_directory()``, ``add_real_symlink()`` and ``add_real_paths()``. +They take a file path, a directory path, a symlink path, or a list of paths, +respectively, and make them accessible from the fake file system. By +default, the contents of the mapped files and directories are read only on +demand, so that mapping them is relatively cheap. The access to the files is +by default read-only, but even if you add them using ``read_only=False``, +the files are written only in the fake system (e.g. in memory). The real +files are never changed. + +``add_real_file()``, ``add_real_directory()`` and ``add_real_symlink()`` also +allow you to map a file or a directory tree into another location in the +fake filesystem via the argument ``target_path``. + +.. code:: python + + from pyfakefs.fake_filesystem_unittest import TestCase + + class ExampleTestCase(TestCase): + + fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') + def setUp(self): + self.setUpPyfakefs() + # make the file accessible in the fake file system + self.fs.add_real_directory(self.fixture_path) + + def test_using_fixture1(self): + with open(os.path.join(self.fixture_path, 'fixture1.txt') as f: + # file contents are copied to the fake file system + # only at this point + contents = f.read() + +You can do the same using ``pytest`` by using a fixture for test setup: + +.. code:: python + + import pytest + import os + + fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') + + @pytest.fixture + def my_fs(fs): + fs.add_real_directory(fixture_path) + yield fs + + def test_using_fixture1(my_fs): + with open(os.path.join(fixture_path, 'fixture1.txt') as f: + contents = f.read() + +When using ``pytest`` another option is to load the contents of the real file +in a fixture and pass this fixture to the test function **before** passing +the ``fs`` fixture. + +.. code:: python + + import pytest + import os + + @pytest.fixture + def content(): + fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') + with open(os.path.join(fixture_path, 'fixture1.txt') as f: + contents = f.read() + return contents + + def test_using_file_contents(content, fs): + fs.create_file("fake/path.txt") + assert content != "" + + +Handling mount points +~~~~~~~~~~~~~~~~~~~~~ +Under Linux and MacOS, the root path (``/``) is the only mount point created +in the fake file system. If you need support for more mount points, you can add +them using ``add_mount_point()``. + +Under Windows, drives and UNC paths are internally handled as mount points. +Adding a file or directory on another drive or UNC path automatically +adds a mount point for that drive or UNC path root if needed. Explicitly +adding mount points shall not be needed under Windows. + +A mount point has a separate device ID (``st_dev``) under all systems, and +some operations (like ``rename``) are not possible for files located on +different mount points. The fake file system size (if used) is also set per +mount point. + +Setting the file system size +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you need to know the file system size in your tests (for example for +testing cleanup scripts), you can set the fake file system size using +``set_disk_usage()``. By default, this sets the total size in bytes of the +root partition; if you add a path as parameter, the size will be related to +the mount point (see above) the path is related to. + +By default, the size of the fake file system is considered infinite. As soon +as you set a size, all files will occupy the space according to their size, +and you may fail to create new files if the fake file system is full. + +.. code:: python + + from pyfakefs.fake_filesystem_unittest import TestCase + + class ExampleTestCase(TestCase): + + def setUp(self): + self.setUpPyfakefs() + self.fs.set_disk_usage(100) + + def test_disk_full(self): + with open('/foo/bar.txt', 'w') as f: + self.assertRaises(OSError, f.write, 'a' * 200) + +To get the file system size, you may use ``get_disk_usage()``, which is +modeled after ``shutil.disk_usage()``. + +Pausing patching +~~~~~~~~~~~~~~~~ +Sometimes, you may want to access the real filesystem inside the test with +no patching applied. This can be achieved by using the ``pause/resume`` +functions, which exist in ``fake_filesystem_unittest.Patcher``, +``fake_filesystem_unittest.TestCase`` and ``fake_filesystem.FakeFilesystem``. +There is also a context manager class ``fake_filesystem_unittest.Pause`` +which encapsulates the calls to ``pause()`` and ``resume()``. + +Here is an example that tests the usage with the pyfakefs pytest fixture: + +.. code:: python + + from pyfakefs.fake_filesystem_unittest import Pause + + def test_pause_resume_contextmanager(fs): + fake_temp_file = tempfile.NamedTemporaryFile() + assert os.path.exists(fake_temp_file.name) + fs.pause() + assert not os.path.exists(fake_temp_file.name) + real_temp_file = tempfile.NamedTemporaryFile() + 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) + +Here is the same code using a context manager: + +.. code:: python + + from pyfakefs.fake_filesystem_unittest import Pause + + def test_pause_resume_contextmanager(fs): + fake_temp_file = tempfile.NamedTemporaryFile() + assert os.path.exists(fake_temp_file.name) + with Pause(fs): + assert not os.path.exists(fake_temp_file.name) + real_temp_file = tempfile.NamedTemporaryFile() + 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) + +Troubleshooting +--------------- + +Modules not working with pyfakefs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Modules may not work with ``pyfakefs`` for several reasons. ``pyfakefs`` +works by patching some file system related modules and functions, specifically: + +- most file system related functions in the ``os`` and ``os.path`` modules +- the ``pathlib`` module +- the build-in ``open`` function and ``io.open`` +- ``shutil.disk_usage`` + +Other file system related modules work with ``pyfakefs``, because they use +exclusively these patched functions, specifically ``shutil`` (except for +``disk_usage``), ``tempfile``, ``glob`` and ``zipfile``. + +A module may not work with ``pyfakefs`` because of one of the following +reasons: + +- It uses a file system related function of the mentioned modules that is + not or not correctly patched. Mostly these are functions that are seldom + used, but may be used in Python libraries (this has happened for example + with a changed implementation of ``shutil`` in Python 3.7). Generally, + these shall be handled in issues and we are happy to fix them. +- It uses file system related functions in a way that will not be patched + automatically. This is the case for functions that are executed while + reading a module. This case and a possibility to make them work is + documented above under ``modules_to_reload``. +- It uses OS specific file system functions not contained in the Python + libraries. These will not work out of the box, and we generally will not + support them in ``pyfakefs``. If these functions are used in isolated + functions or classes, they may be patched by using the ``modules_to_patch`` + parameter (see the example for file locks in Django above), and if there + are more examples for patches that may be useful, we may add them in the + documentation. +- It uses C libraries to access the file system. There is no way no make + such a module work with ``pyfakefs`` - if you want to use it, you have to + patch the whole module. In some cases, a library implemented in Python with + a similar interface already exists. An example is ``lxml``, + which can be substituted with ``ElementTree`` in most cases for testing. + +A list of Python modules that are known to not work correctly with +``pyfakefs`` will be collected here: + +- ``multiprocessing`` has several issues (related to points 1 and 3 above). + Currently there are no plans to fix this, but this may change in case of + sufficient demand. + +If you are not sure if a module can be handled, or how to do it, you can +always write a new issue, of course! + +OS temporary directories +~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests relying on a completely empty file system on test start will fail. +As ``pyfakefs`` does not fake the ``tempfile`` module (as described above), +a temporary directory is required to ensure ``tempfile`` works correctly, +e.g., that ``tempfile.gettempdir()`` will return a valid value. This +means that any newly created fake file system will always have either a +directory named ``/tmp`` when running on Linux or Unix systems, +``/var/folders/<hash>/T`` when running on MacOs and +``C:\Users\<user>\AppData\Local\Temp`` on Windows. + +User rights +~~~~~~~~~~~ + +If you run pyfakefs tests as root (this happens by default if run in a +docker container), pyfakefs also behaves as a root user, for example can +write to write-protected files. This may not be the expected behavior, and +can be changed. +Pyfakefs has a rudimentary concept of user rights, which differentiates +between root user (with the user id 0) and any other user. By default, +pyfakefs assumes the user id of the current user, but you can change +that using ``fake_filesystem.set_uid()`` in your setup. This allows to run +tests as non-root user in a root user environment and vice verse. +Another possibility is the convenience argument ``allow_root_user`` +described above. diff --git a/extra_requirements.txt b/extra_requirements.txt new file mode 100644 index 0000000..9352552 --- /dev/null +++ b/extra_requirements.txt @@ -0,0 +1,13 @@ +# "pathlib2" and "scandir" are backports of new standard modules, pyfakefs will +# use them if available when running on older Python versions. +# +# They are dependencies of pytest when Python < 3.6 so we sometimes get them via +# requirements.txt, this file makes them explicit dependencies for testing & +# development. +# +# Older versions might work ok, the versions chosen here are just the latest +# available at the time of writing. + +pathlib2>=2.3.2 + +scandir>=1.8 diff --git a/pyfakefs/__init__.py b/pyfakefs/__init__.py new file mode 100755 index 0000000..e69de29 --- /dev/null +++ b/pyfakefs/__init__.py diff --git a/pyfakefs/deprecator.py b/pyfakefs/deprecator.py new file mode 100644 index 0000000..25a5caa --- /dev/null +++ b/pyfakefs/deprecator.py @@ -0,0 +1,69 @@ +# 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. +"""Utilities for handling deprecated functions.""" + +import functools +import warnings + + +class Deprecator(object): + """Decorator class for adding deprecated functions. + + Warnings are switched on by default. + To disable deprecation warnings, use: + + >>> from pyfakefs.deprecator import Deprecator + >>> + >>> Deprecator.show_warnings = False + """ + + show_warnings = True + + def __init__(self, use_instead=None, func_name=None): + self.use_instead = use_instead + self.func_name = func_name + + def __call__(self, func): + """Decorator to mark functions as deprecated. Emit warning + when the function is used.""" + + @functools.wraps(func) + def _new_func(*args, **kwargs): + if self.show_warnings: + warnings.simplefilter('always', DeprecationWarning) + message = '' + if self.use_instead is not None: + message = 'Use {} instead.'.format(self.use_instead) + warnings.warn('Call to deprecated function {}. {}'.format( + self.func_name or func.__name__, message), + category=DeprecationWarning, stacklevel=2) + warnings.simplefilter('default', DeprecationWarning) + return func(*args, **kwargs) + + return _new_func + + @staticmethod + def add(clss, func, deprecated_name): + """Add the deprecated version of a member function to the given class. + Gives a deprecation warning on usage. + + Args: + clss: the class where the deprecated function is to be added + func: the actual function that is called by the deprecated version + deprecated_name: the deprecated name of the function + """ + + @Deprecator(func.__name__, deprecated_name) + def _old_function(*args, **kwargs): + return func(*args, **kwargs) + + setattr(clss, deprecated_name, _old_function) diff --git a/pyfakefs/extra_packages.py b/pyfakefs/extra_packages.py new file mode 100644 index 0000000..ae84c74 --- /dev/null +++ b/pyfakefs/extra_packages.py @@ -0,0 +1,44 @@ +# 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. + +"""Imports external packages that replace or emulate internal packages. +If the external module is not present, the build-in module is imported. +""" + +try: + import pathlib2 + + pathlib = pathlib2 +except ImportError: + pathlib2 = None + + try: + import pathlib + except ImportError: + pathlib = None + +try: + import scandir + + use_scandir_package = True + use_builtin_scandir = False +except ImportError: + try: + from os import scandir # noqa: F401 + + use_builtin_scandir = True + use_scandir_package = False + except ImportError: + use_builtin_scandir = False + use_scandir_package = False + +use_scandir = use_scandir_package or use_builtin_scandir diff --git a/pyfakefs/fake_filesystem.py b/pyfakefs/fake_filesystem.py new file mode 100644 index 0000000..45fa0fb --- /dev/null +++ b/pyfakefs/fake_filesystem.py @@ -0,0 +1,5111 @@ +# 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. + +:Includes: + * :py:class:`FakeFile`: Provides the appearance of a real file. + * :py:class:`FakeDirectory`: Provides the appearance of a real directory. + * :py:class:`FakeFilesystem`: Provides the appearance of a real directory + hierarchy. + * :py:class:`FakeOsModule`: Uses :py:class:`FakeFilesystem` to provide a + fake :py:mod:`os` module replacement. + * :py:class:`FakeIoModule`: Uses :py:class:`FakeFilesystem` to provide a + fake ``io`` module replacement. + * :py:class:`FakePathModule`: Faked ``os.path`` module replacement. + * :py:class:`FakeFileOpen`: Faked ``file()`` and ``open()`` function + replacements. + +:Usage: + +>>> from pyfakefs import fake_filesystem +>>> filesystem = fake_filesystem.FakeFilesystem() +>>> os_module = fake_filesystem.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 errno +import heapq +import io +import locale +import os +import sys +import time +import uuid +from collections import namedtuple +from stat import ( + S_IFREG, S_IFDIR, S_ISLNK, S_IFMT, S_ISDIR, S_IFLNK, S_ISREG, S_IFSOCK +) + +from pyfakefs.deprecator import Deprecator +from pyfakefs.extra_packages import use_scandir +from pyfakefs.fake_scandir import scandir, walk +from pyfakefs.helpers import ( + FakeStatResult, FileBufferIO, NullFileBufferIO, + is_int_type, is_byte_string, is_unicode_string, + make_string_path, IS_WIN, to_string) + +__pychecker__ = 'no-reimportself' + +__version__ = '4.1dev' + +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. + +_OpenModes = namedtuple( + 'open_modes', + 'must_exist can_read can_write truncate append must_not_exist' +) + +_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) +} + +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 + +NR_STD_STREAMS = 3 +USER_ID = 1 if IS_WIN else os.getuid() +GROUP_ID = 1 if IS_WIN else os.getgid() + + +def set_uid(uid): + """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 set_gid(gid): + """Set the global group id. This is only used to set st_gid for new files, + no permision 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(): + """Set the global user ID and group ID back to default values.""" + set_uid(1 if IS_WIN else os.getuid()) + set_gid(1 if IS_WIN else os.getgid()) + + +def is_root(): + """Return True if the current user is the root user.""" + return USER_ID == 0 + + +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): + super(FakeLargeFileIoException, self).__init__( + 'Read and write operations not supported for ' + 'fake large file: %s' % file_path) + + +def _copy_module(old): + """Recompiles and creates new module object.""" + saved = sys.modules.pop(old.__name__, None) + new = __import__(old.__name__) + sys.modules[old.__name__] = saved + return new + + +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, st_mode=S_IFREG | PERM_DEF_FILE, + contents=None, filesystem=None, encoding=None, errors=None, + side_effect=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) + contents: The contents of the filesystem object; should be a string + or byte object for regular files, and a list of other + FakeFile or FakeDirectory objects 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. + """ + # to be backwards compatible regarding argument order, we raise on None + if filesystem is None: + raise ValueError('filesystem shall not be None') + self.filesystem = filesystem + self._side_effect = side_effect + self.name = name + self.stat_result = FakeStatResult( + filesystem.is_windows_fs, USER_ID, GROUP_ID, time.time()) + self.stat_result.st_mode = st_mode + self.encoding = encoding + self.errors = errors or 'strict' + self._byte_contents = self._encode_contents(contents) + self.stat_result.st_size = ( + len(self._byte_contents) if self._byte_contents is not None else 0) + self.epoch = 0 + self.parent_dir = None + # Linux specific: extended file system attributes + self.xattr = {} + + @property + def byte_contents(self): + """Return the contents as raw byte array.""" + return self._byte_contents + + @property + def contents(self): + """Return the contents as string with the original encoding.""" + if isinstance(self.byte_contents, bytes): + return self.byte_contents.decode( + self.encoding or locale.getpreferredencoding(False), + errors=self.errors) + return self.byte_contents + + @property + def st_ctime(self): + """Return the creation time of the fake file.""" + return self.stat_result.st_ctime + + @property + def st_atime(self): + """Return the access time of the fake file.""" + return self.stat_result.st_atime + + @property + def st_mtime(self): + """Return the modification time of the fake file.""" + return self.stat_result.st_mtime + + @st_ctime.setter + def st_ctime(self, val): + """Set the creation time of the fake file.""" + self.stat_result.st_ctime = val + + @st_atime.setter + def st_atime(self, val): + """Set the access time of the fake file.""" + self.stat_result.st_atime = val + + @st_mtime.setter + def st_mtime(self, val): + """Set the modification time of the fake file.""" + self.stat_result.st_mtime = val + + def set_large_file_size(self, st_size): + """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): + # 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): + """Return `True` if this file was initialized with size but no contents. + """ + return self._byte_contents is None + + def _encode_contents(self, contents): + if is_unicode_string(contents): + contents = bytes( + contents, + self.encoding or locale.getpreferredencoding(False), + self.errors) + return contents + + def _set_initial_contents(self, contents): + """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 + """ + contents = self._encode_contents(contents) + changed = self._byte_contents != contents + st_size = len(contents) + + if self._byte_contents: + self.size = 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 = contents + self.st_size = st_size + self.epoch += 1 + return changed + + def set_contents(self, contents, encoding=None): + """Sets the file contents and size and increases the modification time. + Also executes the side_effects if available. + + Args: + contents: (str, bytes, unicode) 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. + + Raises: + OSError: if `st_size` is not a non-negative integer, + or if it exceeds the available file system space. + """ + self.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): + """Return the size in bytes of the file contents. + """ + return self.st_size + + @property + def path(self): + """Return the full path of the current object.""" + names = [] + obj = self + while obj: + names.insert(0, obj.name) + obj = obj.parent_dir + sep = self.filesystem._path_separator(self.name) + if names[0] == sep: + names.pop(0) + dir_path = sep.join(names) + # Windows paths with drive have a root separator entry + # which should be removed + is_drive = names and len(names[0]) == 2 and names[0][1] == ':' + if not is_drive: + dir_path = sep + dir_path + else: + dir_path = sep.join(names) + dir_path = self.filesystem.absnormpath(dir_path) + return dir_path + + @Deprecator('property path') + def GetPath(self): + return self.path + + @Deprecator('property size') + def GetSize(self): + return self.size + + @size.setter + def size(self, st_size): + """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 + + @Deprecator('property size') + def SetSize(self, value): + self.size = value + + @Deprecator('property st_atime') + def SetATime(self, st_atime): + """Set the self.st_atime attribute. + + Args: + st_atime: The desired access time. + """ + self.st_atime = st_atime + + @Deprecator('property st_mtime') + def SetMTime(self, st_mtime): + """Set the self.st_mtime attribute. + + Args: + st_mtime: The desired modification time. + """ + self.st_mtime = st_mtime + + @Deprecator('property st_ctime') + def SetCTime(self, st_ctime): + """Set the self.st_ctime attribute. + + Args: + st_ctime: The desired creation time. + """ + self.st_ctime = st_ctime + + def __getattr__(self, item): + """Forward some properties to stat_result.""" + if item in self.stat_types: + return getattr(self.stat_result, item) + return super(FakeFile, self).__getattr__(item) + + def __setattr__(self, key, value): + """Forward some properties to stat_result.""" + if key in self.stat_types: + return setattr(self.stat_result, key, value) + return super(FakeFile, self).__setattr__(key, value) + + def __str__(self): + return '%s(%o)' % (self.name, self.st_mode) + + @Deprecator('st_ino') + def SetIno(self, st_ino): + """Set the self.st_ino attribute. + Note that a unique inode is assigned automatically to a new fake file. + This function does not guarantee uniqueness and should be used with + caution. + + Args: + st_ino: (int) The desired inode. + """ + self.st_ino = st_ino + + +class FakeNullFile(FakeFile): + def __init__(self, filesystem): + devnull = '/dev/nul' if filesystem.is_windows_fs else '/dev/nul' + super(FakeNullFile, self).__init__( + devnull, filesystem=filesystem, contents=b'') + + @property + def byte_contents(self): + return b'' + + def _set_initial_contents(self, contents): + pass + + +Deprecator.add(FakeFile, FakeFile.set_large_file_size, 'SetLargeFileSize') +Deprecator.add(FakeFile, FakeFile.set_contents, 'SetContents') +Deprecator.add(FakeFile, FakeFile.is_large_file, 'IsLargeFile') + + +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, filesystem, side_effect=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(FakeFileFromRealFile, self).__init__( + name=os.path.basename(file_path), filesystem=filesystem, + side_effect=side_effect) + self.contents_read = False + + @property + def byte_contents(self): + 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(FakeFileFromRealFile, self).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, perm_bits=PERM_DEF, filesystem=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, + # inclusing '.' and '..' + self.st_nlink += 1 + + def set_contents(self, contents, encoding=None): + raise self.filesystem.raise_os_error(errno.EISDIR, self.path) + + @property + def contents(self): + """Return the list of contained directory entries.""" + return self.byte_contents + + @property + def ordered_dirs(self): + """Return the list of contained directory entry names ordered by + creation order. + """ + return [item[0] for item in sorted( + self.byte_contents.items(), key=lambda entry: entry[1].st_ino)] + + def add_entry(self, path_object): + """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 is_root() and not self.st_mode & PERM_WRITE and + not self.filesystem.is_windows_fs): + raise OSError(errno.EACCES, 'Permission Denied', self.path) + + path_object_name = to_string(path_object.name) + if path_object_name in self.contents: + self.filesystem.raise_os_error(errno.EEXIST, self.path) + + self.contents[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): + """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.contents[to_string(pathname_name)] + + def _normalized_entryname(self, pathname_name): + if not self.filesystem.is_case_sensitive: + matching_names = [name for name in self.contents + if name.lower() == pathname_name.lower()] + if matching_names: + pathname_name = matching_names[0] + return pathname_name + + def remove_entry(self, pathname_name, recursive=True): + """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 entry.st_mode & PERM_WRITE == 0: + self.filesystem.raise_os_error(errno.EACCES, pathname_name) + if self.filesystem.has_open_file(entry): + self.filesystem.raise_os_error(errno.EACCES, pathname_name) + else: + if (not is_root() and (self.st_mode & (PERM_WRITE | PERM_EXE) != + PERM_WRITE | PERM_EXE)): + self.filesystem.raise_os_error(errno.EACCES, pathname_name) + + if recursive and isinstance(entry, FakeDirectory): + while entry.contents: + entry.remove_entry(list(entry.contents)[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.contents[to_string(pathname_name)] + + @property + def size(self): + """Return the total size of all files contained in this directory tree. + """ + return sum([item[1].size for item in self.contents.items()]) + + @Deprecator('property size') + def GetSize(self): + return self.size + + def has_parent_object(self, dir_object): + """Return `True` if dir_object is a direct or indirect parent + directory, or if both are the same object.""" + obj = self + while obj: + if obj == dir_object: + return True + obj = obj.parent_dir + return False + + def __str__(self): + description = super(FakeDirectory, self).__str__() + ':\n' + for item in self.contents: + item_desc = self.contents[item].__str__() + for line in item_desc.split('\n'): + if line: + description = description + ' ' + line + '\n' + return description + + +Deprecator.add(FakeDirectory, FakeDirectory.add_entry, 'AddEntry') +Deprecator.add(FakeDirectory, FakeDirectory.get_entry, 'GetEntry') +Deprecator.add(FakeDirectory, FakeDirectory.set_contents, 'SetContents') +Deprecator.add(FakeDirectory, FakeDirectory.remove_entry, 'RemoveEntry') + + +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, filesystem, read_only, + target_path=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(FakeDirectoryFromRealDirectory, self).__init__( + name=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 + self.read_only = read_only + self.contents_read = False + + @property + def contents(self): + """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) + 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.byte_contents + + @property + def size(self): + # we cannot get the size until the contents are loaded + if not self.contents_read: + return 0 + return super(FakeDirectoryFromRealDirectory, self).size + + +class FakeFilesystem: + """Provides the appearance of a real directory tree for unit testing. + + Attributes: + path_separator: The path separator, corresponds to `os.path.sep`. + alternative_path_separator: Corresponds to `os.path.altsep`. + is_windows_fs: `True` in a real or faked Windows file system. + is_macos: `True` under MacOS, or if we are faking it. + is_case_sensitive: `True` if a case-sensitive file system is assumed. + root: The root :py:class:`FakeDirectory` entry of the file system. + cwd: The current working directory path. + 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. + """ + + def __init__(self, path_separator=os.path.sep, total_size=None, + patcher=None): + """ + Args: + path_separator: optional substitute for os.path.sep + total_size: if not None, the total size in bytes of the + root filesystem. + + Example usage to use the same path separator under all systems: + + >>> filesystem = FakeFilesystem(path_separator='/') + + """ + self.path_separator = path_separator + self.alternative_path_separator = os.path.altsep + self.patcher = patcher + if path_separator != os.sep: + self.alternative_path_separator = None + + # 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 = not (self.is_windows_fs or self.is_macos) + + self.root = FakeDirectory(self.path_separator, filesystem=self) + self.cwd = self.root.name + + # We can't query the current value without changing it: + self.umask = 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 = [] + # A heap containing all free positions in self.open_files list + self._free_fd_heap = [] + # last used numbers for inodes (st_ino) and devices (st_dev) + self.last_ino = 0 + self.last_dev = 0 + self.mount_points = {} + self.add_mount_point(self.root.name, total_size) + self._add_standard_streams() + self.dev_null = FakeNullFile(self) + + @property + def is_linux(self): + return not self.is_windows_fs and not self.is_macos + + def reset(self, total_size=None): + """Remove all file system contents and reset the root.""" + self.root = FakeDirectory(self.path_separator, filesystem=self) + self.cwd = self.root.name + + self.open_files = [] + self._free_fd_heap = [] + self.last_ino = 0 + self.last_dev = 0 + self.mount_points = {} + self.add_mount_point(self.root.name, total_size) + self._add_standard_streams() + + def pause(self): + """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. + 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): + """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. + 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 line_separator(self): + return '\r\n' if self.is_windows_fs else '\n' + + def _error_message(self, errno): + return os.strerror(errno) + ' in the fake filesystem' + + def raise_os_error(self, errno, filename=None, winerror=None): + """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: + errno: 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 = self._error_message(errno) + if (winerror is not None and sys.platform == 'win32' and + self.is_windows_fs): + raise OSError(errno, message, filename, winerror) + raise OSError(errno, message, filename) + + @staticmethod + def _matching_string(matched, string): + """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(locale.getpreferredencoding(False)) + return string + + def _path_separator(self, path): + """Return the path separator as the same type as path""" + return self._matching_string(path, self.path_separator) + + def _alternative_path_separator(self, path): + """Return the alternative path separator as the same type as path""" + return self._matching_string(path, self.alternative_path_separator) + + def add_mount_point(self, path, total_size=None): + """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. + + Returns: + The newly created mount point dict. + + Raises: + OSError: if trying to mount an existing mount point again. + """ + path = self.absnormpath(path) + if path in self.mount_points: + 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 + } + # special handling for root path: has been created before + if path == self.root.name: + root_dir = self.root + self.last_ino += 1 + root_dir.st_ino = self.last_ino + else: + root_dir = self.create_dir(path) + root_dir.st_dev = self.last_dev + return self.mount_points[path] + + def _auto_mount_drive_if_needed(self, path, force=False): + if (self.is_windows_fs and + (force or not self._mount_point_for_path(path))): + drive = self.splitdrive(path)[0] + if drive: + return self.add_mount_point(path=drive) + + def _mount_point_for_path(self, path): + def to_str(string): + """Convert the str, unicode or byte object to a str + using the default encoding.""" + if string is None or isinstance(string, str): + return string + return string.decode(locale.getpreferredencoding(False)) + + path = self.absnormpath(self._original_path(path)) + if path in self.mount_points: + return self.mount_points[path] + mount_path = self._matching_string(path, '') + drive = self.splitdrive(path)[:1] + for root_path in self.mount_points: + root_path = self._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_str(mount_path)] + mount_point = self._auto_mount_drive_if_needed(path, force=True) + assert mount_point + return mount_point + + def _mount_point_for_device(self, idev): + for mount_point in self.mount_points.values(): + if mount_point['idev'] == idev: + return mount_point + + def get_disk_usage(self, path=None): + """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('usage', 'total, used, free') + if path is None: + mount_point = self.mount_points[self.root.name] + else: + mount_point = self._mount_point_for_path(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, path=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. + """ + if path is None: + path = self.root.name + mount_point = self._mount_point_for_path(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, file_path, st_dev): + """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, follow_symlinks=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 + file_object = self.resolve( + entry_path, follow_symlinks, + allow_fd=True, check_read_perm=False) + 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) + + 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, + file_object, + follow_symlinks=True, + macos_handling=False): + 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, mode, follow_symlinks=True): + """Change the permissions of a file as encoded in integer mode. + + Args: + path: (str) Path to the file. + mode: (int) Permissions. + follow_symlinks: If `False` and `path` points to a symlink, + the link itself is affected instead of the linked object. + """ + file_object = self.resolve(path, follow_symlinks, allow_fd=True) + if self.is_windows_fs: + if mode & 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 & ~PERM_ALL) | + (mode & PERM_ALL)) + file_object.st_ctime = time.time() + + def utime(self, path, times=None, *, ns=None, follow_symlinks=True): + """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 = time.time() + file_object.st_atime = current_time + file_object.st_mtime = current_time + + def _handle_utime_arg_errors(self, ns, times): + 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") + + @Deprecator + def SetIno(self, path, st_ino): + """Set the self.st_ino attribute of file at 'path'. + Note that a unique inode is assigned automatically to a new fake file. + Using this function does not guarantee uniqueness and should used + with caution. + + Args: + path: Path to file. + st_ino: The desired inode. + """ + self.get_object(path).st_ino = st_ino + + def _add_open_file(self, file_obj): + """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. + + Returns: + File descriptor number for the file object. + """ + 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): + """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): + """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. + """ + if not is_int_type(file_des): + raise TypeError('an integer is required') + if (file_des >= len(self.open_files) or + self.open_files[file_des] is None): + self.raise_os_error(errno.EBADF, str(file_des)) + return self.open_files[file_des][0] + + def has_open_file(self, file_object): + """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): + if self.alternative_path_separator is None or not path: + return path + return path.replace(self._alternative_path_separator(path), + self._path_separator(path)) + + def normcase(self, path): + """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. + """ + path = make_string_path(path) + return self._normalize_path_sep(path) + + def normpath(self, path): + """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 = self.normcase(path) + drive, path = self.splitdrive(path) + sep = self._path_separator(path) + is_absolute_path = path.startswith(sep) + path_components = path.split(sep) + collapsed_path_components = [] + dot = self._matching_string(path, '.') + dotdot = self._matching_string(path, '..') + 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): + """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( + path_components[len(normalized_components):]) + sep = self._path_separator(path) + normalized_path = sep.join(normalized_components) + if path.startswith(sep) and not normalized_path.startswith(sep): + normalized_path = sep + normalized_path + return normalized_path + + if self.is_case_sensitive or not path: + return 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, current_dir = self._directory_content( + current_dir, component) + if current_dir is None or ( + isinstance(current_dir, FakeDirectory) and + current_dir._byte_contents is None and + current_dir.st_size == 0): + return components_to_path() + normalized_components.append(dir_name) + return components_to_path() + + def absnormpath(self, path): + """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 = self._matching_string(path, self.cwd) + if not path: + path = self.path_separator + if path == self._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 = self._matching_string(path, self.root.name) + empty = self._matching_string(path, '') + path = self._path_separator(path).join( + (cwd != root_name and cwd or empty, path)) + if path == self._matching_string(path, '.'): + path = cwd + return self.normpath(path) + + def splitpath(self, path): + """Mimic os.path.splitpath using the specified path_separator. + + Mimics os.path.splitpath 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 = self.normcase(path) + sep = self._path_separator(path) + path_components = path.split(sep) + if not path_components: + return ('', '') + + starts_with_drive = self._starts_with_drive_letter(path) + basename = path_components.pop() + colon = self._matching_string(path, ':') + if not path_components: + if starts_with_drive: + components = basename.split(colon) + return (components[0] + colon, components[1]) + return ('', basename) + for component in path_components: + if component: + # The path is not the root; it contains a non-separator + # component. Strip all trailing separators. + while not path_components[-1]: + path_components.pop() + if starts_with_drive: + if not path_components: + components = basename.split(colon) + return (components[0] + colon, components[1]) + if (len(path_components) == 1 and + path_components[0].endswith(colon)): + return (path_components[0] + sep, basename) + return (sep.join(path_components), basename) + # Root path. Collapse all leading separators. + return (sep, basename) + + def splitdrive(self, path): + """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 = make_string_path(path) + if self.is_windows_fs: + if len(path) >= 2: + path = self.normcase(path) + sep = self._path_separator(path) + # UNC path handling + if (path[0:2] == sep * 2) and ( + path[2:3] != sep): + # UNC path handling - splits off the mount point + # instead of the drive + sep_index = path.find(sep, 2) + if sep_index == -1: + return path[:0], path + sep_index2 = path.find(sep, sep_index + 1) + if sep_index2 == sep_index + 1: + return path[:0], path + if sep_index2 == -1: + sep_index2 = len(path) + return path[:sep_index2], path[sep_index2:] + if path[1:2] == self._matching_string(path, ':'): + return path[:2], path[2:] + return path[:0], path + + def _join_paths_with_drive_support(self, *all_paths): + """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._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 = self._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): + """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. + """ + if sys.version_info >= (3, 6): + paths = [os.fspath(path) for path in paths] + if len(paths) == 1: + return paths[0] + if self.is_windows_fs: + return self._join_paths_with_drive_support(*paths) + joined_path_segments = [] + sep = self._path_separator(paths[0]) + for path_segment in paths: + if self._starts_with_root_path(path_segment): + # An absolute path + joined_path_segments = [path_segment] + else: + if (joined_path_segments and + not joined_path_segments[-1].endswith(sep)): + joined_path_segments.append(sep) + if path_segment: + joined_path_segments.append(path_segment) + return self._matching_string(paths[0], '').join(joined_path_segments) + + def _path_components(self, path): + """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.contents: + 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._path_separator(path): + return [] + drive, path = self.splitdrive(path) + path_components = path.split(self._path_separator(path)) + 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): + """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 = self._matching_string(file_path, ':') + return (self.is_windows_fs and len(file_path) >= 2 and + file_path[:1].isalpha and (file_path[1:2]) == colon) + + def _starts_with_root_path(self, file_path): + root_name = self._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 _is_root_path(self, file_path): + root_name = self._matching_string(file_path, self.root.name) + return (file_path == root_name or not self.is_case_sensitive and + file_path.lower() == root_name.lower() or + 2 <= len(file_path) <= 3 and + self._starts_with_drive_letter(file_path)) + + def ends_with_path_separator(self, file_path): + """Return True if ``file_path`` ends with a valid path separator.""" + if is_int_type(file_path): + return False + file_path = make_string_path(file_path) + return (file_path and + file_path not in (self.path_separator, + self.alternative_path_separator) and + (file_path.endswith(self._path_separator(file_path)) or + self.alternative_path_separator is not None and + file_path.endswith( + self._alternative_path_separator(file_path)))) + + def is_filepath_ending_with_separator(self, path): + if not self.ends_with_path_separator(path): + return False + return self.isfile(self._path_without_trailing_separators(path)) + + def _directory_content(self, directory, component): + if not isinstance(directory, FakeDirectory): + return None, None + if component in directory.contents: + return component, directory.contents[component] + if not self.is_case_sensitive: + matching_content = [(subdir, directory.contents[subdir]) for + subdir in directory.contents + if subdir.lower() == component.lower()] + if matching_content: + return matching_content[0] + + return None, None + + def exists(self, file_path, check_link=False): + """Return true if a path points to an existing file system object. + + Args: + file_path: The path to examine. + + 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 + file_path = make_string_path(file_path) + if file_path is None: + raise TypeError + if not file_path: + return False + if file_path == self.dev_null.name: + return not self.is_windows_fs or sys.version_info >= (3, 8) + try: + if self.is_filepath_ending_with_separator(file_path): + return False + file_path = self.resolve_path(file_path) + except OSError: + return False + if file_path == self.root.name: + return True + + path_components = self._path_components(file_path) + current_dir = self.root + for component in path_components: + current_dir = self._directory_content(current_dir, component)[1] + if not current_dir: + return False + return True + + def resolve_path(self, file_path, allow_fd=False, raw_io=True): + """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. + raw_io: `True` if called from low-level I/O functions. + + Returns: + The resolved_path (string) or None. + + 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 + file_path = make_string_path(file_path) + if file_path is None: + # file.open(None) raises TypeError, so mimic that. + raise TypeError('Expected file system path string, received None') + if not file_path or not self._valid_relative_path(file_path): + # file.open('') raises OSError, so mimic that, and validate that + # all parts of a relative path exist. + self.raise_os_error(errno.ENOENT, file_path) + file_path = self.absnormpath(self._original_path(file_path)) + if self._is_root_path(file_path): + return file_path + if file_path == self.dev_null.name: + return file_path + path_components = self._path_components(file_path) + resolved_components = self._resolve_components(path_components, raw_io) + return self._components_to_path(resolved_components) + + def _components_to_path(self, component_folders): + sep = (self._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, path_components, raw_io): + current_dir = self.root + link_depth = 0 + resolved_components = [] + while path_components: + component = path_components.pop(0) + resolved_components.append(component) + current_dir = self._directory_content(current_dir, component)[1] + if current_dir 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. + if S_ISLNK(current_dir.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, current_dir) + + # 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 + return resolved_components + + def _valid_relative_path(self, file_path): + if self.is_windows_fs: + return True + slash_dotdot = self._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, link): + """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 + # ignore UNC prefix for local files + if self.is_windows_fs and link_path.startswith('\\\\?\\'): + link_path = link_path[4:] + sep = self._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) + + def get_object_from_normpath(self, file_path, check_read_perm=True): + """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 + + Returns: + The FakeFile object corresponding to file_path. + + Raises: + OSError: if the object is not found. + """ + file_path = make_string_path(file_path) + if file_path == self.root.name: + return self.root + if file_path == self.dev_null.name: + return self.dev_null + + file_path = self._original_path(file_path) + path_components = self._path_components(file_path) + target_object = self.root + try: + for component in path_components: + if S_ISLNK(target_object.st_mode): + target_object = self.resolve(target_object.contents) + if not S_ISDIR(target_object.st_mode): + if not self.is_windows_fs: + self.raise_os_error(errno.ENOTDIR, file_path) + self.raise_os_error(errno.ENOENT, file_path) + target_object = target_object.get_entry(component) + if (not is_root() and check_read_perm and target_object and + not target_object.st_mode & PERM_READ): + self.raise_os_error(errno.EACCES, target_object.path) + except KeyError: + self.raise_os_error(errno.ENOENT, file_path) + return target_object + + def get_object(self, file_path, check_read_perm=True): + """Search for the specified filesystem object within the fake + filesystem. + + Args: + file_path: Specifies the target FakeFile object to retrieve. + check_read_perm: If True, raises OSError if a parent directory + does not have read permission + + Returns: + The FakeFile object corresponding to `file_path`. + + Raises: + OSError: if the object is not found. + """ + file_path = make_string_path(file_path) + file_path = self.absnormpath(self._original_path(file_path)) + return self.get_object_from_normpath(file_path, check_read_perm) + + def resolve(self, file_path, follow_symlinks=True, allow_fd=False, + check_read_perm=True): + """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 + + Returns: + The FakeFile object corresponding to `file_path`. + + Raises: + OSError: if the object is not found. + """ + if isinstance(file_path, int): + if allow_fd: + return self.get_open_file(file_path).get_object() + raise TypeError('path should be string, bytes or ' + 'os.PathLike (if supported), not int') + + if follow_symlinks: + file_path = make_string_path(file_path) + return self.get_object_from_normpath(self.resolve_path( + file_path, check_read_perm), check_read_perm) + return self.lresolve(file_path) + + def lresolve(self, path): + """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 = make_string_path(path) + if not path: + raise OSError(errno.ENOENT, path) + if path == self.root.name: + # The root directory will never be a link + return self.root + + # remove trailing separator + path = self._path_without_trailing_separators(path) + if path == self._matching_string(path, '.'): + path = self.cwd + path = self._original_path(path) + + parent_directory, child_name = self.splitpath(path) + if not parent_directory: + parent_directory = 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) + self.raise_os_error(errno.ENOENT, path) + if not parent_obj.st_mode & PERM_READ: + self.raise_os_error(errno.EACCES, parent_directory) + return (parent_obj.get_entry(child_name) if child_name + else parent_obj) + except KeyError: + self.raise_os_error(errno.ENOENT, path) + + def add_object(self, file_path, file_object): + """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 + else: + target_directory = self.resolve(file_path) + 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, new_file_path, force_replace=False): + """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). + """ + ends_with_sep = self.ends_with_path_separator(old_file_path) + old_file_path = self.absnormpath(old_file_path) + new_file_path = self.absnormpath(new_file_path) + if not self.exists(old_file_path, check_link=True): + self.raise_os_error(errno.ENOENT, old_file_path, 2) + if ends_with_sep: + self._handle_broken_link_with_trailing_sep(old_file_path) + + old_object = self.lresolve(old_file_path) + if not self.is_windows_fs: + self._handle_posix_dir_link_errors( + new_file_path, old_file_path, ends_with_sep) + + if self.exists(new_file_path, check_link=True): + new_file_path = self._rename_to_existing_path( + force_replace, new_file_path, old_file_path, + old_object, ends_with_sep) + + if not new_file_path: + return + + old_dir, old_name = self.splitpath(old_file_path) + new_dir, new_name = self.splitpath(new_file_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_file_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_file_path) + if new_dir_object.has_parent_object(old_object): + self.raise_os_error(errno.EINVAL, new_file_path) + + 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) + if new_name in new_dir_object.contents: + # in case of overwriting remove the old entry first + new_dir_object.remove_entry(new_name) + new_dir_object.add_entry(object_to_rename) + + def _handle_broken_link_with_trailing_sep(self, path): + # 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, old_file_path, + ends_with_sep): + 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, new_file_path, + old_file_path, old_object, ends_with_sep): + 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 # Nothing to do here. + + if old_object == new_object: + new_file_path = self._rename_same_object( + new_file_path, old_file_path) + elif (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, + new_file_path, new_object, + old_object, ends_with_sep): + 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.contents: + 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, old_file_path): + 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 + new_file_path = None + return new_file_path + + def remove_object(self, file_path): + """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): + path = make_string_path(path) + os_sep = self._matching_string(path, os.sep) + fake_sep = self._matching_string(path, self.path_separator) + return path.replace(os_sep, fake_sep) + + def create_dir(self, directory_path, perm_bits=PERM_DEF): + """Create `directory_path`, and all the parent directories. + + 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`. + + Returns: + The newly created FakeDirectory object. + + Raises: + OSError: if the directory already exists. + """ + directory_path = self.make_string_path(directory_path) + directory_path = self.absnormpath(directory_path) + self._auto_mount_drive_if_needed(directory_path) + if self.exists(directory_path, check_link=True): + self.raise_os_error(errno.EEXIST, directory_path) + path_components = self._path_components(directory_path) + current_dir = self.root + + new_dirs = [] + for component in path_components: + directory = self._directory_content(current_dir, 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: + if S_ISLNK(directory.st_mode): + directory = self.resolve(directory.contents) + current_dir = 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: + new_dir.st_mode = S_IFDIR | perm_bits + + return current_dir + + def create_file(self, file_path, st_mode=S_IFREG | PERM_DEF_FILE, + contents='', st_size=None, create_missing_dirs=True, + apply_umask=False, encoding=None, errors=None, + side_effect=None): + """Create `file_path`, including all the parent directories along + the way. + + 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: `True` if 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. + side_effect: function handle that is executed when file is written, + must accept the file object as an argument. + + Returns: + The newly created 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, read_only=True, target_path=None): + """Create `file_path`, including all the parent directories along the + way, for an existing real file. 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 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 = make_string_path(source_path) + target_path = self.make_string_path(target_path) + real_stat = os.stat(source_path) + 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 + self.change_disk_usage(fake_file.size, fake_file.name, + fake_file.st_dev) + return fake_file + + def add_real_symlink(self, source_path, target_path=None): + """Create a symlink at source_path (or target_path, if given). 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 + fileystem, otherwise, the same as `source_path`. + + Returns: + the newly created FakeDirectory 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 = self._path_without_trailing_separators(source_path) + if not os.path.exists(source_path) and not os.path.islink(source_path): + self.raise_os_error(errno.ENOENT, source_path) + + target = os.readlink(source_path) + + if target_path: + return self.create_symlink(target_path, target) + else: + return self.create_symlink(source_path, target) + + def add_real_directory(self, source_path, read_only=True, lazy_read=True, + target_path=None): + """Create a fake directory corresponding to the real directory at the + specified path. Add entries in the fake directory corresponding to + the entries in the real directory. Symlinks are supported. + + 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 FakeDirectory object. + + Raises: + OSError: if the directory does not exist in the real file system. + OSError: if the directory already exists in the fake file system. + """ + source_path = self._path_without_trailing_separators(source_path) + if not os.path.exists(source_path): + self.raise_os_error(errno.ENOENT, source_path) + target_path = target_path or source_path + if lazy_read: + parent_path = os.path.split(target_path)[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, self, read_only, target_path) + parent_dir.add_entry(new_dir) + else: + new_dir = self.create_dir(target_path) + for base, _, files in os.walk(source_path): + new_base = os.path.join(new_dir.path, + os.path.relpath(base, source_path)) + for fileEntry in os.listdir(base): + abs_fileEntry = os.path.join(base, fileEntry) + + if not os.path.islink(abs_fileEntry): + continue + + self.add_real_symlink( + abs_fileEntry, os.path.join(new_base, fileEntry)) + for fileEntry in files: + path = os.path.join(base, fileEntry) + if os.path.islink(path): + continue + self.add_real_file(path, + read_only, + os.path.join(new_base, fileEntry)) + return new_dir + + def add_real_paths(self, path_list, read_only=True, lazy_dir_read=True): + """This convenience method adds multiple files and/or directories from + the real file system to the fake file system. See `add_real_file()` and + `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 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 `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 any of the files and directories in the list + already exists in the fake file system. + """ + 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, + st_mode=S_IFREG | PERM_DEF_FILE, + contents='', st_size=None, + create_missing_dirs=True, + apply_umask=False, encoding=None, errors=None, + read_from_real_fs=False, raw_io=False, + side_effect=None): + """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. + raw_io: `True` if called from low-level API (`os.open`) + side_effect: function handle that is executed when file is written, + must accept the file object as an argument. + """ + file_path = self.make_string_path(file_path) + file_path = self.absnormpath(file_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(file_path, check_link=True): + self.raise_os_error(errno.EEXIST, file_path) + parent_directory, new_file = self.splitpath(file_path) + if not parent_directory: + parent_directory = 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) + self.create_dir(parent_directory) + else: + parent_directory = self._original_path(parent_directory) + if apply_umask: + st_mode &= ~self.umask + if read_from_real_fs: + file_object = FakeFileFromRealFile(file_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) + except OSError: + self.remove_object(file_path) + raise + + return file_object + + # pylint: disable=unused-argument + def create_symlink(self, file_path, link_target, create_missing_dirs=True): + """Create the specified symlink, pointed at the specified link target. + + 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 FakeFile object. + + Raises: + OSError: if the symlink could not be created + (see :py:meth:`create_file`). + """ + # the link path cannot end with a path separator + file_path = self.make_string_path(file_path) + link_target = self.make_string_path(link_target) + file_path = self.normcase(file_path) + if self.ends_with_path_separator(file_path): + if self.exists(file_path): + self.raise_os_error(errno.EEXIST, file_path) + if self.exists(link_target): + if not self.is_windows_fs: + self.raise_os_error(errno.ENOENT, file_path) + else: + if self.is_windows_fs: + self.raise_os_error(errno.EINVAL, link_target) + if not self.exists( + self._path_without_trailing_separators(file_path), + check_link=True): + self.raise_os_error(errno.ENOENT, link_target) + if self.is_macos: + # to avoid EEXIST exception, remove the link + # if it already exists + if self.exists(file_path, check_link=True): + self.remove_object(file_path) + else: + self.raise_os_error(errno.EEXIST, link_target) + + # resolve the link path only if it is not a link itself + if not self.islink(file_path): + file_path = self.resolve_path(file_path) + link_target = make_string_path(link_target) + return self.create_file_internally( + file_path, st_mode=S_IFLNK | PERM_DEF, + contents=link_target, + create_missing_dirs=create_missing_dirs, + raw_io=True) + + def link(self, old_path, new_path, follow_symlinks=True): + """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. + """ + new_path_normalized = self.absnormpath(new_path) + if self.exists(new_path_normalized, check_link=True): + self.raise_os_error(errno.EEXIST, new_path) + + new_parent_directory, new_basename = self.splitpath( + new_path_normalized) + if not new_parent_directory: + new_parent_directory = self.cwd + + if not self.exists(new_parent_directory): + self.raise_os_error(errno.ENOENT, new_parent_directory) + + if self.ends_with_path_separator(old_path): + error = errno.EINVAL if self.is_windows_fs else errno.ENOTDIR + self.raise_os_error(error, old_path) + + if not self.is_windows_fs and self.ends_with_path_separator(new_path): + self.raise_os_error(errno.ENOENT, old_path) + + # Retrieve the target file + try: + old_file = self.resolve(old_path, follow_symlinks=follow_symlinks) + except OSError: + self.raise_os_error(errno.ENOENT, old_path) + + if old_file.st_mode & S_IFDIR: + self.raise_os_error( + errno.EACCES if self.is_windows_fs else errno.EPERM, old_path) + + # abuse the name field to control the filename of the + # newly created link + old_file.name = new_basename + self.add_object(new_parent_directory, old_file) + return old_file + + def _is_circular_link(self, link_obj): + try: + self.resolve_path(link_obj.contents) + except OSError as exc: + return exc.errno == errno.ELOOP + return False + + def readlink(self, path): + """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_obj = self.lresolve(path) + if S_IFMT(link_obj.st_mode) != S_IFLNK: + self.raise_os_error(errno.EINVAL, path) + + if self.ends_with_path_separator(path): + if not self.is_windows_fs and self.exists(path): + self.raise_os_error(errno.EINVAL, path) + if not self.exists(link_obj.path): + if self.is_windows_fs: + error = errno.EINVAL + elif self._is_circular_link(link_obj): + if self.is_macos: + return link_obj.path + error = errno.ELOOP + else: + error = errno.ENOENT + self.raise_os_error(error, link_obj.path) + + return link_obj.contents + + def makedir(self, dir_name, mode=PERM_DEF): + """Create a leaf Fake directory. + + Args: + dir_name: (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_name) + 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, _ = self.splitpath(dir_name) + if parent_dir: + base_dir = self.normpath(parent_dir) + ellipsis = self._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 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.path_separator: + 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( + head, FakeDirectory(tail, mode & ~self.umask, filesystem=self)) + + def _path_without_trailing_separators(self, path): + while self.ends_with_path_separator(path): + path = path[:-1] + return path + + def makedirs(self, dir_name, mode=PERM_DEF, exist_ok=False): + """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, '') + dir_name = to_string(dir_name) + 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) + + path_components = self._path_components(dir_name) + + # Raise a permission denied error if thioe first existing directory + # is not writeable. + current_dir = self.root + for component in path_components: + if (component not in current_dir.contents + or not isinstance(current_dir.contents, dict)): + break + else: + current_dir = current_dir.contents[component] + try: + self.create_dir(dir_name, mode & ~self.umask) + 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 + self.raise_os_error(e.errno, e.filename) + + def _is_of_type(self, path, st_flag, follow_symlinks=True): + """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 + + Returns: + (boolean) `True` if the st_flag is set in path's st_mode. + + Raises: + TypeError: if path is None + """ + path = make_string_path(path) + if path is None: + raise TypeError + try: + obj = self.resolve(path, follow_symlinks) + if obj: + self.raise_for_filepath_ending_with_separator( + 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, follow_symlinks=True): + """Determine if path identifies a directory. + + Args: + path: Path to filesystem 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, follow_symlinks=True): + """Determine if path identifies a regular file. + + Args: + path: Path to filesystem 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): + """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) + + def confirmdir(self, target_directory): + """Test that the target is actually a directory, raising OSError + if not. + + Args: + target_directory: Path to the target directory within the fake + filesystem. + + Returns: + The FakeDirectory object corresponding to target_directory. + + Raises: + OSError: if the target is not a directory. + """ + directory = self.resolve(target_directory) + if not directory.st_mode & S_IFDIR: + self.raise_os_error(errno.ENOTDIR, target_directory, 267) + return directory + + def remove(self, path): + """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 = self.absnormpath(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) + + norm_path = make_string_path(norm_path) + if path.endswith(self.path_separator): + 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, allow_symlink=False): + """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 in (b'.', u'.'): + 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): + 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) + if dir_object.contents: + self.raise_os_error(errno.ENOTEMPTY, target_directory) + self.remove_object(target_directory) + + def listdir(self, target_directory): + """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. + + Raises: + OSError: if the target is not a directory. + """ + target_directory = self.resolve_path(target_directory, allow_fd=True) + directory = self.confirmdir(target_directory) + directory_contents = directory.contents + return list(directory_contents.keys()) + + def __str__(self): + return str(self.root) + + def _add_standard_streams(self): + self._add_open_file(StandardStreamWrapper(sys.stdin)) + self._add_open_file(StandardStreamWrapper(sys.stdout)) + self._add_open_file(StandardStreamWrapper(sys.stderr)) + + +Deprecator.add(FakeFilesystem, FakeFilesystem.get_disk_usage, 'GetDiskUsage') +Deprecator.add(FakeFilesystem, FakeFilesystem.set_disk_usage, 'SetDiskUsage') +Deprecator.add(FakeFilesystem, + FakeFilesystem.change_disk_usage, 'ChangeDiskUsage') +Deprecator.add(FakeFilesystem, FakeFilesystem.add_mount_point, 'AddMountPoint') +Deprecator.add(FakeFilesystem, FakeFilesystem.stat, 'GetStat') +Deprecator.add(FakeFilesystem, FakeFilesystem.chmod, 'ChangeMode') +Deprecator.add(FakeFilesystem, FakeFilesystem.utime, 'UpdateTime') +Deprecator.add(FakeFilesystem, FakeFilesystem._add_open_file, 'AddOpenFile') +Deprecator.add(FakeFilesystem, + FakeFilesystem._close_open_file, 'CloseOpenFile') +Deprecator.add(FakeFilesystem, FakeFilesystem.has_open_file, 'HasOpenFile') +Deprecator.add(FakeFilesystem, FakeFilesystem.get_open_file, 'GetOpenFile') +Deprecator.add(FakeFilesystem, + FakeFilesystem.normcase, 'NormalizePathSeparator') +Deprecator.add(FakeFilesystem, FakeFilesystem.normpath, 'CollapsePath') +Deprecator.add(FakeFilesystem, FakeFilesystem._original_path, 'NormalizeCase') +Deprecator.add(FakeFilesystem, FakeFilesystem.absnormpath, 'NormalizePath') +Deprecator.add(FakeFilesystem, FakeFilesystem.splitpath, 'SplitPath') +Deprecator.add(FakeFilesystem, FakeFilesystem.splitdrive, 'SplitDrive') +Deprecator.add(FakeFilesystem, FakeFilesystem.joinpaths, 'JoinPaths') +Deprecator.add(FakeFilesystem, + FakeFilesystem._path_components, 'GetPathComponents') +Deprecator.add(FakeFilesystem, FakeFilesystem._starts_with_drive_letter, + 'StartsWithDriveLetter') +Deprecator.add(FakeFilesystem, FakeFilesystem.exists, 'Exists') +Deprecator.add(FakeFilesystem, FakeFilesystem.resolve_path, 'ResolvePath') +Deprecator.add(FakeFilesystem, FakeFilesystem.get_object_from_normpath, + 'GetObjectFromNormalizedPath') +Deprecator.add(FakeFilesystem, FakeFilesystem.get_object, 'GetObject') +Deprecator.add(FakeFilesystem, FakeFilesystem.resolve, 'ResolveObject') +Deprecator.add(FakeFilesystem, FakeFilesystem.lresolve, 'LResolveObject') +Deprecator.add(FakeFilesystem, FakeFilesystem.add_object, 'AddObject') +Deprecator.add(FakeFilesystem, FakeFilesystem.remove_object, 'RemoveObject') +Deprecator.add(FakeFilesystem, FakeFilesystem.rename, 'RenameObject') +Deprecator.add(FakeFilesystem, FakeFilesystem.create_dir, 'CreateDirectory') +Deprecator.add(FakeFilesystem, FakeFilesystem.create_file, 'CreateFile') +Deprecator.add(FakeFilesystem, FakeFilesystem.create_symlink, 'CreateLink') +Deprecator.add(FakeFilesystem, FakeFilesystem.link, 'CreateHardLink') +Deprecator.add(FakeFilesystem, FakeFilesystem.readlink, 'ReadLink') +Deprecator.add(FakeFilesystem, FakeFilesystem.makedir, 'MakeDirectory') +Deprecator.add(FakeFilesystem, FakeFilesystem.makedirs, 'MakeDirectories') +Deprecator.add(FakeFilesystem, FakeFilesystem.isdir, 'IsDir') +Deprecator.add(FakeFilesystem, FakeFilesystem.isfile, 'IsFile') +Deprecator.add(FakeFilesystem, FakeFilesystem.islink, 'IsLink') +Deprecator.add(FakeFilesystem, FakeFilesystem.confirmdir, 'ConfirmDir') +Deprecator.add(FakeFilesystem, FakeFilesystem.remove, 'RemoveFile') +Deprecator.add(FakeFilesystem, FakeFilesystem.rmdir, 'RemoveDirectory') +Deprecator.add(FakeFilesystem, FakeFilesystem.listdir, 'ListDir') + + +class FakePathModule: + """Faked os.path module replacement. + + FakePathModule should *only* be instantiated by FakeOsModule. See the + FakeOsModule docstring for details. + """ + _OS_PATH_COPY = _copy_module(os.path) + + @staticmethod + def dir(): + """Return the list of patched function names. Used for patching + functions imported from the module. + """ + return [ + 'abspath', 'dirname', 'exists', 'expanduser', 'getatime', + 'getctime', 'getmtime', 'getsize', 'isabs', 'isdir', 'isfile', + 'islink', 'ismount', 'join', 'lexists', 'normcase', 'normpath', + 'realpath', 'relpath', 'split', 'splitdrive', 'samefile' + ] + + def __init__(self, filesystem, os_module): + """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 + self.sep = self.filesystem.path_separator + self.altsep = self.filesystem.alternative_path_separator + + def exists(self, path): + """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): + """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): + """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): + """Return True if path is an absolute pathname.""" + if self.filesystem.is_windows_fs: + path = self.splitdrive(path)[1] + path = make_string_path(path) + sep = self.filesystem._path_separator(path) + altsep = self.filesystem._alternative_path_separator(path) + if self.filesystem.is_windows_fs: + return len(path) > 0 and path[:1] in (sep, altsep) + else: + return (path.startswith(sep) or + altsep is not None and path.startswith(altsep)) + + def isdir(self, path): + """Determine if path identifies a directory.""" + return self.filesystem.isdir(path) + + def isfile(self, path): + """Determine if path identifies a regular file.""" + return self.filesystem.isfile(path) + + def islink(self, path): + """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) + + def getmtime(self, path): + """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) + + def getatime(self, path): + """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 + + def getctime(self, path): + """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 + + def abspath(self, path): + """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) + sep = self.filesystem._path_separator(path) + altsep = self.filesystem._alternative_path_separator(path) + if not self.isabs(path): + path = self.join(getcwd(), path) + elif (self.filesystem.is_windows_fs and + path.startswith(sep) or altsep is not None and + path.startswith(altsep)): + cwd = getcwd() + if self.filesystem._starts_with_drive_letter(cwd): + path = self.join(cwd[:2], path) + return self.normpath(path) + + def join(self, *p): + """Return the completed path with a separator of the parts.""" + return self.filesystem.joinpaths(*p) + + def split(self, path): + """Split the path into the directory and the filename of the path. + """ + return self.filesystem.splitpath(path) + + def splitdrive(self, path): + """Split the path into the drive part and the rest of the path, if + supported.""" + return self.filesystem.splitdrive(path) + + def normpath(self, path): + """Normalize path, eliminating double slashes, etc.""" + return self.filesystem.normpath(path) + + def normcase(self, path): + """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, start=None): + """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) + if start is not None: + start = make_string_path(start) + else: + start = self.filesystem.cwd + if self.filesystem.alternative_path_separator is not None: + path = path.replace(self.filesystem.alternative_path_separator, + self._os_path.sep) + start = start.replace(self.filesystem.alternative_path_separator, + self._os_path.sep) + path = path.replace(self.filesystem.path_separator, self._os_path.sep) + start = start.replace( + self.filesystem.path_separator, self._os_path.sep) + path = self._os_path.relpath(path, start) + return path.replace(self._os_path.sep, self.filesystem.path_separator) + + def realpath(self, filename): + """Return the canonical path of the specified filename, eliminating any + symbolic links encountered in the path. + """ + if self.filesystem.is_windows_fs: + return self.abspath(filename) + filename = make_string_path(filename) + path, ok = self._joinrealpath(filename[:0], filename, {}) + return self.abspath(path) + + def samefile(self, path1, path2): + """Return whether path1 and path2 point to the same file. + + Args: + path1: first file path or path object (Python >=3.6) + path2: second file path or path object (Python >=3.6) + + 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) + + def _joinrealpath(self, path, rest, seen): + """Join two paths, normalizing and eliminating any symbolic links + encountered in the second path. + Taken from Python source and adapted. + """ + curdir = self.filesystem._matching_string(path, '.') + pardir = self.filesystem._matching_string(path, '..') + + sep = self.filesystem._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 + path = seen[newpath] + if path is not None: + # use cached value + 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._joinrealpath( + 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): + """Returns the first part of the result of `split()`.""" + return self.split(path)[0] + + def expanduser(self, path): + """Return the argument with an initial component of ~ or ~user + replaced by that user's home directory. + """ + return self._os_path.expanduser(path).replace( + self._os_path.sep, self.sep) + + def ismount(self, path): + """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). + """ + path = make_string_path(path) + if not path: + return False + normed_path = self.filesystem.absnormpath(path) + sep = self.filesystem._path_separator(path) + if self.filesystem.is_windows_fs: + if self.filesystem.alternative_path_separator is not None: + path_seps = ( + sep, self.filesystem._alternative_path_separator(path) + ) + 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 normed_path.rstrip(sep) == mount_point.rstrip(sep): + return True + return False + + def __getattr__(self, name): + """Forwards any non-faked calls to the real os.path.""" + return getattr(self._os_path, name) + + +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_filesystem.FakeOsModule(filesystem) + """ + + devnull = None + + @staticmethod + def dir(): + """Return the list of patched function names. Used for patching + functions imported from the module. + """ + dir = [ + 'access', 'chdir', 'chmod', 'chown', 'close', 'fstat', 'fsync', + 'getcwd', 'lchmod', 'link', 'listdir', 'lstat', 'makedirs', + 'mkdir', 'mknod', 'open', 'read', 'readlink', 'remove', + 'removedirs', 'rename', 'rmdir', 'stat', 'symlink', 'umask', + 'unlink', 'utime', 'walk', 'write', 'getcwdb', 'replace' + ] + if sys.platform.startswith('linux'): + dir += [ + 'fdatasync', 'getxattr', 'listxattr', + 'removexattr', 'setxattr' + ] + if use_scandir: + dir += ['scandir'] + return dir + + def __init__(self, filesystem): + """Also exposes self.path (to fake os.path). + + Args: + filesystem: FakeFilesystem used to provide file system information + """ + self.filesystem = filesystem + self.sep = filesystem.path_separator + self.altsep = filesystem.alternative_path_separator + self.linesep = filesystem.line_separator() + self._os_module = os + self.path = FakePathModule(self.filesystem, self) + self.__class__.devnull = ('/dev/nul' if filesystem.is_windows_fs + else '/dev/nul') + + def fdopen(self, fd, *args, **kwargs): + """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): + """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; + # as 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, flags, mode=None, *, dir_fd=None): + """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_tmpfile_flag = (hasattr(os, 'O_TMPFILE') and + flags & getattr(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, 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) + if fake_file.file_object != self.filesystem.dev_null: + self.chmod(path, mode) + return fake_file.fileno() + + def close(self, fd): + """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() + + def read(self, fd, n): + """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) + file_handle.raw_io = True + return file_handle.read(n) + + def write(self, fd, contents): + """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 = 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 pipe(self): + read_fd, write_fd = os.pipe() + read_wrapper = FakePipeWrapper(self.filesystem, read_fd) + file_des = self.filesystem._add_open_file(read_wrapper) + read_wrapper.filedes = file_des + write_wrapper = FakePipeWrapper(self.filesystem, write_fd) + file_des = self.filesystem._add_open_file(write_wrapper) + write_wrapper.filedes = file_des + return read_wrapper.filedes, write_wrapper.filedes + + @staticmethod + def stat_float_times(newvalue=None): + """Determine whether a file's time stamps are reported as floats + or ints. + + Calling without arguments returns the current value. The value is + shared by all instances of FakeOsModule. + + Args: + newvalue: If `True`, mtime, ctime, atime are reported as floats. + Otherwise, they are returned as ints (rounding down). + """ + return FakeStatResult.stat_float_times(newvalue) + + def fstat(self, fd): + """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() + return file_object.stat_result.copy() + + def umask(self, mask): + """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): + """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. + """ + path = self.filesystem.resolve_path( + path, allow_fd=True) + self.filesystem.confirmdir(path) + directory = self.filesystem.resolve(path) + # A full implementation would check permissions all the way + # up the tree. + if not is_root() and not directory.st_mode | PERM_EXE: + self.filesystem.raise_os_error(errno.EACCES, directory) + self.filesystem.cwd = path + + def getcwd(self): + """Return current working directory.""" + return self.filesystem.cwd + + def getcwdb(self): + """Return current working directory as bytes.""" + return bytes( + self.filesystem.cwd, locale.getpreferredencoding(False)) + + def listdir(self, path): + """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, attribute, *, follow_symlinks=True): + """Return the value of the given extended filesystem attribute for + `path`. + + Args: + path: File path, file descriptor or path-like object (for + Python >= 3.6). + 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) + return file_obj.xattr.get(attribute) + + def listxattr(self, path=None, *, follow_symlinks=True): + """Return a list of the extended filesystem attributes on `path`. + + Args: + path: File path, file descriptor or path-like object (for + Python >= 3.6). 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'") + + if path is None: + path = self.getcwd() + file_obj = self.filesystem.resolve(path, follow_symlinks, + allow_fd=True) + return list(file_obj.xattr.keys()) + + def removexattr(self, path, attribute, *, follow_symlinks=True): + """Removes the extended filesystem attribute attribute from `path`. + + Args: + path: File path, file descriptor or path-like object (for + Python >= 3.6). + 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, attribute, value, + flags=0, *, follow_symlinks=True): + """Sets the value of the given extended filesystem attribute for + `path`. + + Args: + path: File path, file descriptor or path-like object (for + Python >= 3.6). + 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 + + if use_scandir: + 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. + """ + return scandir(self.filesystem, path) + + def walk(self, top, topdown=True, onerror=None, followlinks=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, dir_fd=None): + """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, *, dir_fd=None, follow_symlinks=True): + """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, *, dir_fd=None): + """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) + return self.filesystem.stat(path, follow_symlinks=False) + + def remove(self, path, dir_fd=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) + self.filesystem.remove(path) + + def unlink(self, path, *, dir_fd=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, dst, *, src_dir_fd=None, dst_dir_fd=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 replace(self, src, dst, *, src_dir_fd=None, dst_dir_fd=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) + dst = self._path_with_dir_fd(dst, self.rename, dst_dir_fd) + self.filesystem.rename(src, dst, force_replace=True) + + def rmdir(self, path, *, dir_fd=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): + """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.contents: + 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.contents: + 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, mode=PERM_DEF, *, dir_fd=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, mode=PERM_DEF, exist_ok=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 + self.filesystem.makedirs(name, mode, exist_ok) + + def _path_with_dir_fd(self, path, fct, dir_fd): + """Return the path considering dir_fd. Raise on invalid parameters.""" + path = to_string(path) + if dir_fd is not None: + # check if fd is supported for the built-in real function + real_fct = getattr(os, fct.__name__) + if real_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" % fct.__name__) + if not self.path.isabs(path): + return self.path.join( + self.filesystem.get_open_file( + dir_fd).get_object().path, path) + return path + + def access(self, path, mode, *, dir_fd=None, follow_symlinks=True): + """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. + 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. + """ + 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(): + mode &= ~os.W_OK + return (mode & ((stat_result.st_mode >> 6) & 7)) == mode + + def chmod(self, path, mode, *, dir_fd=None, follow_symlinks=True): + """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. + """ + path = self._path_with_dir_fd(path, self.chmod, dir_fd) + self.filesystem.chmod(path, mode, follow_symlinks) + + def lchmod(self, path, mode): + """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, times=None, ns=None, + dir_fd=None, follow_symlinks=True): + """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, uid, gid, *, dir_fd=None, follow_symlinks=True): + """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 ((is_int_type(uid) or uid is None) and + (is_int_type(gid) or gid is None)): + 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, mode=None, device=0, *, dir_fd=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 (b'.', u'.', b'..', u'..'): + 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, dst, *, dir_fd=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. + dir_fd: If not `None`, the file descriptor of a directory, + with `src` being relative to this directory. + + Raises: + OSError: if the file already exists. + """ + src = self._path_with_dir_fd(src, self.symlink, dir_fd) + self.filesystem.create_symlink( + dst, src, create_missing_dirs=False) + + def link(self, src, dst, *, src_dir_fd=None, dst_dir_fd=None): + """Create a hard link at new_path, pointing at old_path. + + 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. + + Returns: + The FakeFile object referred to by `src`. + + Raises: + OSError: if something already exists at new_path. + OSError: if the parent directory doesn't exist. + """ + 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) + + def fsync(self, fd): + """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 = 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): + """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, fd_in, offset, count): + """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 = self.filesystem.get_open_file(fd_in) + dest = 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 __getattr__(self, name): + """Forwards any unfaked calls to the standard os module.""" + return getattr(self._os_module, name) + + +class FakeIoModule: + """Uses FakeFilesystem to provide a fake io module replacement. + + Currently only used to wrap `io.open()` which is an alias to `open()`. + + You need a fake_filesystem to use this: + filesystem = fake_filesystem.FakeFilesystem() + my_io_module = fake_filesystem.FakeIoModule(filesystem) + """ + + @staticmethod + def dir(): + """Return the list of patched function names. Used for patching + functions imported from the module. + """ + return 'open', + + def __init__(self, filesystem): + """ + Args: + filesystem: FakeFilesystem used to provide file system information. + """ + self.filesystem = filesystem + self._io_module = io + + def open(self, file, mode='r', buffering=-1, encoding=None, + errors=None, newline=None, closefd=True, opener=None): + """Redirect the call to FakeFileOpen. + See FakeFileOpen.call() for description. + """ + fake_open = FakeFileOpen(self.filesystem) + return fake_open(file, mode, buffering, encoding, errors, + newline, closefd, opener) + + def __getattr__(self, name): + """Forwards any unfaked calls to the standard io module.""" + return getattr(self._io_module, name) + + +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, file_path, update=False, read=False, + append=False, delete_on_close=False, filesystem=None, + newline=None, binary=True, closefd=True, encoding=None, + errors=None, raw_io=False, is_stream=False): + self.file_object = file_object + self.file_path = file_path + 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.is_stream = is_stream + self._changed = False + contents = file_object.byte_contents + self._encoding = encoding or locale.getpreferredencoding(False) + errors = errors or 'strict' + buffer_class = (NullFileBufferIO if file_object == filesystem.dev_null + else FileBufferIO) + self._io = buffer_class(contents, linesep=filesystem.line_separator(), + binary=binary, 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 = None + + def __enter__(self): + """To support usage of this fake file with the 'with' statement.""" + return self + + def __exit__(self, type, value, traceback): + """To support usage of this fake file with the 'with' statement.""" + self.close() + + def _raise(self, message): + if self.raw_io: + self._filesystem.raise_os_error(errno.EBADF, self.file_path) + raise io.UnsupportedOperation(message) + + def get_object(self): + """Return the FakeFile object that is wrapped by the current instance. + """ + return self.file_object + + def fileno(self): + """Return the file descriptor of the file object.""" + return self.filedes + + def close(self): + """Close the file.""" + # ignore closing a closed file + if not self._is_open(): + return + + # for raw io, all writes are flushed immediately + if self.allow_update and not self.raw_io: + self.flush() + if self._filesystem.is_windows_fs and self._changed: + self.file_object.st_mtime = time.time() + if self._closefd: + self._filesystem._close_open_file(self.filedes) + else: + self._filesystem.open_files[self.filedes].remove(self) + if self.delete_on_close: + self._filesystem.remove_object(self.get_object().path) + + @property + def closed(self): + """Simulate the `closed` attribute on file.""" + return not self._is_open() + + def flush(self): + """Flush file contents to 'disk'.""" + self._check_open_file() + if self.allow_update and not self.is_stream: + contents = self._io.getvalue() + if self._append: + self._sync_io() + old_contents = (self.file_object.byte_contents + if is_byte_string(contents) else + self.file_object.contents) + contents = old_contents + contents[self._flush_pos:] + self._set_stream_contents(contents) + self.update_flush_pos() + else: + self._io.flush() + if self.file_object.set_contents(contents, self._encoding): + if self._filesystem.is_windows_fs: + self._changed = True + else: + current_time = time.time() + self.file_object.st_ctime = current_time + self.file_object.st_mtime = current_time + self._file_epoch = self.file_object.epoch + + if not self.is_stream: + self._flush_related_files() + + def update_flush_pos(self): + self._flush_pos = self._io.tell() + + def _flush_related_files(self): + 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 + self.file_object == open_file.file_object and + not open_file._append): + open_file._sync_io() + + def seek(self, offset, whence=0): + """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): + """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): + """Update the stream with changes to the file object contents.""" + if self._file_epoch == self.file_object.epoch: + return + + if self._io.binary: + contents = self.file_object.byte_contents + else: + contents = self.file_object.contents + + self._set_stream_contents(contents) + self._file_epoch = self.file_object.epoch + + def _set_stream_contents(self, contents): + whence = self._io.tell() + self._io.seek(0) + self._io.truncate() + if not self._io.binary and is_byte_string(contents): + contents = contents.decode(self._encoding) + self._io.putvalue(contents) + if not self._append: + self._io.seek(whence) + + def _read_wrappers(self, name): + """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, writing): + """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 _adapt_size_for_related_files(self, size): + 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 + self.file_object == open_file.file_object and + open_file._append): + open_file._read_seek += size + + def _truncate_wrapper(self): + """Wrap truncate() to allow flush after truncate. + + Returns: + Wrapper which is described below. + """ + io_attr = getattr(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.write('\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): + """Return the content size in bytes of the wrapped file.""" + return self.file_object.st_size + + def __getattr__(self, name): + 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.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 = time.time() + if truncate: + return self._truncate_wrapper() + if self._append: + if reading: + return self._read_wrappers(name) + else: + return self._other_wrapper(name, writing) + + return getattr(self._io, name) + + def _read_error(self): + 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 u'' + self._raise('File is not open for reading.') + + return read_error + + def _write_error(self): + 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): + return (self.filedes < len(self._filesystem.open_files) and + self._filesystem.open_files[self.filedes] is not None and + self in self._filesystem.open_files[self.filedes]) + + def _check_open_file(self): + if not self.is_stream and not self._is_open(): + raise ValueError('I/O operation on closed file') + + def __iter__(self): + 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): + self._stream_object = stream_object + self.filedes = None + + def get_object(self): + return self._stream_object + + def fileno(self): + """Return the file descriptor of the wrapped standard stream.""" + return self.filedes + + def close(self): + """We do not support closing standard streams.""" + pass + + def is_stream(self): + return True + + +class FakeDirWrapper: + """Wrapper for a FakeDirectory object to be used in open files list. + """ + + def __init__(self, file_object, file_path, filesystem): + self.file_object = file_object + self.file_path = file_path + self._filesystem = filesystem + self.filedes = None + + def get_object(self): + """Return the FakeFile object that is wrapped by the current instance. + """ + return self.file_object + + def fileno(self): + """Return the file descriptor of the file object.""" + return self.filedes + + def close(self): + """Close the directory.""" + self._filesystem._close_open_file(self.filedes) + + +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, fd): + self._filesystem = filesystem + self.fd = fd # the real file descriptor + self.file_object = None + self.filedes = None + + def get_object(self): + return self.file_object + + def fileno(self): + """Return the fake file descriptor of the pipe object.""" + return self.filedes + + def read(self, numBytes): + """Read from the real pipe.""" + return os.read(self.fd, numBytes) + + def write(self, contents): + """Write to the real pipe.""" + return os.write(self.fd, contents) + + def close(self): + """Close the pipe descriptor.""" + self._filesystem.open_files[self.filedes].remove(self) + os.close(self.fd) + + +Deprecator.add(FakeFileWrapper, FakeFileWrapper.get_object, 'GetObject') +Deprecator.add(FakeFileWrapper, FakeFileWrapper.size, 'Size') + + +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, delete_on_close=False, raw_io=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, **kwargs): + """Redirects calls to file() or open() to appropriate method.""" + return self.call(*args, **kwargs) + + def call(self, file_, mode='r', buffering=-1, encoding=None, + errors=None, newline=None, closefd=True, opener=None, + open_modes=None): + """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: ignored. (Used for signature compliance with + __builtin__.open) + 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: not supported. + 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 + newline, open_modes = self._handle_file_mode(mode, newline, open_modes) + + file_object, file_path, filedes, real_path = self._handle_file_arg( + file_) + if not filedes: + closefd = True + + if (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) + + file_object = self._init_file_object(file_object, + file_path, open_modes, + real_path) + + 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 = time.time() + 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, + 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, + raw_io=self.raw_io) + if filedes is not None: + fakefile.filedes = filedes + # replace the file wrapper + self.filesystem.open_files[filedes].append(fakefile) + else: + fakefile.filedes = self.filesystem._add_open_file(fakefile) + return fakefile + + def _init_file_object(self, file_object, file_path, + open_modes, real_path): + if file_object: + if (not is_root() and + ((open_modes.can_read and + not file_object.st_mode & PERM_READ) + or (open_modes.can_write and + not file_object.st_mode & 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) + target_path = link_object.contents + 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, raw_io=self.raw_io) + return file_object + + def _handle_file_arg(self, file_): + file_object = None + if isinstance(file_, int): + # opening a file descriptor + filedes = file_ + wrapper = self.filesystem.get_open_file(filedes) + self._delete_on_close = wrapper.delete_on_close + file_object = self.filesystem.get_open_file(filedes).get_object() + file_path = file_object.name + real_path = file_path + else: + # open a file file by path + filedes = None + file_path = file_ + 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, raw_io=self.raw_io) + 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, filedes, real_path + + def _handle_file_mode(self, mode, newline, open_modes): + orig_modes = mode # Save original modes for error messages. + # Normalize modes. Handle 't' and 'U'. + if 'b' in mode and 't' 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]) + return newline, open_modes + + +def _run_doctest(): + import doctest + import pyfakefs + return doctest.testmod(pyfakefs.fake_filesystem) + + +if __name__ == '__main__': + _run_doctest() diff --git a/pyfakefs/fake_filesystem_shutil.py b/pyfakefs/fake_filesystem_shutil.py new file mode 100755 index 0000000..314e11b --- /dev/null +++ b/pyfakefs/fake_filesystem_shutil.py @@ -0,0 +1,64 @@ +# 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 shutil + + +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) + + def __getattr__(self, name): + """Forwards any non-faked calls to the standard shutil module.""" + return getattr(self._shutil_module, name) diff --git a/pyfakefs/fake_filesystem_unittest.py b/pyfakefs/fake_filesystem_unittest.py new file mode 100644 index 0000000..dbb2e34 --- /dev/null +++ b/pyfakefs/fake_filesystem_unittest.py @@ -0,0 +1,778 @@ +# 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 doctest +import functools +import inspect +import shutil +import sys +import tempfile +import unittest +import warnings + +from pyfakefs.deprecator import Deprecator +from pyfakefs.fake_filesystem import set_uid, set_gid, reset_ids +from pyfakefs.helpers import IS_PYPY + +try: + from importlib.machinery import ModuleSpec +except ImportError: + ModuleSpec = object + +from importlib import reload + +from pyfakefs import fake_filesystem +from pyfakefs import fake_filesystem_shutil +from pyfakefs import mox3_stubout +from pyfakefs.extra_packages import pathlib, pathlib2, use_scandir + +if pathlib: + from pyfakefs import fake_pathlib + +if use_scandir: + from pyfakefs import fake_scandir + +OS_MODULE = 'nt' if sys.platform == 'win32' else 'posix' +PATH_MODULE = 'ntpath' if sys.platform == 'win32' else 'posixpath' +BUILTIN_MODULE = '__builtin__' + + +def _patchfs(f): + """Internally used to be able to use patchfs without parentheses.""" + + @functools.wraps(f) + def decorated(*args, **kwargs): + with Patcher() as p: + kwargs['fs'] = p.fs + return f(*args, **kwargs) + + return decorated + + +def patchfs(additional_skip_names=None, + modules_to_reload=None, + modules_to_patch=None, + allow_root_user=True): + """Convenience decorator to use patcher with additional parameters in a + test function. + + Usage:: + + @patchfs + test_my_function(fs): + fs.create_file('foo') + + @patchfs(allow_root_user=False) + test_with_patcher_args(fs): + os.makedirs('foo/bar') + """ + + def wrap_patchfs(f): + @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) as p: + kwargs['fs'] = p.fs + return f(*args, **kwargs) + + return wrapped + + # workaround to be able to use the decorator without using calling syntax + # (the default usage without parameters) + # if using the decorator without parentheses, the first argument here + # will be the wrapped function, so we pass it to the decorator function + # that doesn't use arguments + if inspect.isfunction(additional_skip_names): + return _patchfs(additional_skip_names) + + return wrap_patchfs + + +def load_doctests(loader, tests, ignore, module, + additional_skip_names=None, + modules_to_reload=None, + modules_to_patch=None, + allow_root_user=True): # 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. + """ + _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) + globs = _patcher.replace_globs(vars(module)) + tests.addTests(doctest.DocTestSuite(module, + globs=globs, + setUp=_patcher.setUp, + tearDown=_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 inside of which 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 = None + modules_to_reload = None + modules_to_patch = None + + @property + def fs(self): + return self._stubber.fs + + def setUpPyfakefs(self, + additional_skip_names=None, + modules_to_reload=None, + modules_to_patch=None, + allow_root_user=True): + """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 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._stubber = 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 + ) + + self._stubber.setUp() + self.addCleanup(self._stubber.tearDown) + + def pause(self): + """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._stubber.pause() + + def resume(self): + """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._stubber.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='runTest', + additional_skip_names=None, + modules_to_reload=None, + modules_to_patch=None, + allow_root_user=True): + """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(TestCase, self).__init__(methodName) + + self.additional_skip_names = additional_skip_names + self.modules_to_reload = modules_to_reload + self.modules_to_patch = modules_to_patch + self.allow_root_user = allow_root_user + + @Deprecator('add_real_file') + def copyRealFile(self, real_file_path, fake_file_path=None, + create_missing_dirs=True): + """Add the file `real_file_path` in the real file system to the same + path in the fake file system. + + **This method is deprecated** in favor of + :py:meth:`FakeFilesystem..add_real_file`. + `copyRealFile()` is retained with limited functionality for backward + compatibility only. + + Args: + real_file_path: Path to the file in both the real and fake + file systems + fake_file_path: Deprecated. Use the default, which is + `real_file_path`. + If a value other than `real_file_path` is specified, a `ValueError` + exception will be raised. + create_missing_dirs: Deprecated. Use the default, which creates + missing directories in the fake file system. If `False` is + specified, a `ValueError` exception is raised. + + Returns: + The newly created FakeFile object. + + Raises: + OSError: If the file already exists in the fake file system. + ValueError: If deprecated argument values are specified. + + See: + :py:meth:`FakeFileSystem.add_real_file` + """ + if fake_file_path is not None and real_file_path != fake_file_path: + raise ValueError("CopyRealFile() is deprecated and no longer " + "supports different real and fake file paths") + if not create_missing_dirs: + raise ValueError("CopyRealFile() is deprecated and no longer " + "supports NOT creating missing directories") + return self._stubber.fs.add_real_file(real_file_path, read_only=False) + + @DeprecationWarning + def tearDownPyfakefs(self): + """This method is deprecated and exists only for backward + compatibility. It does nothing. + """ + pass + + +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`. + ''' + SKIPMODULES = {None, fake_filesystem, fake_filesystem_shutil, sys} + assert None in SKIPMODULES, ("sys.modules contains 'None' values;" + " must skip them.") + + IS_WINDOWS = sys.platform in ('win32', 'cygwin') + + SKIPNAMES = {'os', 'path', 'io', 'genericpath', OS_MODULE, PATH_MODULE} + if pathlib: + SKIPNAMES.add('pathlib') + if pathlib2: + SKIPNAMES.add('pathlib2') + + def __init__(self, additional_skip_names=None, + modules_to_reload=None, modules_to_patch=None, + allow_root_user=True): + """For a description of the arguments, see TestCase.__init__""" + + if not allow_root_user: + # set non-root IDs even if the real user is root + set_uid(1) + set_gid(1) + + self._skipNames = self.SKIPNAMES.copy() + # save the original open function for use in pytest plugin + self.original_open = open + self.fake_open = None + + if additional_skip_names is not None: + skip_names = [m.__name__ if inspect.ismodule(m) else m + for m in additional_skip_names] + self._skipNames.update(skip_names) + + self._fake_module_classes = {} + self._class_modules = {} + self._init_fake_module_classes() + + self.modules_to_reload = modules_to_reload or [] + + if modules_to_patch is not None: + for name, fake_module in modules_to_patch.items(): + self._fake_module_classes[name] = fake_module + + self._fake_module_functions = {} + self._init_fake_module_functions() + + # Attributes set by _refresh() + self._modules = {} + self._fct_modules = {} + self._def_functions = [] + self._open_functions = {} + self._stubs = None + self.fs = None + self.fake_modules = {} + self._dyn_patcher = None + + # _isStale is set by tearDown(), reset by _refresh() + self._isStale = True + self._patching = False + + def _init_fake_module_classes(self): + # 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_filesystem.FakeOsModule, + 'shutil': fake_filesystem_shutil.FakeShutilModule, + 'io': fake_filesystem.FakeIoModule, + } + if IS_PYPY: + # in PyPy io.open, the module is referenced as _io + self._fake_module_classes['_io'] = fake_filesystem.FakeIoModule + + # class modules maps class names against a list of modules they can + # be contained in - this allows for alternative modules like + # `pathlib` and `pathlib2` + if pathlib: + self._class_modules['Path'] = [] + if pathlib: + self._fake_module_classes[ + 'pathlib'] = fake_pathlib.FakePathlibModule + self._class_modules['Path'].append('pathlib') + if pathlib2: + self._fake_module_classes[ + 'pathlib2'] = fake_pathlib.FakePathlibModule + self._class_modules['Path'].append('pathlib2') + self._fake_module_classes[ + 'Path'] = fake_pathlib.FakePathlibPathModule + if use_scandir: + self._fake_module_classes[ + 'scandir'] = fake_scandir.FakeScanDirModule + + def _init_fake_module_functions(self): + # 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') and + inspect.isfunction(fake_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): + """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, exc_val, exc_tb): + self.tearDown() + + def _is_fs_module(self, mod, name, module_names): + 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 AttributeError: + # handle cases where the module has no __name__ or __module__ + # attribute - see #460 + return False + + def _is_fs_function(self, fct): + 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 AttributeError: + # handle cases where the function has no __name__ or __module__ + # attribute + return False + + def _def_values(self, item): + """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 + if inspect.isfunction(item): + if item.__defaults__: + for i, d in enumerate(item.__defaults__): + if self._is_fs_function(d): + yield item, i, d + elif inspect.isclass(item): + # check for methods in class (nested classes are ignored for now) + try: + for m in inspect.getmembers(item, + predicate=inspect.isfunction): + m = m[1] + if m.__defaults__: + for i, d in enumerate(m.__defaults__): + if self._is_fs_function(d): + yield m, i, d + except Exception: + # Ignore any exception, examples: + # ImportError: No module named '_gdbm' + # _DontDoThat() (see #523) + pass + + def _find_modules(self): + """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 (module in self.SKIPMODULES or + not inspect.ismodule(module) or + module.__name__.split('.')[0] in self._skipNames): + continue + except AttributeError: + # workaround for some py (part of pytest) versions + # where py.error has no __name__ attribute + # see https://github.com/pytest-dev/py/issues/73 + continue + + module_items = module.__dict__.copy().items() + + # suppress specific pytest warning - see #466 + with warnings.catch_warnings(): + warnings.filterwarnings( + 'ignore', + message='The compiler package is deprecated', + category=DeprecationWarning, + module='py' + ) + modules = {name: mod for name, mod in module_items + if self._is_fs_module(mod, name, module_names)} + + for name, mod in modules.items(): + self._modules.setdefault(name, set()).add((module, + mod.__name__)) + functions = {name: fct for name, fct in + module_items + if self._is_fs_function(fct)} + + # find default arguments that are file system functions + for _, fct in module_items: + for f, i, d in self._def_values(fct): + self._def_functions.append((f, i, d)) + + for name, fct in functions.items(): + self._fct_modules.setdefault( + (name, fct.__name__, fct.__module__), set()).add(module) + + def _refresh(self): + """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) + for name in self._fake_module_classes: + self.fake_modules[name] = self._fake_module_classes[name](self.fs) + self.fake_modules[PATH_MODULE] = self.fake_modules['os'].path + self.fake_open = fake_filesystem.FakeFileOpen(self.fs) + + self._isStale = False + + def setUp(self, doctester=None): + """Bind the file-related modules to the :py:mod:`pyfakefs` fake + modules real ones. Also bind the fake `file()` and `open()` functions. + """ + 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 + + temp_dir = tempfile.gettempdir() + self._find_modules() + self._refresh() + + if doctester is not None: + doctester.globs = self.replace_globs(doctester.globs) + + self.start_patching() + + # the temp directory is assumed to exist at least in `tempfile1`, + # so we create it here for convenience + self.fs.create_dir(temp_dir) + + def start_patching(self): + if not self._patching: + self._patching = True + + for name, modules in self._modules.items(): + for module, attr in modules: + self._stubs.smart_set( + module, name, self.fake_modules[attr]) + for (name, ft_name, ft_mod), modules in self._fct_modules.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__) + for module in modules: + self._stubs.smart_set(module, name, attr) + + for (fct, idx, ft) in self._def_functions: + 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__) + new_defaults = [] + for i, d in enumerate(fct.__defaults__): + if i == idx: + new_defaults.append(attr) + else: + new_defaults.append(d) + fct.__defaults__ = tuple(new_defaults) + + self._dyn_patcher = DynamicPatcher(self) + sys.meta_path.insert(0, self._dyn_patcher) + for module in self.modules_to_reload: + if module.__name__ in sys.modules: + reload(module) + + def replace_globs(self, globs_): + 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=None): + """Clear the fake filesystem bindings created by `setUp()`.""" + self.stop_patching() + if self.has_fcopy_file: + shutil._HAS_FCOPYFILE = True + + reset_ids() + + def stop_patching(self): + if self._patching: + self._isStale = True + self._patching = False + self._stubs.smart_unset_all() + self.unset_defaults() + self._dyn_patcher.cleanup() + sys.meta_path.pop(0) + + def unset_defaults(self): + for (fct, idx, ft) in self._def_functions: + new_defaults = [] + for i, d in enumerate(fct.__defaults__): + if i == idx: + new_defaults.append(ft) + else: + new_defaults.append(d) + fct.__defaults__ = tuple(new_defaults) + self._def_functions = [] + + def pause(self): + """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() + + def resume(self): + """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.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 it's scope. + """ + + def __init__(self, caller): + """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)): + self._fs = caller.fs + elif isinstance(caller, fake_filesystem.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): + self._fs.pause() + return self._fs + + def __exit__(self, *args): + return self._fs.resume() + + +class DynamicPatcher: + """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): + self._patcher = patcher + self.sysmodules = {} + self.modules = self._patcher.fake_modules + self._loaded_module_names = set() + + # 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): + for module in self.sysmodules: + sys.modules[module] = self.sysmodules[module] + 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] + # Dereference all modules loaded during the test so they will reload on + # the next use, ensuring that no faked modules are referenced after the + # test. + for name in self._loaded_module_names: + if name in sys.modules and name not in reloaded_module_names: + del sys.modules[name] + + def needs_patch(self, name): + """Check 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]) == self.modules[name]): + return False + return True + + def find_spec(self, fullname, path, target=None): + """Module finder for Python 3.""" + if self.needs_patch(fullname): + return ModuleSpec(fullname, self) + + def load_module(self, fullname): + """Replaces the module by its fake implementation.""" + sys.modules[fullname] = self.modules[fullname] + return self.modules[fullname] diff --git a/pyfakefs/fake_pathlib.py b/pyfakefs/fake_pathlib.py new file mode 100644 index 0000000..0c06708 --- /dev/null +++ b/pyfakefs/fake_pathlib.py @@ -0,0 +1,705 @@ +# 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 fnmatch +import os +import re + +try: + from urllib.parse import quote_from_bytes as urlquote_from_bytes +except ImportError: + from urllib import quote as urlquote_from_bytes + +import sys + +import functools + +import errno + +from pyfakefs import fake_scandir +from pyfakefs.extra_packages import use_scandir, pathlib, pathlib2 +from pyfakefs.fake_filesystem import FakeFileOpen, FakeFilesystem + + +def init_module(filesystem): + """Initializes the fake module with the fake file system.""" + # pylint: disable=protected-access + FakePath.filesystem = filesystem + FakePathlibModule.PureWindowsPath._flavour = _FakeWindowsFlavour( + filesystem) + FakePathlibModule.PurePosixPath._flavour = _FakePosixFlavour(filesystem) + + +def _wrap_strfunc(strfunc): + @functools.wraps(strfunc) + def _wrapped(pathobj, *args): + return strfunc(pathobj.filesystem, str(pathobj), *args) + + return staticmethod(_wrapped) + + +def _wrap_binary_strfunc(strfunc): + @functools.wraps(strfunc) + def _wrapped(pathobj1, pathobj2, *args): + return strfunc( + pathobj1.filesystem, str(pathobj1), str(pathobj2), *args) + + return staticmethod(_wrapped) + + +def _wrap_binary_strfunc_reverse(strfunc): + @functools.wraps(strfunc) + def _wrapped(pathobj1, pathobj2, *args): + return strfunc( + pathobj2.filesystem, str(pathobj2), str(pathobj1), *args) + + return staticmethod(_wrapped) + + +try: + accessor = pathlib._Accessor +except AttributeError: + accessor = object + + +class _FakeAccessor(accessor): + """Accessor which forwards some of the functions to FakeFilesystem methods. + """ + + stat = _wrap_strfunc(FakeFilesystem.stat) + + lstat = _wrap_strfunc( + lambda fs, path: FakeFilesystem.stat(fs, path, follow_symlinks=False)) + + listdir = _wrap_strfunc(FakeFilesystem.listdir) + + chmod = _wrap_strfunc(FakeFilesystem.chmod) + + if use_scandir: + scandir = _wrap_strfunc(fake_scandir.scandir) + + if hasattr(os, "lchmod"): + lchmod = _wrap_strfunc(lambda fs, path, mode: FakeFilesystem.chmod( + fs, path, mode, follow_symlinks=False)) + else: + def lchmod(self, pathobj, mode): + """Raises not implemented for Windows systems.""" + raise NotImplementedError("lchmod() not available on this system") + + mkdir = _wrap_strfunc(FakeFilesystem.makedir) + + unlink = _wrap_strfunc(FakeFilesystem.remove) + + rmdir = _wrap_strfunc(FakeFilesystem.rmdir) + + rename = _wrap_binary_strfunc(FakeFilesystem.rename) + + replace = _wrap_binary_strfunc( + lambda fs, old_path, new_path: FakeFilesystem.rename( + fs, old_path, new_path, force_replace=True)) + + symlink = _wrap_binary_strfunc_reverse( + lambda fs, file_path, link_target, target_is_directory: + FakeFilesystem.create_symlink(fs, file_path, link_target, + create_missing_dirs=False)) + + utime = _wrap_strfunc(FakeFilesystem.utime) + + +_fake_accessor = _FakeAccessor() + +flavour = pathlib._Flavour if pathlib else object + + +class _FakeFlavour(flavour): + """Fake Flavour implementation used by PurePath and _Flavour""" + + filesystem = None + sep = '/' + altsep = None + has_drv = False + + ext_namespace_prefix = '\\\\?\\' + + drive_letters = ( + set(chr(x) for x in range(ord('a'), ord('z') + 1)) | + set(chr(x) for x in range(ord('A'), ord('Z') + 1)) + ) + + def __init__(self, filesystem): + self.filesystem = filesystem + self.sep = filesystem.path_separator + self.altsep = filesystem.alternative_path_separator + self.has_drv = filesystem.is_windows_fs + super(_FakeFlavour, self).__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.""" + if sep is None: + sep = self.filesystem.path_separator + if self.filesystem.is_windows_fs: + 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. + """ + 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 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 self.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:///%s/%s' % ( + 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. + """ + + 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 + + +path_module = pathlib.Path if pathlib else object + + +class FakePath(path_module): + """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 + + def __new__(cls, *args, **kwargs): + """Creates the correct subclass based on OS.""" + if cls is FakePathlibModule.Path: + cls = (FakePathlibModule.WindowsPath if os.name == 'nt' + else FakePathlibModule.PosixPath) + self = cls._from_parts(args, init=True) + return self + + def _path(self): + """Returns the underlying path string as used by the fake filesystem. + """ + return str(self) + + def _init(self, template=None): + """Initializer called from base class.""" + self._accessor = _fake_accessor + self._closed = False + + @classmethod + def cwd(cls): + """Return a new path pointing to the current working directory + (as returned by os.getcwd()). + """ + return cls(cls.filesystem.cwd) + + 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. + New in Python 3.6. + + Raises: + OSError: if the path doesn't exist (strict=True or Python < 3.6) + """ + if sys.version_info >= (3, 6) or pathlib2: + if strict is None: + strict = False + else: + if strict is not None: + raise TypeError( + "resolve() got an unexpected keyword argument 'strict'") + strict = True + if self._closed: + self._raise_closed() + path = self._flavour.resolve(self, strict=strict) + 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. + """ + if self._closed: + self._raise_closed() + return FakeFileOpen(self.filesystem)( + 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 FakeFileOpen(self.filesystem)(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 FakeFileOpen(self.filesystem)(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 FakeFileOpen(self.filesystem)(self._path(), mode='wb') as f: + return f.write(view) + + def write_text(self, data, encoding=None, errors=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: ignored + 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__) + with FakeFileOpen(self.filesystem)(self._path(), + mode='w', + encoding=encoding, + errors=errors) 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('~')). + """ + return cls(cls()._flavour.gethomedir(None). + 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 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. + """ + if self._closed: + self._raise_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') + fake_file.close() + self.chmod(mode) + + +class FakePathlibModule: + """Uses FakeFilesystem to provide a fake pathlib module replacement. + Can be used to replace both the standard `pathlib` module and the + `pathlib2` package available on PyPi. + + You need a fake_filesystem to use this: + `filesystem = fake_filesystem.FakeFilesystem()` + `fake_pathlib_module = fake_filesystem.FakePathlibModule(filesystem)` + """ + + PurePath = pathlib.PurePath if pathlib else object + + 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__ = () + + class PureWindowsPath(PurePath): + """A subclass of PurePath, that represents Windows filesystem paths""" + __slots__ = () + + if sys.platform == 'win32': + class WindowsPath(FakePath, PureWindowsPath): + """A subclass of Path and PureWindowsPath that represents + concrete Windows filesystem paths. + """ + __slots__ = () + else: + class PosixPath(FakePath, PurePosixPath): + """A subclass of Path and PurePosixPath that represents + concrete non-Windows filesystem paths. + """ + __slots__ = () + + 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): + if self.fake_pathlib is None: + self.__class__.fake_pathlib = FakePathlibModule(filesystem) + + def __call__(self, *args, **kwargs): + return self.fake_pathlib.Path(*args, **kwargs) + + def __getattr__(self, name): + return getattr(self.fake_pathlib.Path, name) diff --git a/pyfakefs/fake_scandir.py b/pyfakefs/fake_scandir.py new file mode 100644 index 0000000..5573b40 --- /dev/null +++ b/pyfakefs/fake_scandir.py @@ -0,0 +1,311 @@ +# 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.extra_packages import use_scandir_package +from pyfakefs.helpers import to_string + +if sys.version_info >= (3, 6): + BaseClass = os.PathLike +else: + BaseClass = object + + +class DirEntry(BaseClass): + """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 + + if sys.version_info >= (3, 6): + def __fspath__(self): + return self.path + + +class ScanDirIter: + """Iterator for DirEntry objects returned from `scandir()` + function.""" + + def __init__(self, filesystem, path): + self.filesystem = filesystem + if isinstance(path, int): + if not use_scandir_package and ( + sys.version_info < (3, 7) or + 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 = '' + else: + self.abspath = self.filesystem.absnormpath(path) + self.path = to_string(path) + contents = self.filesystem.confirmdir(self.abspath).contents + self.contents_iter = iter(contents) + + def __iter__(self): + return self + + def __next__(self): + entry = self.contents_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 + + if sys.version_info >= (3, 6): + 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]: + if not followlinks and filesystem.islink(directory): + continue + for contents in do_walk(filesystem.joinpaths(top_dir, + directory)): + yield contents + + if not topdown: + yield top_contents + + return do_walk(to_string(top), top_most=True) + + +class FakeScanDirModule: + """Uses FakeFilesystem to provide a fake `scandir` module replacement. + + .. Note:: The ``scandir`` function is a part of the standard ``os`` module + since Python 3.5. This class handles the separate ``scandir`` module + that is available on pypi. + + 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 + + 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. + """ + 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. + """ + return walk(self.filesystem, top, topdown, onerror, followlinks) diff --git a/pyfakefs/helpers.py b/pyfakefs/helpers.py new file mode 100644 index 0000000..58de021 --- /dev/null +++ b/pyfakefs/helpers.py @@ -0,0 +1,432 @@ +# 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 io +import locale +import platform +import stat +import sys +from copy import copy +from stat import S_IFLNK + +import os + +IS_PYPY = platform.python_implementation() == 'PyPy' +IS_WIN = sys.platform == 'win32' +IN_DOCKER = os.path.exists('/.dockerenv') + + +def is_int_type(val): + """Return True if `val` is of integer type.""" + return isinstance(val, int) + + +def is_byte_string(val): + """Return True if `val` is a bytes-like object, False for a unicode + string.""" + return not hasattr(val, 'encode') + + +def is_unicode_string(val): + """Return True if `val` is a unicode string, False for a bytes-like + object.""" + return hasattr(val, 'encode') + + +def make_string_path(dir_name): + if sys.version_info >= (3, 6): + dir_name = os.fspath(dir_name) + return dir_name + + +def to_string(path): + """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(locale.getpreferredencoding(False)) + return path + + +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. + """ + _stat_float_times = True + + def __init__(self, is_windows, user_id, group_id, initial_time=None): + self._use_float = None + self.st_mode = None + self.st_ino = None + self.st_dev = None + self.st_nlink = 0 + self.st_uid = user_id + self.st_gid = group_id + self._st_size = None + self.is_windows = is_windows + if initial_time is not None: + self._st_atime_ns = int(initial_time * 1e9) + else: + self._st_atime_ns = None + self._st_mtime_ns = self._st_atime_ns + self._st_ctime_ns = self._st_atime_ns + + @property + def use_float(self): + if self._use_float is None: + return self.stat_float_times() + return self._use_float + + @use_float.setter + def use_float(self, val): + self._use_float = val + + def __eq__(self, other): + 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): + return not self == other + + def copy(self): + """Return a copy where the float usage is hard-coded to mimic the + behavior of the real os.stat_result. + """ + stat_result = copy(self) + stat_result.use_float = self.use_float + return stat_result + + def set_from_stat_result(self, stat_result): + """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 + + @classmethod + def stat_float_times(cls, newvalue=None): + """Determine whether a file's time stamps are reported as floats + or ints. + + Calling without arguments returns the current value. + The value is shared by all instances of FakeOsModule. + + Args: + newvalue: If `True`, mtime, ctime, atime are reported as floats. + Otherwise, they are returned as ints (rounding down). + """ + if newvalue is not None: + cls._stat_float_times = bool(newvalue) + return cls._stat_float_times + + @property + def st_ctime(self): + """Return the creation time in seconds.""" + ctime = self._st_ctime_ns / 1e9 + return ctime if self.use_float else int(ctime) + + @property + def st_atime(self): + """Return the access time in seconds.""" + atime = self._st_atime_ns / 1e9 + return atime if self.use_float else int(atime) + + @property + def st_mtime(self): + """Return the modification time in seconds.""" + mtime = self._st_mtime_ns / 1e9 + return mtime if self.use_float else int(mtime) + + @st_ctime.setter + def st_ctime(self, val): + """Set the creation time in seconds.""" + self._st_ctime_ns = int(val * 1e9) + + @st_atime.setter + def st_atime(self, val): + """Set the access time in seconds.""" + self._st_atime_ns = int(val * 1e9) + + @st_mtime.setter + def st_mtime(self, val): + """Set the modification time in seconds.""" + self._st_mtime_ns = int(val * 1e9) + + @property + def st_size(self): + 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): + self._st_size = val + + @property + def st_file_attributes(self): + 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 + if st_mode & stat.S_IFREG: + mode |= stat.FILE_ATTRIBUTE_NORMAL + if st_mode & (stat.S_IFCHR | stat.S_IFBLK): + mode |= stat.FILE_ATTRIBUTE_DEVICE + if st_mode & stat.S_IFLNK: + mode |= stat.FILE_ATTRIBUTE_REPARSE_POINT + return mode + + @property + def st_reparse_tag(self): + 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 + return 0 + + def __getitem__(self, item): + """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): + """Return the access time in nanoseconds.""" + return self._st_atime_ns + + @property + def st_mtime_ns(self): + """Return the modification time in nanoseconds.""" + return self._st_mtime_ns + + @property + def st_ctime_ns(self): + """Return the creation time in nanoseconds.""" + return self._st_ctime_ns + + @st_atime_ns.setter + def st_atime_ns(self, val): + """Set the access time in nanoseconds.""" + self._st_atime_ns = val + + @st_mtime_ns.setter + def st_mtime_ns(self, val): + """Set the modification time of the fake file in nanoseconds.""" + self._st_mtime_ns = val + + @st_ctime_ns.setter + def st_ctime_ns(self, val): + """Set the creation time of the fake file in nanoseconds.""" + self._st_ctime_ns = val + + +class FileBufferIO: + """Stream class that handles Python string and byte contents for files. + The standard io.StringIO cannot be used for strings due to the slightly + different handling of newline mode. + Uses an io.BytesIO stream for the raw data and adds handling of encoding + and newlines. + """ + + def __init__(self, contents=None, linesep='\n', binary=False, + newline=None, encoding=None, errors='strict'): + self._newline = newline + self._encoding = encoding + self.errors = errors + self._linesep = linesep + self.binary = binary + self._bytestream = io.BytesIO() + if contents is not None: + self.putvalue(contents) + self._bytestream.seek(0) + + def encoding(self): + return self._encoding or locale.getpreferredencoding(False) + + def encoded_string(self, contents): + if is_byte_string(contents): + return contents + return contents.encode(self.encoding(), self.errors) + + def decoded_string(self, contents): + return contents.decode(self.encoding(), self.errors) + + def convert_newlines_for_writing(self, s): + if self.binary: + return s + if self._newline in (None, '-'): + return s.replace('\n', self._linesep) + if self._newline in ('', '\n'): + return s + return s.replace('\n', self._newline) + + def convert_newlines_after_reading(self, s): + if self._newline is None: + return s.replace('\r\n', '\n').replace('\r', '\n') + if self._newline == '-': + return s.replace(self._linesep, '\n') + return s + + def read(self, size=-1): + contents = self._bytestream.read(size) + if self.binary: + return contents + return self.convert_newlines_after_reading( + self.decoded_string(contents)) + + def readline(self, size=-1): + seek_pos = self._bytestream.tell() + byte_contents = self._bytestream.read(size) + if self.binary: + read_contents = byte_contents + LF = b'\n' + else: + read_contents = self.convert_newlines_after_reading( + self.decoded_string(byte_contents)) + LF = '\n' + end_pos = 0 + + if self._newline is None: + end_pos = self._linelen_for_universal_newlines(byte_contents) + if end_pos > 0: + length = read_contents.find(LF) + 1 + elif self._newline == '': + end_pos = self._linelen_for_universal_newlines(byte_contents) + if end_pos > 0: + if byte_contents[end_pos - 1] == ord(b'\r'): + newline = '\r' + elif end_pos > 1 and byte_contents[end_pos - 2] == ord(b'\r'): + newline = '\r\n' + else: + newline = '\n' + length = read_contents.find(newline) + len(newline) + else: + newline = '\n' if self._newline == '-' else self._newline + length = read_contents.find(newline) + if length >= 0: + nl_len = len(newline) + end_pos = byte_contents.find(newline.encode()) + nl_len + length += nl_len + + if end_pos == 0: + length = len(read_contents) + end_pos = len(byte_contents) + + self._bytestream.seek(seek_pos + end_pos) + return (byte_contents[:end_pos] if self.binary + else read_contents[:length]) + + def _linelen_for_universal_newlines(self, byte_contents): + if self.binary: + return byte_contents.find(b'\n') + 1 + pos_lf = byte_contents.find(b'\n') + pos_cr = byte_contents.find(b'\r') + if pos_lf == -1 and pos_cr == -1: + return 0 + if pos_lf != -1 and (pos_lf < pos_cr or pos_cr == -1): + end_pos = pos_lf + else: + end_pos = pos_cr + if end_pos == pos_cr and end_pos + 1 == pos_lf: + end_pos = pos_lf + return end_pos + 1 + + def readlines(self, size=-1): + remaining_size = size + lines = [] + while True: + line = self.readline(remaining_size) + if not line: + return lines + lines.append(line) + if size > 0: + remaining_size -= len(line) + if remaining_size <= 0: + return lines + + def putvalue(self, s): + self._bytestream.write(self.encoded_string(s)) + + def write(self, s): + if self.binary != is_byte_string(s): + raise TypeError('Incorrect type for writing') + contents = self.convert_newlines_for_writing(s) + length = len(contents) + self.putvalue(contents) + return length + + def writelines(self, lines): + for line in lines: + self.write(line) + + def __iter__(self): + return self + + def __next__(self): + line = self.readline() + if not line: + raise StopIteration + return line + + def __getattr__(self, name): + return getattr(self._bytestream, name) + + +class NullFileBufferIO(FileBufferIO): + """Special stream for null device. Does nothing on writing.""" + + def putvalue(self, s): + pass diff --git a/pyfakefs/mox3_stubout.py b/pyfakefs/mox3_stubout.py new file mode 100644 index 0000000..d936dcd --- /dev/null +++ b/pyfakefs/mox3_stubout.py @@ -0,0 +1,162 @@ +# Copyright 2008 Google Inc. +# +# 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 is a fork of the pymox library intended to work with Python 3. +The file was modified by quermit@gmail.com and dawid.fatyga@gmail.com + +Previously, pyfakefs used just this file from the mox3 library. +However, mox3 will soon be decommissioned, yet standard mock cannot +be used because of the problem described in pyfakefs #182 and +mock issue 250 (https://github.com/testing-cabal/mock/issues/250). +Therefore just this file was forked from mox3 and incorporated +into pyfakefs. +""" + +import inspect + + +class StubOutForTesting: + """Sample Usage: + + You want os.path.exists() to always return true during testing. + + stubs = StubOutForTesting() + stubs.Set(os.path, 'exists', lambda x: 1) + ... + stubs.UnsetAll() + + The above changes os.path.exists into a lambda that returns 1. Once + the ... part of the code finishes, the UnsetAll() looks up the old value + of os.path.exists and restores it. + + """ + + def __init__(self): + self.cache = [] + self.stubs = [] + + def __del__(self): + self.smart_unset_all() + self.unset_all() + + def smart_set(self, obj, attr_name, new_attr): + """Replace obj.attr_name with new_attr. + + This method is smart and works at the module, class, and instance level + while preserving proper inheritance. It will not stub out C types + however unless that has been explicitly allowed by the type. + + This method supports the case where attr_name is a staticmethod or a + classmethod of obj. + + Notes: + - If obj is an instance, then it is its class that will actually be + stubbed. Note that the method Set() does not do that: if obj is + an instance, it (and not its class) will be stubbed. + - The stubbing is using the builtin getattr and setattr. So, the + __get__ and __set__ will be called when stubbing (TODO: A better + idea would probably be to manipulate obj.__dict__ instead of + getattr() and setattr()). + + Raises AttributeError if the attribute cannot be found. + """ + if (inspect.ismodule(obj) or + (not inspect.isclass(obj) and attr_name in obj.__dict__)): + orig_obj = obj + orig_attr = getattr(obj, attr_name) + + else: + if not inspect.isclass(obj): + mro = list(inspect.getmro(obj.__class__)) + else: + mro = list(inspect.getmro(obj)) + + mro.reverse() + + orig_attr = None + + for cls in mro: + try: + orig_obj = cls + orig_attr = getattr(obj, attr_name) + except AttributeError: + continue + + if orig_attr is None: + raise AttributeError("Attribute not found.") + + # Calling getattr() on a staticmethod transforms it to a 'normal' + # function. We need to ensure that we put it back as a staticmethod. + old_attribute = obj.__dict__.get(attr_name) + if (old_attribute is not None + and isinstance(old_attribute, staticmethod)): + orig_attr = staticmethod(orig_attr) + + self.stubs.append((orig_obj, attr_name, orig_attr)) + setattr(orig_obj, attr_name, new_attr) + + def smart_unset_all(self): + """Reverses all the SmartSet() calls. + + Restores things to their original definition. Its okay to call + SmartUnsetAll() repeatedly, as later calls have no effect if no + SmartSet() calls have been made. + """ + self.stubs.reverse() + + for args in self.stubs: + setattr(*args) + + self.stubs = [] + + def set(self, parent, child_name, new_child): + """Replace child_name's old definition with new_child. + + Replace definition in the context of the given parent. The parent could + be a module when the child is a function at module scope. Or the parent + could be a class when a class' method is being replaced. The named + child is set to new_child, while the prior definition is saved away + for later, when unset_all() is called. + + This method supports the case where child_name is a staticmethod or a + classmethod of parent. + """ + old_child = getattr(parent, child_name) + + old_attribute = parent.__dict__.get(child_name) + if old_attribute is not None: + if isinstance(old_attribute, staticmethod): + old_child = staticmethod(old_child) + elif isinstance(old_attribute, classmethod): + old_child = classmethod(old_child.__func__) + + self.cache.append((parent, old_child, child_name)) + setattr(parent, child_name, new_child) + + def unset_all(self): + """Reverses all the Set() calls. + + Restores things to their original definition. Its okay to call + unset_all() repeatedly, as later calls have no effect if no Set() + calls have been made. + """ + # Undo calls to set() in reverse order, in case set() was called on the + # same arguments repeatedly (want the original call to be last one + # undone) + self.cache.reverse() + + for (parent, old_child, child_name) in self.cache: + setattr(parent, child_name, old_child) + self.cache = [] diff --git a/pyfakefs/pytest_plugin.py b/pyfakefs/pytest_plugin.py new file mode 100644 index 0000000..93702f2 --- /dev/null +++ b/pyfakefs/pytest_plugin.py @@ -0,0 +1,43 @@ +"""A pytest plugin for using pyfakefs as a fixture + +When pyfakefs is installed, the "fs" fixture becomes available. + +:Usage: + +def my_fakefs_test(fs): + fs.create_file('/var/data/xx1.txt') + assert os.path.exists('/var/data/xx1.txt') +""" + +import linecache +import tokenize + +import py +import pytest + +from pyfakefs.fake_filesystem_unittest import Patcher + +Patcher.SKIPMODULES.add(pytest) +Patcher.SKIPMODULES.add(py) # Ignore pytest components when faking filesystem + +# 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. +Patcher.SKIPMODULES.add(linecache) +Patcher.SKIPMODULES.add(tokenize) + + +@pytest.fixture +def fs(request): + """ Fake filesystem. """ + if hasattr(request, 'param'): + # pass optional parameters via @pytest.mark.parametrize + patcher = Patcher(*request.param) + else: + patcher = Patcher() + patcher.setUp() + tokenize._builtin_open = patcher.original_open + yield patcher.fs + patcher.tearDown() diff --git a/pyfakefs/pytest_tests/__init__.py b/pyfakefs/pytest_tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pyfakefs/pytest_tests/__init__.py diff --git a/pyfakefs/pytest_tests/conftest.py b/pyfakefs/pytest_tests/conftest.py new file mode 100644 index 0000000..92b49ff --- /dev/null +++ b/pyfakefs/pytest_tests/conftest.py @@ -0,0 +1,43 @@ +# 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 custom pytest fixture with an argument to Patcher. +# Use this as a template if you want to write your own pytest plugin +# with specific Patcher arguments. +# See `pytest_plugin.py` for more information. + +import linecache +import tokenize + +import py +import pytest + +from pyfakefs.fake_filesystem_unittest import Patcher + +Patcher.SKIPMODULES.add(pytest) +Patcher.SKIPMODULES.add(py) +Patcher.SKIPMODULES.add(linecache) +Patcher.SKIPMODULES.add(tokenize) + +from pyfakefs.fake_filesystem_unittest import Patcher # noqa: E402 +from pyfakefs.pytest_tests import example # noqa: E402 + + +@pytest.fixture +def fs_reload_example(): + """ Fake filesystem. """ + patcher = Patcher(modules_to_reload=[example]) + patcher.setUp() + linecache.open = patcher.original_open + tokenize._builtin_open = patcher.original_open + yield patcher.fs + patcher.tearDown() diff --git a/pyfakefs/pytest_tests/example.py b/pyfakefs/pytest_tests/example.py new file mode 100644 index 0000000..62d619a --- /dev/null +++ b/pyfakefs/pytest_tests/example.py @@ -0,0 +1,25 @@ +# 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 as SUT for pytest_fixture_test.py + +try: + from pathlib2 import Path + + EXAMPLE_FILE = Path('/test') / 'file' +except ImportError: + try: + from pathlib import Path + + EXAMPLE_FILE = Path('/test') / 'file' + except ImportError: + EXAMPLE_FILE = None diff --git a/pyfakefs/pytest_tests/pytest_check_failed_plugin_test.py b/pyfakefs/pytest_tests/pytest_check_failed_plugin_test.py new file mode 100644 index 0000000..aebf948 --- /dev/null +++ b/pyfakefs/pytest_tests/pytest_check_failed_plugin_test.py @@ -0,0 +1,16 @@ +"""Tests that a failed pytest properly displays the call stack. +Uses the output from running pytest with pytest_plugin_failing_test.py. +Regression test for #381. +""" + + +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 diff --git a/pyfakefs/pytest_tests/pytest_doctest_test.py b/pyfakefs/pytest_tests/pytest_doctest_test.py new file mode 100644 index 0000000..1649eb5 --- /dev/null +++ b/pyfakefs/pytest_tests/pytest_doctest_test.py @@ -0,0 +1,48 @@ +""" +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: + + $ py.test --doctest-modules pytest_doctest_test.py + +Add `-s` option to enable print statements. +""" +from __future__ import unicode_literals + + +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) diff --git a/pyfakefs/pytest_tests/pytest_fixture_param_test.py b/pyfakefs/pytest_tests/pytest_fixture_param_test.py new file mode 100644 index 0000000..4ff6b46 --- /dev/null +++ b/pyfakefs/pytest_tests/pytest_fixture_param_test.py @@ -0,0 +1,43 @@ +# 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 +# Needs Python >= 3.6 + +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() diff --git a/pyfakefs/pytest_tests/pytest_fixture_test.py b/pyfakefs/pytest_tests/pytest_fixture_test.py new file mode 100644 index 0000000..e69e61e --- /dev/null +++ b/pyfakefs/pytest_tests/pytest_fixture_test.py @@ -0,0 +1,51 @@ +# 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 +# Needs Python >= 3.6 + +import pytest + +import pyfakefs.pytest_tests.example as example +from pyfakefs.fake_filesystem_unittest import Patcher + + +@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 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() diff --git a/pyfakefs/pytest_tests/pytest_plugin_failing_test.py b/pyfakefs/pytest_tests/pytest_plugin_failing_test.py new file mode 100644 index 0000000..dd4bab8 --- /dev/null +++ b/pyfakefs/pytest_tests/pytest_plugin_failing_test.py @@ -0,0 +1,6 @@ +""" Failing test to test stacktrace output - see +``pytest_check_failed_plugin_test.py``.""" + + +def test_fs(fs): + assert False diff --git a/pyfakefs/pytest_tests/pytest_plugin_test.py b/pyfakefs/pytest_tests/pytest_plugin_test.py new file mode 100644 index 0000000..61f8cdc --- /dev/null +++ b/pyfakefs/pytest_tests/pytest_plugin_test.py @@ -0,0 +1,39 @@ +"""Tests that the pytest plugin properly provides the "fs" fixture""" +import os +import tempfile + +from pyfakefs.fake_filesystem_unittest import Pause + + +def test_fs_fixture(fs): + fs.create_file('/var/data/xx1.txt') + assert os.path.exists('/var/data/xx1.txt') + + +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) diff --git a/pyfakefs/tests/__init__.py b/pyfakefs/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pyfakefs/tests/__init__.py diff --git a/pyfakefs/tests/all_tests.py b/pyfakefs/tests/all_tests.py new file mode 100644 index 0000000..f91ca24 --- /dev/null +++ b/pyfakefs/tests/all_tests.py @@ -0,0 +1,66 @@ +# 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.extra_packages import pathlib +from pyfakefs.tests import dynamic_patch_test, fake_stat_time_test +from pyfakefs.tests import fake_open_test +from pyfakefs.tests import fake_os_test +from pyfakefs.tests import example_test +from pyfakefs.tests import fake_filesystem_glob_test +from pyfakefs.tests import fake_filesystem_shutil_test +from pyfakefs.tests import fake_filesystem_test +from pyfakefs.tests import fake_filesystem_unittest_test +from pyfakefs.tests import fake_tempfile_test +from pyfakefs.tests import fake_filesystem_vs_real_test +from pyfakefs.tests import mox3_stubout_test + +if pathlib: + from pyfakefs.tests import fake_pathlib_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), + ]) + if pathlib: + self.addTests([ + loader.loadTestsFromModule(fake_pathlib_test) + ]) + return self + + +if __name__ == '__main__': + result = unittest.TextTestRunner(verbosity=2).run(AllTests().suite()) + sys.exit(int(not result.wasSuccessful())) diff --git a/pyfakefs/tests/all_tests_without_extra_packages.py b/pyfakefs/tests/all_tests_without_extra_packages.py new file mode 100644 index 0000000..28ecfad --- /dev/null +++ b/pyfakefs/tests/all_tests_without_extra_packages.py @@ -0,0 +1,42 @@ +# 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 pathlib2 and scandir packages.""" + +import sys +import unittest + +from pyfakefs import extra_packages + +if extra_packages.pathlib2: + extra_packages.pathlib2 = None + try: + import pathlib + except ImportError: + pathlib = None + extra_packages.pathlib = pathlib + +if extra_packages.use_scandir_package: + extra_packages.use_scandir_package = False + try: + from os import scandir + except ImportError: + scandir = None + extra_packages.scandir = scandir + extra_packages.use_scandir = scandir + +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())) diff --git a/pyfakefs/tests/dynamic_patch_test.py b/pyfakefs/tests/dynamic_patch_test.py new file mode 100644 index 0000000..f81c843 --- /dev/null +++ b/pyfakefs/tests/dynamic_patch_test.py @@ -0,0 +1,72 @@ +# 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 unittest + +from pyfakefs import fake_filesystem_unittest +from pyfakefs.extra_packages import pathlib + + +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(DynamicImportPatchTest, self).__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) + + @unittest.skipIf(not pathlib, 'only run if pathlib is available') + def test_pathlib_path_patch(self): + file_path = 'test.txt' + path = pathlib.Path(file_path) + with path.open('w') 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() diff --git a/pyfakefs/tests/example.py b/pyfakefs/tests/example.py new file mode 100644 index 0000000..5793cdd --- /dev/null +++ b/pyfakefs/tests/example.py @@ -0,0 +1,148 @@ +# 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 +<pyfakefs.fake_filesystem.FakeOsModule object...> +>>> os.path #doctest: +ELLIPSIS +<pyfakefs.fake_filesystem.FakePathModule object...> +>>> shutil #doctest: +ELLIPSIS +<pyfakefs.fake_filesystem_shutil.FakeShutilModule object...> + +`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') as f: + ... f.readlines() + ["This is test file '/test/file.txt'.\\n", \ +'It was created using open().\\n'] + """ + with open(path, 'w') as f: + f.write("This is test file '{0}'.\n".format(path)) + 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() diff --git a/pyfakefs/tests/example_test.py b/pyfakefs/tests/example_test.py new file mode 100644 index 0000000..b44a695 --- /dev/null +++ b/pyfakefs/tests/example_test.py @@ -0,0 +1,180 @@ +# 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.extra_packages import use_scandir_package +from pyfakefs.tests import example # The module under test + + +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\n' + 'Second 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(not use_scandir_package, + '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() diff --git a/pyfakefs/tests/fake_filesystem_glob_test.py b/pyfakefs/tests/fake_filesystem_glob_test.py new file mode 100644 index 0000000..87ccbe1 --- /dev/null +++ b/pyfakefs/tests/fake_filesystem_glob_test.py @@ -0,0 +1,74 @@ +# 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 glob +import os +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 = os.sep + '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 = os.sep + '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([os.sep + '[Temp]'], glob.glob('/*emp*')) + + def test_glob1(self): + 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() diff --git a/pyfakefs/tests/fake_filesystem_shutil_test.py b/pyfakefs/tests/fake_filesystem_shutil_test.py new file mode 100644 index 0000000..bfa167d --- /dev/null +++ b/pyfakefs/tests/fake_filesystem_shutil_test.py @@ -0,0 +1,511 @@ +# 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 pyfakefs import fake_filesystem_unittest +from pyfakefs.fake_filesystem import is_root +from pyfakefs.tests.test_utils import RealFsTestMixin + +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): + self.cwd = os.getcwd() + 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): + 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): + if self.use_real_fs(): + return sys.platform == 'win32' + return self.filesystem.is_windows_fs + + +class FakeShutilModuleTest(RealFsTestCase): + 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) + 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(): + 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): + 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): + self.assertRaises(OSError, shutil.rmtree, dir_path) + self.assertTrue(os.path.exists(dir_path)) + + def test_rmtree_non_existing_dir(self): + directory = 'nonexisting' + 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_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=2) + self.assertAlmostEqual(src_stat.st_mtime, dst_stat.st_mtime, places=2) + + 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=2) + 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=2) + 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)) + 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) + # root = self.os.path.splitdrive(file_path)[0] + self.fs.path_separator + 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 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(FakeCopyFileTest, self).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)) + self.assertRaises(shutil.Error, + shutil.copyfile, src_file, dst_file) + + def test_raises_if_dest_is_a_symlink_to_src(self): + 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)) + 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: + 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(): + 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)) + 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(): + 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)) + if self.is_windows_fs: + self.assertRaises(OSError, shutil.copyfile, src_file, dst_file) + else: + 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)) + if self.is_windows_fs: + self.assertRaises(OSError, shutil.copyfile, src_file, dst_dir) + else: + 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() diff --git a/pyfakefs/tests/fake_filesystem_test.py b/pyfakefs/tests/fake_filesystem_test.py new file mode 100644 index 0000000..2923366 --- /dev/null +++ b/pyfakefs/tests/fake_filesystem_test.py @@ -0,0 +1,2186 @@ +# -*- coding: utf-8 -*- +# 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 stat +import sys +import time +import unittest + +from pyfakefs import fake_filesystem +from pyfakefs.fake_filesystem import set_uid, set_gid, is_root, reset_ids +from pyfakefs.helpers import IS_WIN +from pyfakefs.tests.test_utils import DummyTime, TestCase + + +class FakeDirectoryUnitTest(TestCase): + def setUp(self): + self.orig_time = time.time + time.time = DummyTime(10, 1) + self.filesystem = fake_filesystem.FakeFilesystem(path_separator='/') + self.os = fake_filesystem.FakeOsModule(self.filesystem) + 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): + time.time = self.orig_time + + 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.contents) + self.assertEqual(10, self.fake_file.st_ctime) + + def test_add_entry(self): + self.fake_dir.add_entry(self.fake_file) + self.assertEqual({'foobar': self.fake_file}, self.fake_dir.contents) + + 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): + self.filesystem.root.add_entry(self.fake_dir) + self.fake_dir.add_entry(self.fake_file) + self.assertEqual('/somedir/foobar', self.fake_file.path) + self.assertEqual('/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): + 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(dir_path, 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') + self.assertRaises(KeyError, self.fake_dir.get_entry, 'foobar') + + def test_should_throw_if_set_size_is_not_integer(self): + def set_size(): + self.fake_file.size = 0.1 + + self.assert_raises_os_error(errno.ENOSPC, set_size) + + def test_should_throw_if_set_size_is_negative(self): + def set_size(): + self.fake_file.size = -1 + + self.assert_raises_os_error(errno.ENOSPC, set_size) + + 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 + self.assert_raises_os_error( + errno.EISDIR, self.fake_dir.set_contents, 'a') + self.filesystem.is_windows_fs = False + self.assert_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 = 13 + self.assertEqual(13, 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 = fake_filesystem.FakeOsModule(filesystem) + file_path = 'some_file1' + filesystem.create_file(file_path, contents='contents here1') + self.assertLess(0, fake_os.stat(file_path)[stat.ST_INO]) + + file_obj = filesystem.get_object(file_path) + file_obj.st_ino = 43 + self.assertEqual(43, fake_os.stat(file_path)[stat.ST_INO]) + + def test_directory_inode(self): + filesystem = fake_filesystem.FakeFilesystem(path_separator='/') + fake_os = fake_filesystem.FakeOsModule(filesystem) + dirpath = 'testdir' + filesystem.create_dir(dirpath) + self.assertLess(0, fake_os.stat(dirpath)[stat.ST_INO]) + + dir_obj = filesystem.get_object(dirpath) + dir_obj.st_ino = 43 + self.assertEqual(43, fake_os.stat(dirpath)[stat.ST_INO]) + + 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): + self.assert_raises_os_error(errno.ENOSPC, + self.fake_file.set_large_file_size, 0.1) + + def test_should_throw_if_size_is_negative(self): + self.assert_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 = '/' + + 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_absolute_path_remains_unchanged(self): + path = '/foo/bar' + self.assertEqual(path, self.filesystem.absnormpath(path)) + + def test_dotted_path_is_normalized(self): + path = '/foo/..' + self.assertEqual('/', self.filesystem.absnormpath(path)) + path = 'foo/../bar' + self.assertEqual('/bar', self.filesystem.absnormpath(path)) + + def test_dot_path_is_normalized(self): + path = '.' + self.assertEqual('/', 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.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.root_name, self.filesystem.root.name) + self.assertEqual({}, self.filesystem.root.contents) + + def test_none_raises_type_error(self): + 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, + 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.contents) + + 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 = '/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')) + self.assert_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) + self.assert_raises_os_error( + errno.ENOENT, self.filesystem.get_object, self.fake_file.name) + + def test_remove_nonexisten_object_from_root_error(self): + self.assert_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.get_entry(self.fake_child.name).contents) + + def test_add_object_to_regular_file_error_posix(self): + self.filesystem.is_windows_fs = False + self.filesystem.add_object(self.root_name, self.fake_file) + self.assert_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) + self.assert_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) + self.assert_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) + self.assert_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) + self.assert_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) + self.assert_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) + 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) + 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) + self.assert_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_directory_already_exists_error(self): + path = 'foo/bar/baz' + self.filesystem.create_dir(path) + self.assert_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(): + self.assert_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) + self.assert_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: + self.assertEqual(1, new_file.st_uid) + self.assertEqual(1, 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) as f: + self.assertEqual('', f.read()) + + def test_create_file_with_incorrect_mode_type(self): + 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') + self.assert_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') + self.assert_raises_os_error( + error_subtype, self.filesystem.resolve, 'not_a_dir/foo') + self.assert_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_filesystem.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('/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) + self.assert_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_filesystem.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.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') + 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') + 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 + self.assert_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_filesystem.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 l: l[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.orig_time = time.time + time.time = DummyTime(10, 1) + self.filesystem = fake_filesystem.FakeFilesystem(path_separator='!') + self.os = fake_filesystem.FakeOsModule(self.filesystem) + self.path = self.os.path + + def tearDown(self): + time.time = self.orig_time + + def check_abspath(self, is_windows): + # the implementation differs in Windows and Posix, so test both + self.filesystem.is_windows_fs = is_windows + filename = u'foo' + abspath = u'!%s' % 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(u'..!%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 = b'!' + 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) + basedir = '!%s' % (file_components[0],) + self.filesystem.create_file(filename) + self.os.chdir(basedir) + self.assertEqual(basedir, self.path.abspath(self.path.curdir)) + self.assertEqual('!', 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.assertTrue(self.path.isabs('!')) + self.filesystem.is_windows_fs = True + self.assertTrue(self.path.isabs('C:!foo')) + self.assertTrue(self.path.isabs('!')) + + def test_relpath(self): + path_foo = '!path!to!foo' + path_bar = '!path!to!bar' + path_other = '!some!where!else' + self.assertRaises(ValueError, self.path.relpath, None) + 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')) + + 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')) + + def test_exists(self): + file_path = 'foo!bar!baz' + self.filesystem.create_file(file_path) + self.assertTrue(self.path.exists(file_path)) + self.assertFalse(self.path.exists('!some!other!bogus!path')) + + def test_lexists(self): + file_path = 'foo!bar!baz' + self.filesystem.create_dir('foo!bar') + self.filesystem.create_symlink(file_path, 'bogus') + self.assertTrue(self.path.lexists(file_path)) + self.assertFalse(self.path.exists(file_path)) + 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(u'c:!foo', + self.path.dirname(u'c:!foo!bar')) + self.assertEqual(b'c:!', + self.path.dirname(b'c:!foo')) + self.assertEqual(u'!foo', + self.path.dirname(u'!foo!bar')) + self.assertEqual(b'!', + self.path.dirname(b'!foo')) + self.assertEqual(u'c:foo', + self.path.dirname(u'c:foo!bar')) + self.assertEqual(b'c:', + self.path.dirname(b'c:foo')) + self.assertEqual(u'foo', + self.path.dirname(u'foo!bar')) + + def test_dirname(self): + dirname = 'foo!bar' + self.assertEqual(dirname, self.path.dirname('%s!baz' % dirname)) + + def test_join_strings(self): + components = [u'foo', u'bar', u'baz'] + self.assertEqual(u'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)) + + def test_expand_user(self): + if self.is_windows: + self.assertEqual(self.path.expanduser('~'), + self.os.environ['USERPROFILE'].replace('\\', '!')) + else: + self.assertEqual(self.path.expanduser('~'), + self.os.environ['HOME'].replace('/', '!')) + + @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' + 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' + self.filesystem.create_file(file_path, contents='1234567') + self.assertEqual(7, self.path.getsize(file_path)) + + 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.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 = '!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.assertFalse(self.path.isfile('it_dont_exist')) + + def test_get_mtime(self): + test_file = self.filesystem.create_file('foo!bar1.txt') + time.time.start() + self.assertEqual(10, test_file.st_mtime) + test_file.st_mtime = 24 + self.assertEqual(24, self.path.getmtime('foo!bar1.txt')) + + def test_get_mtime_raises_os_error(self): + self.assertFalse(self.path.exists('it_dont_exist')) + self.assert_raises_os_error(errno.ENOENT, self.path.getmtime, + 'it_dont_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.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.assertFalse(self.path.ismount('!mount!')) + self.filesystem.add_mount_point('!mount') + self.assertTrue(self.path.ismount('!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 = '_joinrealpath' + 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')) + + +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 = fake_filesystem.FakeOsModule(filesystem) + self.assertEqual('!', fake_os.sep) + self.assertEqual('!', fake_os.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('/Foo/Bar', + self.filesystem._original_path('/foo/bar')) + self.assertEqual('/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('/Foo/Bar/baz', + self.filesystem._original_path('/foo/bar/baz')) + self.assertEqual('/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 = fake_filesystem.FakeOsModule(self.filesystem) + self.assertEqual('?', fake_os.altsep) + self.assertEqual('?', fake_os.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('!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.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 = u'' + self.assertEqual(u'c:!foo!bar', + self.filesystem.absnormpath(u'c:!foo!!bar')) + self.filesystem.cwd = u'c:!foo' + self.assertEqual(u'c:!foo!bar', self.filesystem.absnormpath(u'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((u'c:!foo', u'bar'), + self.filesystem.splitpath(u'c:!foo!bar')) + self.assertEqual((u'c:!', u'foo'), + self.filesystem.splitpath(u'c:!foo')) + self.assertEqual((u'!foo', u'bar'), + self.filesystem.splitpath(u'!foo!bar')) + self.assertEqual((u'!', u'foo'), + self.filesystem.splitpath(u'!foo')) + self.assertEqual((u'c:foo', u'bar'), + self.filesystem.splitpath(u'c:foo!bar')) + self.assertEqual((u'c:', u'foo'), + self.filesystem.splitpath(u'c:foo')) + self.assertEqual((u'foo', u'bar'), + self.filesystem.splitpath(u'foo!bar')) + + 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((u'c:', u'!foo!bar'), + self.filesystem.splitdrive(u'c:!foo!bar')) + self.assertEqual((u'', u'!foo!bar'), + self.filesystem.splitdrive(u'!foo!bar')) + self.assertEqual((u'c:', u'foo!bar'), + self.filesystem.splitdrive(u'c:foo!bar')) + self.assertEqual((u'', u'foo!bar'), + self.filesystem.splitdrive(u'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_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!!')) + + +class DiskSpaceTest(TestCase): + def setUp(self): + self.filesystem = fake_filesystem.FakeFilesystem(path_separator='!', + total_size=100) + self.os = fake_filesystem.FakeOsModule(self.filesystem) + + def test_disk_usage_on_file_creation(self): + fake_open = fake_filesystem.FakeFileOpen(self.filesystem) + + total_size = 100 + self.filesystem.add_mount_point('mount', total_size) + + def create_too_large_file(): + with fake_open('!mount!file', 'w') as dest: + dest.write('a' * (total_size + 1)) + + self.assertRaises(OSError, create_too_large_file) + + self.assertEqual(0, self.filesystem.get_disk_usage('!mount').used) + + with fake_open('!mount!file', 'w') as dest: + dest.write('a' * total_size) + + self.assertEqual(total_size, + self.filesystem.get_disk_usage('!mount').used) + + 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.filesystem.create_file('!foo!bar', contents=b'xyzzy') + self.assertEqual((100, 5, 95), self.filesystem.get_disk_usage()) + + def test_file_system_size_after_ascii_string_file_creation(self): + self.filesystem.create_file('!foo!bar', contents=u'complicated') + self.assertEqual((100, 11, 89), self.filesystem.get_disk_usage()) + + def test_filesystem_size_after_2byte_unicode_file_creation(self): + self.filesystem.create_file('!foo!bar', contents=u'сложно', + encoding='utf-8') + self.assertEqual((100, 12, 88), self.filesystem.get_disk_usage()) + + def test_filesystem_size_after_3byte_unicode_file_creation(self): + self.filesystem.create_file('!foo!bar', contents=u'複雑', + encoding='utf-8') + self.assertEqual((100, 6, 94), self.filesystem.get_disk_usage()) + + def test_file_system_size_after_file_deletion(self): + self.filesystem.create_file('!foo!bar', contents=b'xyzzy') + self.filesystem.create_file('!foo!baz', st_size=20) + self.filesystem.remove_object('!foo!bar') + self.assertEqual((100, 20, 80), self.filesystem.get_disk_usage()) + + def test_file_system_size_after_directory_removal(self): + self.filesystem.create_file('!foo!bar', st_size=10) + self.filesystem.create_file('!foo!baz', st_size=20) + self.filesystem.create_file('!foo1!bar', st_size=40) + self.filesystem.remove_object('!foo') + self.assertEqual((100, 40, 60), self.filesystem.get_disk_usage()) + + def test_creating_file_with_fitting_content(self): + initial_usage = self.filesystem.get_disk_usage() + + try: + self.filesystem.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.filesystem.get_disk_usage().used) + + def test_creating_file_with_content_too_large(self): + def create_large_file(): + self.filesystem.create_file('!foo!bar', contents=b'a' * 101) + + initial_usage = self.filesystem.get_disk_usage() + + self.assertRaises(OSError, create_large_file) + + self.assertEqual(initial_usage, self.filesystem.get_disk_usage()) + + def test_creating_file_with_fitting_size(self): + initial_usage = self.filesystem.get_disk_usage() + + try: + self.filesystem.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.filesystem.get_disk_usage().used) + + def test_creating_file_with_size_too_large(self): + initial_usage = self.filesystem.get_disk_usage() + + def create_large_file(): + self.filesystem.create_file('!foo!bar', st_size=101) + + self.assertRaises(OSError, create_large_file) + + self.assertEqual(initial_usage, self.filesystem.get_disk_usage()) + + def test_resize_file_with_fitting_size(self): + file_object = self.filesystem.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.filesystem.create_file('!foo!bar', st_size=50) + self.assert_raises_os_error(errno.ENOSPC, + file_object.set_large_file_size, 200) + self.assert_raises_os_error(errno.ENOSPC, file_object.set_contents, + 'a' * 150) + + def test_file_system_size_after_directory_rename(self): + self.filesystem.create_file('!foo!bar', st_size=20) + self.os.rename('!foo', '!baz') + self.assertEqual(20, self.filesystem.get_disk_usage().used) + + def test_file_system_size_after_file_rename(self): + self.filesystem.create_file('!foo!bar', st_size=20) + self.os.rename('!foo!bar', '!foo!baz') + self.assertEqual(20, self.filesystem.get_disk_usage().used) + + def test_that_hard_link_does_not_change_used_size(self): + file1_path = 'test_file1' + file2_path = 'test_file2' + self.filesystem.create_file(file1_path, st_size=20) + self.assertEqual(20, self.filesystem.get_disk_usage().used) + # creating a hard link shall not increase used space + self.os.link(file1_path, file2_path) + self.assertEqual(20, self.filesystem.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.filesystem.get_disk_usage().used) + self.os.unlink(file2_path) + self.assertEqual(0, self.filesystem.get_disk_usage().used) + + def test_that_the_size_of_correct_mount_point_is_used(self): + self.filesystem.add_mount_point('!mount_limited', total_size=50) + self.filesystem.add_mount_point('!mount_unlimited') + + self.assert_raises_os_error(errno.ENOSPC, + self.filesystem.create_file, + '!mount_limited!foo', st_size=60) + self.assert_raises_os_error(errno.ENOSPC, self.filesystem.create_file, + '!bar', st_size=110) + + try: + self.filesystem.create_file('!foo', st_size=60) + self.filesystem.create_file('!mount_limited!foo', st_size=40) + self.filesystem.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.filesystem.add_mount_point('!mount1', total_size=20) + self.filesystem.add_mount_point('!mount1!bar!mount2', total_size=50) + + self.filesystem.create_file('!foo!bar', st_size=10) + self.filesystem.create_file('!mount1!foo!bar', st_size=10) + self.filesystem.create_file('!mount1!bar!mount2!foo!bar', st_size=10) + + self.assertEqual(90, self.filesystem.get_disk_usage('!foo').free) + self.assertEqual(10, + self.filesystem.get_disk_usage('!mount1!foo').free) + self.assertEqual(40, self.filesystem.get_disk_usage( + '!mount1!bar!mount2').free) + + def test_set_larger_disk_size(self): + self.filesystem.add_mount_point('!mount1', total_size=20) + self.assert_raises_os_error(errno.ENOSPC, + self.filesystem.create_file, '!mount1!foo', + st_size=100) + self.filesystem.set_disk_usage(total_size=200, path='!mount1') + self.filesystem.create_file('!mount1!foo', st_size=100) + self.assertEqual(100, + self.filesystem.get_disk_usage('!mount1!foo').free) + + def test_set_smaller_disk_size(self): + self.filesystem.add_mount_point('!mount1', total_size=200) + self.filesystem.create_file('!mount1!foo', st_size=100) + self.assert_raises_os_error(errno.ENOSPC, + self.filesystem.set_disk_usage, + total_size=50, path='!mount1') + self.filesystem.set_disk_usage(total_size=150, path='!mount1') + self.assertEqual(50, + self.filesystem.get_disk_usage('!mount1!foo').free) + + def test_disk_size_on_unlimited_disk(self): + self.filesystem.add_mount_point('!mount1') + self.filesystem.create_file('!mount1!foo', st_size=100) + self.filesystem.set_disk_usage(total_size=1000, path='!mount1') + self.assertEqual(900, + self.filesystem.get_disk_usage('!mount1!foo').free) + + def test_disk_size_on_auto_mounted_drive_on_file_creation(self): + self.filesystem.is_windows_fs = True + # drive d: shall be auto-mounted and the used size adapted + self.filesystem.create_file('d:!foo!bar', st_size=100) + self.filesystem.set_disk_usage(total_size=1000, path='d:') + self.assertEqual(self.filesystem.get_disk_usage('d:!foo').free, 900) + + def test_disk_size_on_auto_mounted_drive_on_directory_creation(self): + self.filesystem.is_windows_fs = True + self.filesystem.create_dir('d:!foo!bar') + self.filesystem.create_file('d:!foo!bar!baz', st_size=100) + self.filesystem.create_file('d:!foo!baz', st_size=100) + self.filesystem.set_disk_usage(total_size=1000, path='d:') + self.assertEqual(self.filesystem.get_disk_usage('d:!foo').free, 800) + + def test_copying_preserves_byte_contents(self): + source_file = self.filesystem.create_file('foo', contents=b'somebytes') + dest_file = self.filesystem.create_file('bar') + dest_file.set_contents(source_file.contents) + self.assertEqual(dest_file.contents, source_file.contents) + + +class MountPointTest(TestCase): + def setUp(self): + self.filesystem = fake_filesystem.FakeFilesystem(path_separator='!', + total_size=100) + 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.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.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.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.assert_raises_os_error(errno.EEXIST, + self.filesystem.add_mount_point, '!foo') + self.assert_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.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.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.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 RealFileSystemAccessTest(TestCase): + 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') + 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' + self.assert_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) + self.assert_raises_os_error(errno.EEXIST, + self.filesystem.add_real_file, + real_file_path) + + def test_existing_fake_directory_raises(self): + self.filesystem.create_dir(self.root_path) + self.assert_raises_os_error(errno.EEXIST, + self.filesystem.add_real_directory, + self.root_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(): + self.assert_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') + self.assert_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') 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]) + + yield + + for link in symlinks: + os.unlink(link[1]) + + def test_add_existing_real_directory_symlink(self): + fake_open = fake_filesystem.FakeFileOpen(self.filesystem) + real_directory = os.path.join(self.root_path, 'pyfakefs', 'tests') + 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') 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(self.root_path, 'pyfakefs', 'tests', + 'fixtures/symlink_dir_relative'))) + self.assertTrue( + self.filesystem.exists( + os.path.join(self.root_path, 'pyfakefs', 'tests', + 'fixtures/symlink_dir_relative/all_tests.py'))) + self.assertTrue( + self.filesystem.exists( + os.path.join(self.root_path, 'pyfakefs', 'tests', + 'fixtures/symlink_file_relative'))) + + # absolute + self.assertTrue( + self.filesystem.exists( + os.path.join(self.root_path, 'pyfakefs', 'tests', + 'fixtures/symlink_dir_absolute'))) + self.assertTrue( + self.filesystem.exists( + os.path.join(self.root_path, 'pyfakefs', 'tests', + 'fixtures/symlink_dir_absolute/all_tests.py'))) + self.assertTrue( + self.filesystem.exists( + os.path.join(self.root_path, 'pyfakefs', 'tests', + 'fixtures/symlink_file_absolute'))) + + # outside + self.assertTrue( + self.filesystem.exists( + os.path.join(self.root_path, 'pyfakefs', 'tests', + 'fixtures/symlink_file_absolute_outside'))) + self.assertEqual( + fake_open(os.path.join( + self.root_path, 'pyfakefs', 'tests', + 'fixtures/symlink_file_absolute_outside')).read(), + 'good morning' + ) + + def test_add_existing_real_directory_symlink_target_path(self): + real_directory = os.path.join(self.root_path, 'pyfakefs', 'tests') + symlinks = [ + ('..', os.path.join( + real_directory, 'fixtures', 'symlink_dir_relative')), + ('../all_tests.py', os.path.join( + real_directory, 'fixtures', 'symlink_file_relative')), + ] + + try: + with self.create_symlinks(symlinks): + self.filesystem.add_real_directory( + real_directory, target_path='/path', lazy_read=False) + except OSError: + if self.is_windows: + raise unittest.SkipTest( + 'Symlinks under Windows need admin privileges') + raise + + 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): + real_directory = os.path.join(self.root_path, 'pyfakefs', 'tests') + symlinks = [ + ('..', os.path.join( + real_directory, 'fixtures', 'symlink_dir_relative')), + ('../all_tests.py', os.path.join( + real_directory, 'fixtures', 'symlink_file_relative')), + ] + + try: + 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')) + except OSError: + if self.is_windows: + raise unittest.SkipTest( + 'Symlinks under Windows need admin privileges') + raise + + def test_add_existing_real_directory_tree_to_existing_path(self): + self.filesystem.create_dir('/foo/bar') + self.assert_raises_os_error(errno.EEXIST, + self.filesystem.add_real_directory, + self.root_path, + target_path='/foo/bar') + + 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 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') 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') as handle: + handle.write('foo') + self.assertEquals(self.side_effect_file_object_content, 'foo') + + +if __name__ == '__main__': + unittest.main() diff --git a/pyfakefs/tests/fake_filesystem_unittest_test.py b/pyfakefs/tests/fake_filesystem_unittest_test.py new file mode 100644 index 0000000..b444211 --- /dev/null +++ b/pyfakefs/tests/fake_filesystem_unittest_test.py @@ -0,0 +1,650 @@ +# 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 io +import multiprocessing +import os +import shutil +import sys +import tempfile +import unittest +from distutils.dir_util import copy_tree, remove_tree +from unittest import TestCase + +import pyfakefs.tests.import_as_example +from pyfakefs import fake_filesystem_unittest, fake_filesystem +from pyfakefs.extra_packages import pathlib +from pyfakefs.fake_filesystem_unittest import Patcher, Pause, patchfs +from pyfakefs.tests.fixtures import module_with_attributes + + +class TestPatcher(TestCase): + def test_context_manager(self): + with Patcher() as patcher: + patcher.fs.create_file('/foo/bar', contents='test') + with open('/foo/bar') as f: + contents = f.read() + self.assertEqual('test', contents) + + @patchfs + def test_context_decorator(self, fs): + fs.create_file('/foo/bar', contents='test') + with open('/foo/bar') as f: + contents = f.read() + self.assertEqual('test', contents) + + +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') 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') as f: + content = f.read() + self.assertEqual(content, 'This test file was created using the ' + 'open() function.\n') + + 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') 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') as f: + content = f.read() + self.assertEqual(content, 'This test file was created using the ' + 'io.open() function.\n') + + 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(matching_paths, [r'\test\dir1\dir2a']) + else: + self.assertEqual(matching_paths, ['/test/dir1/dir2a']) + self.fs.create_dir('/test/dir1/dir2b') + matching_paths = sorted(glob.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_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')) + + @unittest.skipIf(not pathlib, "only run if pathlib is available") + def test_fakepathlib(self): + with pathlib.Path('/fake_file.txt') as p: + with p.open('w') 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)) + + if pathlib: + 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_function_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_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_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(TestPyfakefsUnittestBase): + 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)) + + +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 test_path_exists(self): + self.assertTrue( + pyfakefs.tests.import_as_example.exists_this_file()) + + +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.assertFalse( + pyfakefs.tests.import_as_example.exists_this_file()) + + +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.assertFalse( + pyfakefs.tests.import_as_example.exists_this_file()) + + +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, fs): + file_path = '/foo/bar' + 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, fs): + file_path = '/foo/bar' + 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' + self.assertRaises(OSError, self.fs.create_file, file_path) + + file_path = '/baz' + self.fs.create_file(file_path) + os.chmod(file_path, 0o400) + self.assertRaises(OSError, open, file_path, 'w') + + +class PauseResumeTest(TestPyfakefsUnittestBase): + 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() + self.assertRaises(RuntimeError, fs.resume) + + +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)) + + 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)) + + +class TestCopyOrAddRealFile(TestPyfakefsUnittestBase): + """Tests the `fake_filesystem_unittest.TestCase.copyRealFile()` method. + Note that `copyRealFile()` is deprecated in favor of + `FakeFilesystem.add_real_file()`. + """ + filepath = None + + @classmethod + def setUpClass(cls): + filename = __file__ + if filename.endswith('.pyc'): # happens on windows / py27 + filename = filename[:-1] + cls.filepath = os.path.abspath(filename) + with open(cls.filepath) as f: + cls.real_string_contents = f.read() + with open(cls.filepath, 'rb') as f: + cls.real_byte_contents = f.read() + cls.real_stat = os.stat(cls.filepath) + + @unittest.skipIf(sys.platform == 'darwin', 'Different copy behavior') + def test_copy_real_file(self): + """Typical usage of deprecated copyRealFile()""" + # Use this file as the file to be copied to the fake file system + fake_file = self.copyRealFile(self.filepath) + + self.assertTrue( + 'class TestCopyOrAddRealFile(TestPyfakefsUnittestBase)' + in self.real_string_contents, + 'Verify real file string contents') + self.assertTrue( + b'class TestCopyOrAddRealFile(TestPyfakefsUnittestBase)' + in self.real_byte_contents, + 'Verify real file byte contents') + + # note that real_string_contents may differ to fake_file.contents + # due to newline conversions in open() + self.assertEqual(fake_file.byte_contents, self.real_byte_contents) + + self.assertEqual(oct(fake_file.st_mode), oct(self.real_stat.st_mode)) + self.assertEqual(fake_file.st_size, self.real_stat.st_size) + self.assertAlmostEqual(fake_file.st_ctime, + self.real_stat.st_ctime, places=5) + self.assertAlmostEqual(fake_file.st_atime, + self.real_stat.st_atime, places=5) + self.assertLess(fake_file.st_atime, self.real_stat.st_atime + 10) + self.assertAlmostEqual(fake_file.st_mtime, + self.real_stat.st_mtime, places=5) + self.assertEqual(fake_file.st_uid, self.real_stat.st_uid) + self.assertEqual(fake_file.st_gid, self.real_stat.st_gid) + + def test_copy_real_file_deprecated_arguments(self): + """Deprecated copyRealFile() arguments""" + self.assertFalse(self.fs.exists(self.filepath)) + # Specify redundant fake file path + self.copyRealFile(self.filepath, self.filepath) + self.assertTrue(self.fs.exists(self.filepath)) + + # Test deprecated argument values + with self.assertRaises(ValueError): + self.copyRealFile(self.filepath, '/different/filename') + with self.assertRaises(ValueError): + self.copyRealFile(self.filepath, create_missing_dirs=False) + + def test_add_real_file(self): + """Add a real file to the fake file system to be read on demand""" + + # this tests only the basic functionality inside a unit test, more + # thorough tests are done in + # fake_filesystem_test.RealFileSystemAccessTest + fake_file = self.fs.add_real_file(self.filepath) + self.assertTrue(self.fs.exists(self.filepath)) + self.assertIsNone(fake_file._byte_contents) + self.assertEqual(self.real_byte_contents, fake_file.byte_contents) + + def test_add_real_directory(self): + """Add a real directory and the contained files to the fake file system + to be read on demand""" + + # This tests only the basic functionality inside a unit test, + # more thorough tests are done in + # fake_filesystem_test.RealFileSystemAccessTest. + # Note: this test fails (add_real_directory raises) if 'genericpath' + # is not added to SKIPNAMES + real_dir_path = os.path.split(os.path.dirname(self.filepath))[0] + self.fs.add_real_directory(real_dir_path) + self.assertTrue(self.fs.exists(real_dir_path)) + self.assertTrue(self.fs.exists( + os.path.join(real_dir_path, 'fake_filesystem.py'))) + + def test_add_real_directory_with_backslash(self): + """Add a real directory ending with a path separator.""" + real_dir_path = os.path.split(os.path.dirname(self.filepath))[0] + self.fs.add_real_directory(real_dir_path + os.sep) + self.assertTrue(self.fs.exists(real_dir_path)) + self.assertTrue(self.fs.exists( + os.path.join(real_dir_path, 'fake_filesystem.py'))) + + +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 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') + + +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/')) + + +if __name__ == "__main__": + unittest.main() diff --git a/pyfakefs/tests/fake_filesystem_vs_real_test.py b/pyfakefs/tests/fake_filesystem_vs_real_test.py new file mode 100644 index 0000000..756d7cd --- /dev/null +++ b/pyfakefs/tests/fake_filesystem_vs_real_test.py @@ -0,0 +1,637 @@ +# 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 + + +def sep(path): + """Converts slashes in the path to the architecture's path seperator.""" + 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 TestCase(unittest.TestCase): + is_windows = sys.platform.startswith('win') + _FAKE_FS_BASE = sep('/fakefs') + + +class FakeFilesystemVsRealTest(TestCase): + 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') + fh.write(contents or '') + fh.close() + fh = self.fake_open(fake_path, 'w') + 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'): + 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) + self.fake_base = self._FAKE_FS_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_filesystem.FakeOsModule(self.fake_filesystem) + self.fake_open = fake_filesystem.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_filsystem 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 '{}({})'.format(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 = '%s' % method_name + method_call += '()' if path == () else '(%s)' % path + # We only compare on the error class because the acutal 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 '%s: real version returned %s, fake raised %s' % ( + method_call, real_value, _error_class(fake_err)) + if fake_err is None: + return '%s: real version raised %s, fake returned %s' % ( + method_call, _error_class(real_err), fake_value) + return '%s: real version raised %s, fake raised %s' % ( + 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 '%s(%s): both raised %s, real errno %s, fake errno %s' % ( + 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 '%s: real return %s, fake returned %s' % ( + 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] + fake_value = str(fake_method(*args)) + 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) + real_value = str(real_method(*args)) + 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 fkae 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. + """ + with open(path, mode) as real_fh: + with self.fake_open(path, mode) 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_filesytem 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): + path = sep(path) + os_method_names = [] if self.is_windows else ['readlink'] + os_method_names_no_args = ['getcwd'] + os_path_method_names = ['isabs', + 'isdir', + 'isfile', + 'exists' + ] + if not self.is_windows: + os_path_method_names.append('islink') + os_path_method_names.append('lexists') + wrapped_methods = [ + ['access', self._access_real, self._access_fake], + ['stat.size', self._stat_size_real, self._stat_size_fake], + ['lstat.size', self._lstat_size_real, self._lstat_size_fake] + ] + + 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))) + + # 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_size_real(self, path): + real_path, unused_fake_path = self._paths(path) + # fake_filesystem.py does not implement stat().st_size for directories + if os.path.isdir(real_path): + return None + return os.stat(real_path).st_size + + def _stat_size_fake(self, path): + unused_real_path, fake_path = self._paths(path) + # fake_filesystem.py does not implement stat().st_size for directories + if self.fake_os.path.isdir(fake_path): + return None + return self.fake_os.stat(fake_path).st_size + + 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('/') + + def test_non_existant_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') + + @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') + def test_sym_link_to_empty_file(self): + self._create_test_file('f', 'aFile') + self._create_test_file('l', 'link_to_empty', 'aFile') + self.assertAllOsBehaviorsMatch('link_to_empty') + + @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') + def test_hard_link_to_empty_file(self): + self._create_test_file('f', 'aFile') + self._create_test_file('h', 'link_to_empty', 'aFile') + self.assertAllOsBehaviorsMatch('link_to_empty') + + @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') + def test_sym_link_to_real_file(self): + self._create_test_file('f', 'aFile', 'some contents') + self._create_test_file('l', 'link_to_file', 'aFile') + self.assertAllOsBehaviorsMatch('link_to_file') + + @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') + def test_hard_link_to_real_file(self): + self._create_test_file('f', 'aFile', 'some contents') + self._create_test_file('h', 'link_to_file', 'aFile') + self.assertAllOsBehaviorsMatch('link_to_file') + + @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') + def test_broken_sym_link(self): + self._create_test_file('l', 'broken_link', 'broken') + self._create_test_file('l', 'loop', '/a/loop') + 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') + + @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') + def test_absolute_sym_link_to_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._create_test_file('l', 'a/link', '/a/b') + self.assertAllOsBehaviorsMatch('a/link/file') + + @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') + def test_link_to_folder_after_chdir(self): + 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') + + @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') + def test_relative_sym_link_to_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._create_test_file('l', 'a/link', 'b') + self.assertAllOsBehaviorsMatch('a/link/file') + + @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') + def test_sym_link_to_parent(self): + # Soft links on HFS+ / OS X behave differently. + if os.uname()[0] != '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') + + @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') + def test_path_through_sym_link_to_parent(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('l', 'a/b/c', '..') + self.assertAllOsBehaviorsMatch('a/b/c/target') + + @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') + def test_sym_link_to_sibling_directory(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._create_test_file('l', 'a/b/c', '../sibling_of_b') + self.assertAllOsBehaviorsMatch('a/b/c/target') + + @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') + def test_sym_link_to_sibling_directory_non_existant_file(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._create_test_file('l', 'a/b/c', '../sibling_of_b') + self.assertAllOsBehaviorsMatch('a/b/c/file_does_not_exist') + + @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') + def test_broken_sym_link_to_sibling_directory(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._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_nonexistant_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') + + +def main(_): + unittest.main() + + +if __name__ == '__main__': + unittest.main() diff --git a/pyfakefs/tests/fake_open_test.py b/pyfakefs/tests/fake_open_test.py new file mode 100644 index 0000000..5786af2 --- /dev/null +++ b/pyfakefs/tests/fake_open_test.py @@ -0,0 +1,1647 @@ +# -*- coding: utf-8 -*- +# +# 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_filesystem.FakeOsModule.""" + +import errno +import io +import locale +import os +import stat +import time +import unittest + +from pyfakefs import fake_filesystem +from pyfakefs.fake_filesystem import is_root, PERM_READ +from pyfakefs.tests.test_utils import RealFsTestCase + + +class FakeFileOpenTestBase(RealFsTestCase): + def path_separator(self): + return '!' + + +class FakeFileOpenTest(FakeFileOpenTestBase): + def setUp(self): + super(FakeFileOpenTest, self).setUp() + self.orig_time = time.time + + def tearDown(self): + super(FakeFileOpenTest, self).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'): + 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'): + 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'): + 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' + with self.open(file_path, 'w') as f: + f.write(text_fractions) + with self.open(file_path) as f: + contents = f.read() + self.assertEqual(contents, text_fractions) + + def test_byte_contents_py3(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' + with self.open(file_path, 'w') as f: + f.write(str_contents) + with self.open(file_path, 'rb') as f: + contents = f.read() + self.assertEqual(str_contents, contents.decode( + locale.getpreferredencoding(False))) + + 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) + with self.open(file_path, 'rb') as f: + contents = f.read() + self.assertEqual(contents, byte_fractions) + + 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) 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, buffering=1) as f: + self.assertEqual(contents, f.readlines()) + with self.open(file_path, 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) + self.assertEqual(contents, self.open(file_path).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) 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) 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') as fake_file: + for line in contents: + fake_file.write(line + '\n') + with self.open(file_path) 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') as fake_file: + for line in contents: + fake_file.write(line + '\n') + with self.open(file_path) as fake_file: + result = [line.rstrip() for line in fake_file] + self.assertEqual(contents, result) + + def test_exclusive_create_file_failure(self): + 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') as fake_file: + fake_file.write(contents) + with self.open(file_path) 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('overwite') + self.create_file(file_path, contents='To disappear') + new_contents = [ + 'Only these lines', + 'should be in the file.', + ] + with self.open(file_path, 'w') as fake_file: + for line in new_contents: + fake_file.write(line + '\n') + with self.open(file_path) 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 file' + 'Appended contents', + ] + + self.create_file(file_path, contents=contents[0]) + with self.open(file_path, 'a') as fake_file: + for line in contents[1:]: + fake_file.write(line + '\n') + with self.open(file_path) 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') as fake_file: + self.assertEqual('old contents', fake_file.read()) + # actual tests + with self.open(file_path, 'w+') 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') as fake_file: + self.assertEqual('old contents', fake_file.read()) + # actual tests + with self.open(file_path, 'w+') 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') as fake_file: + self.assertRaises(io.UnsupportedOperation, fake_file.read, 0) + 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) 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') 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+') 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') as fake_file: + self.assertEqual('old contents', fake_file.read()) + # actual tests + with self.open(file_path, 'a+') 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+') 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') as fake_file: + self.assertEqual('old contents here', fake_file.read()) + # actual tests + with self.open(file_path, 'r+') 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 & 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').close() + self.open(file_path, 'w').close() + self.open(file_path, 'w+').close() + self.assertRaises(ValueError, self.open, file_path, 'INV') + + 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').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').close() + self.open(file_path, 'w+').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').close() + if not is_root(): + self.assertRaises(OSError, self.open, file_path, 'r') + self.assertRaises(OSError, self.open, file_path, 'w+') + else: + self.open(file_path, 'r').close() + self.open(file_path, 'w+').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(): + self.assertRaises(OSError, self.open, file_path, 'r') + self.assertRaises(OSError, self.open, file_path, 'w') + self.assertRaises(OSError, self.open, file_path, 'w+') + else: + self.open(file_path, 'r').close() + self.open(file_path, 'w').close() + self.open(file_path, 'w+').close() + + def test_follow_link_read(self): + 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') + got_contents = fh.read() + fh.close() + self.assertEqual(target_contents, got_contents) + + def test_follow_link_write(self): + 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') as fh: + fh.write(target_contents) + with self.open(target, 'r') 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. + self.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') as fh: + fh.write(target_contents) + with self.open(target, 'r') 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) as fake_file1: + with self.open(second_path) as fake_file2: + with self.open(third_path) 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) as fake_file1: + with self.open(second_path) as fake_file2: + with self.open(first_path) as fake_file1a: + fileno2 = fake_file2.fileno() + self.assertGreater(fileno2, fake_file1.fileno()) + self.assertGreater(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') as fake_file1: + with self.open(second_path, 'r') as fake_file2: + fake_file3 = self.open(third_path, 'r') + fake_file1a = self.open(first_path, 'r') + fileno1 = fake_file1.fileno() + fileno2 = fake_file2.fileno() + fileno3 = fake_file3.fileno() + fileno4 = fake_file1a.fileno() + + with self.open(second_path, 'r') as fake_file2: + with self.open(first_path, 'r') 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') as writer: + with self.open(file_path, 'r') 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') as fh: + self.assertRaises(OSError, fh.read) + self.assertRaises(OSError, fh.readlines) + with self.open(file_path, 'w') as fh: + self.assertRaises(OSError, fh.read) + self.assertRaises(OSError, fh.readlines) + with self.open(file_path, 'r') as fh: + self.assertRaises(OSError, fh.truncate) + self.assertRaises(OSError, fh.write, 'contents') + self.assertRaises(OSError, fh.writelines, ['con', 'tents']) + + def _iterator_open(mode): + for _ in self.open(file_path, mode): + pass + + self.assertRaises(OSError, _iterator_open, 'w') + 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 + | fake_filesystem.PERM_ALL) + with self.open(device_path, 'r') 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') 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') + f0 = self.open(file_path, 'w') + f1 = self.open(file_path, 'w') + 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') 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') 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) 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') + fake_file.close() + self.assertRaises(ValueError, lambda: fake_file.read(1)) + self.assertRaises(ValueError, lambda: fake_file.write('a')) + self.assertRaises(ValueError, lambda: fake_file.readline()) + self.assertRaises(ValueError, lambda: fake_file.truncate()) + self.assertRaises(ValueError, lambda: fake_file.tell()) + self.assertRaises(ValueError, lambda: fake_file.seek(1)) + self.assertRaises(ValueError, lambda: 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') + fake_file.close() + self.assertRaises(ValueError, lambda: fake_file.read(1)) + self.assertRaises(ValueError, lambda: 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') 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') 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+') 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+') 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') 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') 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) 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') + self.assertFalse(f.closed) + f.close() + self.assertTrue(f.closed) + f = self.open(file_path) + 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') + f0.close() + with self.open(file_path) 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') as f0: + with self.open(file_path) 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(u'тест') + 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) as f: + f.write('test') + with self.open(self.os.devnull) as f: + self.assertEqual('', f.read()) + + +class RealFileOpenTest(FakeFileOpenTest): + 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(OpenFileWithEncodingTest, self).setUp() + if self.use_real_fs(): + self.open = io.open + else: + self.open = fake_filesystem.FakeFileOpen(self.filesystem) + self.file_path = self.make_path('foo') + + def test_write_str_read_bytes(self): + str_contents = u'علي بابا' + 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 = u'علي بابا' + with self.open(self.file_path, 'w', encoding='cyrillic') as f: + 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 = u'علي بابا' + 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: + 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 = u'علي بابا' + 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 = [ + u'Allons enfants de la Patrie,' + u'Le jour de gloire est arrivé!', + u'Contre nous de la tyrannie,', + u'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 = [ + u'Оригинальное содержание' + u'Дополнительное содержание', + ] + 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=u'старое содержание', + encoding='cyrillic') + with self.open(self.file_path, 'r', encoding='cyrillic') as fake_file: + self.assertEqual(u'старое содержание', fake_file.read()) + + with self.open(self.file_path, 'w+', encoding='cyrillic') as fake_file: + fake_file.write(u'новое содержание') + fake_file.seek(0) + self.assertTrue(u'новое содержание', fake_file.read()) + + def test_open_with_append_flag(self): + contents = [ + u'Калинка,\n', + u'калинка,\n', + u'калинка моя,\n' + ] + additional_contents = [ + u'В саду ягода-малинка,\n', + u'малинка моя.\n' + ] + self.create_file(self.file_path, contents=''.join(contents), + encoding='cyrillic') + with self.open(self.file_path, 'a', encoding='cyrillic') as fake_file: + self.assertRaises(io.UnsupportedOperation, fake_file.read, 0) + 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=u'старое содержание', + 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(u'новое содержание') + self.assertEqual(33, fake_file.tell()) + fake_file.seek(0) + self.assertEqual(u'старое содержаниеновое содержание', + fake_file.read()) + + def test_read_with_rplus(self): + self.create_file(self.file_path, + contents=u'старое содержание здесь', + 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(u'старое содержание здесь', fake_file.read()) + fake_file.seek(0) + fake_file.write(u'новое содержание') + fake_file.seek(0) + self.assertEqual(u'новое содержание здесь', fake_file.read()) + + +class OpenRealFileWithEncodingTest(OpenFileWithEncodingTest): + def use_real_fs(self): + return True + + +class FakeFileOpenLineEndingTest(FakeFileOpenTestBase): + def setUp(self): + super(FakeFileOpenLineEndingTest, self).setUp() + + def test_read_universal_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='rU') as f: + self.assertEqual(['1\n', '2'], f.readlines()) + with self.open(file_path, mode='rU') 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') 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') 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', newline='') as f: + self.assertEqual('1\r\n2\n3\r4', f.read()) + with self.open(file_path, mode='r', newline='\r') as f: + self.assertEqual('1\r\n2\n3\r4', f.read()) + with self.open(file_path, mode='r', newline='\n') as f: + self.assertEqual('1\r\n2\n3\r4', f.read()) + with self.open(file_path, mode='r', 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', newline='') as f: + self.assertEqual(['1\r\n', '2\n', '3\r', '4'], + f.readlines()) + with self.open(file_path, mode='r', newline='\r') as f: + self.assertEqual(['1\r', '\n2\n3\r', '4'], f.readlines()) + with self.open(file_path, mode='r', newline='\n') as f: + self.assertEqual(['1\r\n', '2\n', '3\r4'], f.readlines()) + with self.open(file_path, mode='r', newline='\r\n') as f: + self.assertEqual(['1\r\n', '2\n3\r4'], f.readlines()) + + 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', newline='\r') as f: + self.assertEqual('1\r\n2\n3\r4', f.read()) + with self.open(file_path, mode='r', newline='\r') as f: + self.assertEqual('1\r\n2\n3\r4', f.read()) + with self.open(file_path, mode='U', newline='\r') as f: + self.assertEqual('1\r\n2\n3\r4', f.read()) + + def test_write_with_newline_arg(self): + file_path = self.make_path('some_file') + with self.open(file_path, 'w', 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', 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', 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', 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(FakeFileOpenLineEndingWithEncodingTest, self).setUp() + if self.use_real_fs(): + self.open = io.open + else: + self.open = fake_filesystem.FakeFileOpen(self.filesystem) + + def test_read_universal_newline_mode(self): + file_path = self.make_path('some_file') + for contents in (u'раз\nдва', u'раз\r\nдва', u'раз\rдва'): + self.create_file(file_path, contents=contents, encoding='cyrillic') + with self.open(file_path, mode='rU', + encoding='cyrillic') as fake_file: + self.assertEqual([u'раз\n', u'два'], fake_file.readlines()) + with self.open(file_path, mode='rU', + encoding='cyrillic') as fake_file: + self.assertEqual(u'раз\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(u'раз\nдва') + with self.open(file_path, mode='rb') as f: + self.assertEqual(u'раз'.encode('cyrillic') + + self.os.linesep.encode() + + u'два'.encode('cyrillic'), f.read()) + + with self.open(file_path, 'w', encoding='cyrillic') as f: + f.write(u'раз\r\nдва') + with self.open(file_path, mode='rb') as f: + self.assertEqual(u'раз\r'.encode('cyrillic') + + self.os.linesep.encode() + + u'два'.encode('cyrillic'), f.read()) + + def test_read_with_newline_arg(self): + file_path = self.make_path('some_file') + file_contents = u'раз\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(u'раз\r\nдва\nтри\rчетыре', f.read()) + with self.open(file_path, mode='r', newline='\r', + encoding='cyrillic') as f: + self.assertEqual(u'раз\r\nдва\nтри\rчетыре', f.read()) + with self.open(file_path, mode='r', newline='\n', + encoding='cyrillic') as f: + self.assertEqual(u'раз\r\nдва\nтри\rчетыре', f.read()) + with self.open(file_path, mode='r', newline='\r\n', + encoding='cyrillic') as f: + self.assertEqual(u'раз\r\nдва\nтри\rчетыре', f.read()) + + def test_readlines_with_newline_arg(self): + file_path = self.make_path('some_file') + file_contents = u'раз\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([u'раз\r\n', u'два\n', u'три\r', u'четыре'], + f.readlines()) + with self.open(file_path, mode='r', newline='\r', + encoding='cyrillic') as f: + self.assertEqual([u'раз\r', u'\nдва\nтри\r', u'четыре'], + f.readlines()) + with self.open(file_path, mode='r', newline='\n', + encoding='cyrillic') as f: + self.assertEqual([u'раз\r\n', u'два\n', u'три\rчетыре'], + f.readlines()) + with self.open(file_path, mode='r', newline='\r\n', + encoding='cyrillic') as f: + self.assertEqual([u'раз\r\n', u'два\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(u'раз\r\nдва\nтри\rчетыре') + with self.open(file_path, mode='rb') as f: + self.assertEqual(u'раз\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(u'раз\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(u'раз\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(u'раз\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').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', closefd=False) + fh.close() + self.assertIsNotNone(self.filesystem.open_files[fd]) + fh = self.open(fd, 'r', 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(OpenWithFlagsTestBase, self).setUp() + self.file_path = self.make_path('some_file') + self.file_contents = None + + def open_file(self, mode): + return self.open(self.file_path, mode=mode) + + 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(OpenWithBinaryFlagsTest, self).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): + fake_file = self.open_file('rb') + self.assertEqual(self.file_contents, fake_file.read()) + + def test_write_binary(self): + fake_file = self.open_file_and_seek('wb') + self.assertEqual(0, fake_file.tell()) + fake_file = self.write_and_reopen_file(fake_file, mode='rb') + self.assertEqual(self.file_contents, fake_file.read()) + # Attempt to reopen the file in text mode + fake_file = self.open_file('wb') + fake_file = self.write_and_reopen_file(fake_file, mode='r', + encoding='ascii') + self.assertRaises(UnicodeDecodeError, fake_file.read) + + def test_write_and_read_binary(self): + fake_file = self.open_file_and_seek('w+b') + self.assertEqual(0, fake_file.tell()) + fake_file = self.write_and_reopen_file(fake_file, mode='rb') + self.assertEqual(self.file_contents, fake_file.read()) + + +class RealOpenWithBinaryFlagsTest(OpenWithBinaryFlagsTest): + def use_real_fs(self): + return True + + +class OpenWithTextModeFlagsTest(OpenWithFlagsTestBase): + def setUp(self): + super(OpenWithTextModeFlagsTest, self).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): + 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): + self.assertRaises(ValueError, self.open, 'some_file', 'R') + + def test_capital_w(self): + self.assertRaises(ValueError, self.open, 'some_file', 'W') + + def test_capital_a(self): + self.assertRaises(ValueError, self.open, 'some_file', 'A') + + def test_lower_u(self): + self.assertRaises(ValueError, self.open, 'some_file', 'u') + + def test_lower_rw(self): + 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') as fh: + fh.write('x') + + def test_none_filepath_raises_type_error(self): + self.assertRaises(TypeError, self.open, None, 'w') + + def test_empty_filepath_raises_io_error(self): + self.assertRaises(OSError, self.open, '', 'w') + + 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): + 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): + 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): + 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): + 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): + 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): + 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.EBADF, self.os.chdir, 10) + 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. + self.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') + self.assertEqual('x', fh.read()) + + def test_write_link_to_link(self): + 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): + 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)""" + self.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 + + +if __name__ == '__main__': + unittest.main() diff --git a/pyfakefs/tests/fake_os_test.py b/pyfakefs/tests/fake_os_test.py new file mode 100644 index 0000000..4b24dee --- /dev/null +++ b/pyfakefs/tests/fake_os_test.py @@ -0,0 +1,5182 @@ +# -*- coding: utf-8 -*- +# +# 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_filesystem.FakeOpen.""" + +import errno +import os +import stat +import sys +import time +import unittest + +from pyfakefs.helpers import IN_DOCKER + +from pyfakefs import fake_filesystem +from pyfakefs.fake_filesystem import FakeFileOpen, is_root +from pyfakefs.extra_packages import ( + use_scandir, use_scandir_package, use_builtin_scandir +) + +from pyfakefs.tests.test_utils import DummyTime, TestCase, RealFsTestCase + + +class FakeOsModuleTestBase(RealFsTestCase): + def createTestFile(self, path): + self.create_file(path) + self.assertTrue(self.os.path.exists(path)) + st = self.os.stat(path) + self.assertEqual(0o666, 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) + self.assertEqual(0o777, 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(FakeOsModuleTest, self).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.os.getcwd(), self.os.path.sep) + self.os.chdir(dirname) + self.assertEqual(self.os.getcwd(), dirname) + + 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): + 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') as fake_file1: + fileno = fake_file1.fileno() + fake_file2 = self.os.fdopen(fileno) + self.assertNotEqual(fake_file2, fake_file1) + + self.assertRaises(TypeError, self.os.fdopen, None) + 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 + self.assert_raises_os_error(errno.EBADF, self.os.fdopen, 100) + + 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') + fake_file2 = self.open(second_path, 'r') + fake_file3 = self.open(third_path, 'r') + 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) as f: + self.assertFalse(f is fake_file1) + with self.os.fdopen(fileno3) as f: + self.assertFalse(f is fake_file3) + self.assert_raises_os_error(errno.EBADF, self.os.fdopen, fileno2) + + def test_fdopen_mode(self): + self.skip_real_fs() + file_path1 = self.make_path('some_file1') + self.create_file(file_path1, contents='contents here1') + self.os.chmod(file_path1, (stat.S_IFREG | 0o666) ^ stat.S_IWRITE) + + fake_file1 = self.open(file_path1, 'r') + fileno1 = fake_file1.fileno() + self.os.fdopen(fileno1) + self.os.fdopen(fileno1, 'r') + if not is_root(): + self.assertRaises(OSError, self.os.fdopen, fileno1, 'w') + else: + self.os.fdopen(fileno1, 'w') + self.os.close(fileno1) + + 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) 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_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) 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() + self.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() + self.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 = self.os.lstat(self.base_path) + self.assertEqual(stat, + self.os.lstat(self.base_path + self.path_separator())) + self.assertEqual(stat, 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() + self.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() + self.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() + self.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() + self.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() + self.skip_if_symlink_not_supported() + self.check_remove_link_ending_with_sep(errno.EACCES) + + def test_lstat_uses_open_fd_as_path(self): + 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) 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) + + def test_lexists_with_trailing_separator_linux_windows(self): + self.check_linux_and_windows() + self.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() + self.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 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): + 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() + self.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() + self.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): + 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): + self.skip_if_symlink_not_supported() + 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() + self.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() + self.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): + 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): + 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() + 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'): + 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') + 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): + 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() + self.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): + self.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) + self.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): + 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 + 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') as f0: + with self.open(file_path, 'a') 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') 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') 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, check_read_perm=False) + 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() + self.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('/%s/%s' % (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 exeption 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 = '%s/foo' % (parent,) + 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() + self.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): + 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(): + self.assertRaises(Exception, 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) + + # test fsync and fdatasync + def test_fsync_raises_on_non_int(self): + 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, 100) + + 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, 100) + + 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') 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 + 10) + + 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+') 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 + 10) + with self.open(test_file_path, 'r') 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') + 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 + 10) + + 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): + 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 + self.assertTrue( + self.os.access(link_path, self.os.F_OK, follow_symlinks=False)) + self.assertTrue( + self.os.access(link_path, self.os.R_OK, follow_symlinks=False)) + self.assertTrue( + self.os.access(link_path, self.os.W_OK, follow_symlinks=False)) + 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=False)) + + 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_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): + 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) as f: + self.os.chmod(f.filedes, 0o6543) + st = self.os.stat(path) + self.assert_mode_equal(0o6543, st.st_mode) + + def test_chmod_follow_symlink(self): + self.check_posix_only() + if self.use_real_fs() and 'chmod' not in os.supports_follow_symlinks: + raise unittest.SkipTest('follow_symlinks not available') + 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) + self.assert_mode_equal(0o777, st.st_mode) + + def test_chmod_no_follow_symlink(self): + self.check_posix_only() + if self.use_real_fs() and 'chmod' not in os.supports_follow_symlinks: + raise unittest.SkipTest('follow_symlinks not available') + 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, follow_symlinks=False) + + st = self.os.stat(link_path) + self.assert_mode_equal(0o666, st.st_mode) + st = self.os.stat(link_path, follow_symlinks=False) + self.assert_mode_equal(0o6543, 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(0o666, 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) 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_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() + 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): + 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() + self.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() + self.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() + self.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 + self.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 + self.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() + self.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 + self.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 + self.skip_if_symlink_not_supported() + file_path = self.make_path('foo') + link_path = self.make_path('link') + with self.open(file_path, 'w'): + 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'): + 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() + self.skip_if_symlink_not_supported() + path0 = self.make_path('foo') + self.os.sep + path1 = self.make_path('bar') + with self.open(path1, 'w'): + 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'): + 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() + self.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() + self.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 + self.skip_if_symlink_not_supported() + self.assert_raises_os_error(errno.ENOENT, + self.os.link, '/nonexistent_source', + '/link_dest') + + def test_link_delete(self): + 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) as f: + self.assertEqual(f.read(), contents1) + + def test_link_update(self): + 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) as f: + self.assertEqual(f.read(), contents1) + # update the first file + with self.open(file1_path, 'w') as f: + f.write(contents2) + # assert that second file contains contents2 + with self.open(file2_path) as f: + self.assertEqual(f.read(), contents2) + + def test_link_non_existent_parent(self): + 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): + 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() + self.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.""" + self.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) + + 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 = os.umask(0o22) + os.umask(umask) + self.assertEqual(umask, self.os.umask(0o22)) + + 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() + 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) + + 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').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').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_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) + + 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) + + +class RealOsModuleTest(FakeOsModuleTest): + def use_real_fs(self): + return True + + +class FakeOsModuleTestCaseInsensitiveFS(FakeOsModuleTestBase): + def setUp(self): + super(FakeOsModuleTestCaseInsensitiveFS, self).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): + 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_fdopen_mode(self): + self.skip_real_fs() + file_path1 = self.make_path('some_file1') + file_path2 = self.make_path('Some_File1') + file_path3 = self.make_path('SOME_file1') + self.create_file(file_path1, contents='contents here1') + self.os.chmod(file_path2, (stat.S_IFREG | 0o666) ^ stat.S_IWRITE) + + fake_file1 = self.open(file_path3, 'r') + fileno1 = fake_file1.fileno() + self.os.fdopen(fileno1) + self.os.fdopen(fileno1, 'r') + if not is_root(): + self.assertRaises(OSError, self.os.fdopen, fileno1, 'w') + else: + self.os.fdopen(fileno1, 'w') + + 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): + 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() + self.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() + self.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): + 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): + 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'): + 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') + 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): + 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() + self.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') + 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 + self.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 + self.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 + self.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) + self.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() + self.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_stat_with_mixed_case(self): + # Regression test for #310 + self.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): + 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() + self.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() + self.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() + self.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 + self.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+') + 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) + + 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): + 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): + 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()) as f: + self.assertEqual(f.read(), contents1) + + def test_link_is_existing_file(self): + 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 + self.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 + self.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 setUp(self): + super(FakeOsModuleTimeTest, self).setUp() + self.orig_time = time.time + self.dummy_time = None + self.setDummyTime(200) + + def tearDown(self): + time.time = self.orig_time + super(FakeOsModuleTimeTest, self).tearDown() + + def setDummyTime(self, start): + self.dummy_time = DummyTime(start, 20) + time.time = self.dummy_time + + def test_chmod_st_ctime(self): + # set up + file_path = 'some_file' + self.filesystem.create_file(file_path) + self.assertTrue(self.os.path.exists(file_path)) + self.dummy_time.start() + + 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): + # set up + path = self.make_path('some_file') + self.createTestFile(path) + self.dummy_time.start() + + st = self.os.stat(path) + # 200 is the current time established in setUp(). + self.assertEqual(200, st.st_atime) + self.assertEqual(200, st.st_mtime) + # actual tests + self.os.utime(path, times=None) + st = self.os.stat(path) + self.assertEqual(220, st.st_atime) + self.assertEqual(220, st.st_mtime) + + def test_utime_sets_current_time_if_args_is_none_with_floats(self): + # set up + # we set os.stat_float_times() to False, so atime/ctime/mtime + # are converted as ints (seconds since epoch) + self.setDummyTime(200.9123) + path = '/some_file' + fake_filesystem.FakeOsModule.stat_float_times(False) + self.createTestFile(path) + self.dummy_time.start() + + st = self.os.stat(path) + # 200 is the current time established above (if converted to int). + self.assertEqual(200, st.st_atime) + self.assertTrue(isinstance(st.st_atime, int)) + self.assertEqual(200, st.st_mtime) + self.assertTrue(isinstance(st.st_mtime, int)) + + self.assertEqual(200912300000, st.st_atime_ns) + self.assertEqual(200912300000, st.st_mtime_ns) + + self.assertEqual(200, st.st_mtime) + # actual tests + self.os.utime(path, times=None) + st = self.os.stat(path) + self.assertEqual(220, st.st_atime) + self.assertTrue(isinstance(st.st_atime, int)) + self.assertEqual(220, st.st_mtime) + self.assertTrue(isinstance(st.st_mtime, int)) + self.assertEqual(220912300000, st.st_atime_ns) + self.assertEqual(220912300000, st.st_mtime_ns) + + def test_utime_sets_current_time_if_args_is_none_with_floats_n_sec(self): + fake_filesystem.FakeOsModule.stat_float_times(False) + + self.setDummyTime(200.9123) + path = self.make_path('some_file') + self.createTestFile(path) + test_file = self.filesystem.get_object(path) + + self.dummy_time.start() + st = self.os.stat(path) + self.assertEqual(200, st.st_ctime) + self.assertEqual(200, test_file.st_ctime) + self.assertTrue(isinstance(st.st_ctime, int)) + self.assertTrue(isinstance(test_file.st_ctime, int)) + + self.os.stat_float_times(True) # first time float time + self.assertEqual(200, st.st_ctime) # st does not change + self.assertEqual(200.9123, test_file.st_ctime) # but the file does + self.assertTrue(isinstance(st.st_ctime, int)) + self.assertTrue(isinstance(test_file.st_ctime, float)) + + self.os.stat_float_times(False) # reverting to int + self.assertEqual(200, test_file.st_ctime) + self.assertTrue(isinstance(test_file.st_ctime, int)) + + self.assertEqual(200, st.st_ctime) + self.assertTrue(isinstance(st.st_ctime, int)) + + self.os.stat_float_times(True) + st = self.os.stat(path) + # 200.9123 not converted to int + self.assertEqual(200.9123, test_file.st_atime, test_file.st_mtime) + self.assertEqual(200.9123, st.st_atime, st.st_mtime) + self.os.utime(path, times=None) + st = self.os.stat(path) + self.assertEqual(220.9123, st.st_atime) + self.assertEqual(220.9123, 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.dummy_time.start() + + 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) 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 setUp(self): + os.umask(0o022) + super(FakeOsModuleLowLevelFileOpTest, self).setUp() + + 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) + + 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() + self.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) + + 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() + self.assertTrue(self.os.path.exists(self.os.devnull)) + + def test_devnull_windows(self): + self.check_windows_only() + 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) 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) 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) 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) 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 = [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('foo' 'bar') + 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_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): + print(dirpath, filenames) + 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): + print(dirpath, filenames) + self.assertTrue(dirpath.startswith(base_dir)) + + +class RealOsModuleWalkTest(FakeOsModuleWalkTest): + def use_real_fs(self): + return True + + +class FakeOsModuleDirFdTest(FakeOsModuleTestBase): + def setUp(self): + super(FakeOsModuleDirFdTest, self).setUp() + self.os.supports_dir_fd = set() + self.filesystem.is_windows_fs = False + self.filesystem.create_dir('/foo/bar') + self.dir_fd = self.os.open('/foo', os.O_RDONLY) + self.filesystem.create_file('/foo/baz') + + def test_access(self): + self.assertRaises( + NotImplementedError, self.os.access, 'baz', self.os.F_OK, + dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.access) + self.assertTrue( + self.os.access('baz', self.os.F_OK, dir_fd=self.dir_fd)) + + def test_chmod(self): + self.assertRaises( + NotImplementedError, self.os.chmod, 'baz', 0o6543, + dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.chmod) + self.os.chmod('baz', 0o6543, dir_fd=self.dir_fd) + st = self.os.stat('/foo/baz') + self.assert_mode_equal(0o6543, st.st_mode) + + @unittest.skipIf(not hasattr(os, 'chown'), + 'chown not on all platforms available') + def test_chown(self): + self.assertRaises( + NotImplementedError, self.os.chown, 'baz', 100, 101, + dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.chown) + self.os.chown('baz', 100, 101, dir_fd=self.dir_fd) + st = self.os.stat('/foo/baz') + self.assertEqual(st[stat.ST_UID], 100) + self.assertEqual(st[stat.ST_GID], 101) + + def test_link_src_fd(self): + self.assertRaises( + NotImplementedError, self.os.link, 'baz', '/bat', + src_dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.link) + self.os.link('baz', '/bat', src_dir_fd=self.dir_fd) + self.assertTrue(self.os.path.exists('/bat')) + + def test_link_dst_fd(self): + self.assertRaises( + NotImplementedError, self.os.link, 'baz', '/bat', + dst_dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.link) + self.os.link('/foo/baz', 'bat', dst_dir_fd=self.dir_fd) + self.assertTrue(self.os.path.exists('/foo/bat')) + + def test_symlink(self): + self.assertRaises( + NotImplementedError, self.os.symlink, 'baz', '/bat', + dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.symlink) + self.os.symlink('baz', '/bat', dir_fd=self.dir_fd) + self.assertTrue(self.os.path.exists('/bat')) + + def test_readlink(self): + self.filesystem.create_symlink('/meyer/lemon/pie', '/foo/baz') + self.filesystem.create_symlink('/geo/metro', '/meyer') + self.assertRaises( + NotImplementedError, self.os.readlink, '/geo/metro/lemon/pie', + dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.readlink) + self.assertEqual('/foo/baz', self.os.readlink( + '/geo/metro/lemon/pie', dir_fd=self.dir_fd)) + + def test_stat(self): + self.assertRaises( + NotImplementedError, self.os.stat, 'baz', dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.stat) + st = self.os.stat('baz', dir_fd=self.dir_fd) + self.assertEqual(st.st_mode, 0o100666) + + def test_lstat(self): + self.assertRaises( + NotImplementedError, self.os.lstat, 'baz', dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.lstat) + st = self.os.lstat('baz', dir_fd=self.dir_fd) + self.assertEqual(st.st_mode, 0o100666) + + def test_mkdir(self): + self.assertRaises( + NotImplementedError, self.os.mkdir, 'newdir', dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.mkdir) + self.os.mkdir('newdir', dir_fd=self.dir_fd) + self.assertTrue(self.os.path.exists('/foo/newdir')) + + def test_rmdir(self): + self.assertRaises( + NotImplementedError, self.os.rmdir, 'bar', dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.rmdir) + self.os.rmdir('bar', dir_fd=self.dir_fd) + self.assertFalse(self.os.path.exists('/foo/bar')) + + @unittest.skipIf(not hasattr(os, 'mknod'), + 'mknod not on all platforms available') + def test_mknod(self): + self.assertRaises( + NotImplementedError, self.os.mknod, 'newdir', dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.mknod) + self.os.mknod('newdir', dir_fd=self.dir_fd) + self.assertTrue(self.os.path.exists('/foo/newdir')) + + def test_rename_src_fd(self): + self.assertRaises( + NotImplementedError, self.os.rename, 'baz', '/foo/batz', + src_dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.rename) + self.os.rename('bar', '/foo/batz', src_dir_fd=self.dir_fd) + self.assertTrue(self.os.path.exists('/foo/batz')) + + def test_rename_dst_fd(self): + self.assertRaises( + NotImplementedError, self.os.rename, 'baz', '/foo/batz', + dst_dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.rename) + self.os.rename('/foo/bar', 'batz', dst_dir_fd=self.dir_fd) + self.assertTrue(self.os.path.exists('/foo/batz')) + + def test_replace_src_fd(self): + self.assertRaises( + NotImplementedError, self.os.rename, 'baz', '/foo/batz', + src_dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.rename) + self.os.replace('bar', '/foo/batz', src_dir_fd=self.dir_fd) + self.assertTrue(self.os.path.exists('/foo/batz')) + + def test_replace_dst_fd(self): + self.assertRaises( + NotImplementedError, self.os.rename, 'baz', '/foo/batz', + dst_dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.rename) + self.os.replace('/foo/bar', 'batz', dst_dir_fd=self.dir_fd) + self.assertTrue(self.os.path.exists('/foo/batz')) + + def test_remove(self): + self.assertRaises( + NotImplementedError, self.os.remove, 'baz', dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.remove) + self.os.remove('baz', dir_fd=self.dir_fd) + self.assertFalse(self.os.path.exists('/foo/baz')) + + def test_unlink(self): + self.assertRaises( + NotImplementedError, self.os.unlink, 'baz', dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.unlink) + self.os.unlink('baz', dir_fd=self.dir_fd) + self.assertFalse(self.os.path.exists('/foo/baz')) + + def test_utime(self): + self.assertRaises( + NotImplementedError, self.os.utime, 'baz', (1, 2), + dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.utime) + self.os.utime('baz', times=(1, 2), dir_fd=self.dir_fd) + st = self.os.stat('/foo/baz') + self.assertEqual(1, st.st_atime) + self.assertEqual(2, st.st_mtime) + + def test_open(self): + self.assertRaises( + NotImplementedError, self.os.open, 'baz', os.O_RDONLY, + dir_fd=self.dir_fd) + self.os.supports_dir_fd.add(os.open) + fd = self.os.open('baz', os.O_RDONLY, dir_fd=self.dir_fd) + self.assertLess(0, fd) + + +class StatPropagationTest(TestCase): + def setUp(self): + self.filesystem = fake_filesystem.FakeFilesystem(path_separator='/') + self.os = fake_filesystem.FakeOsModule(self.filesystem) + self.open = fake_filesystem.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') + 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') + fh.close() + self.assertEqual(size, self.open(file_path, 'r').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') + fh.write(added_content) + self.assertEqual(original_size, fh.size()) + fh.close() + self.assertEqual(expected_size, self.open(file_path, 'r').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') + self.assertRaises(fake_filesystem.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') + 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') + 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') + self.assertEqual(0, self.os.stat(file_path)[stat.ST_SIZE]) + self.assertEqual('', self.filesystem.get_object(file_path).contents) + fh.close() + + +@unittest.skipIf(not use_scandir, 'only run if scandir is available') +class FakeScandirTest(FakeOsModuleTestBase): + FILE_SIZE = 50 + LINKED_FILE_SIZE = 10 + + def setUp(self): + super(FakeScandirTest, self).setUp() + self.supports_symlinks = (not self.is_windows or + not self.use_real_fs()) + + if use_scandir_package: + if self.use_real_fs(): + from scandir import scandir + else: + import pyfakefs.fake_scandir + + def fake_scan_dir(p): + return pyfakefs.fake_scandir.scandir(self.filesystem, p) + + scandir = fake_scan_dir + else: + scandir = self.os.scandir + self.scandir = 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(FakeScandirTest, self).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 + self.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 use_scandir and 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_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) + + def check_stat(self, absolute_symlink_expected_size, + relative_symlink_expected_size): + self.assertEqual(self.FILE_SIZE, self.dir_entries[1].stat().st_size) + 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): + 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) + + @unittest.skipIf(sys.version_info < (3, 6) or not use_builtin_scandir, + 'Path-like objects have been introduced in Python 3.6') + 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(sys.version_info < (3, 7) or TestCase.is_windows or + use_scandir_package, + 'dir_fd support for os.scandir was introduced in Python 3.7') +class FakeScandirFdTest(FakeScandirTest): + def tearDown(self): + self.os.close(self.dir_fd) + super(FakeScandirFdTest, self).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(FakeExtendedAttributeTest, self).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_setxattr(self): + self.assertRaises(TypeError, self.os.setxattr, + self.file_path, 'test', 'value') + self.assert_raises_os_error(errno.EEXIST, 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')) + self.assert_raises_os_error(errno.ENODATA, self.os.setxattr, + self.file_path, 'test', b'value', + self.os.XATTR_CREATE) + + 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)) + self.assertIsNone(self.os.getxattr(self.file_path, 'test')) + + 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): + super(FakeOsUnreadableDirTest, self).setUp() + self.check_posix_only() + 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.os.chmod(self.dir_path, 0o000) + + 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_stat_unreadable_dir(self): + 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) + + +class RealOsUnreadableDirTest(FakeOsUnreadableDirTest): + def use_real_fs(self): + return True diff --git a/pyfakefs/tests/fake_pathlib_test.py b/pyfakefs/tests/fake_pathlib_test.py new file mode 100644 index 0000000..7309cdb --- /dev/null +++ b/pyfakefs/tests/fake_pathlib_test.py @@ -0,0 +1,1040 @@ +# -*- coding: utf-8 -*- +# +# 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 errno +import os +import stat +import sys +import unittest + +from pyfakefs.extra_packages import pathlib, pathlib2 +from pyfakefs.fake_filesystem import is_root + +from pyfakefs import fake_pathlib, fake_filesystem +from pyfakefs.tests.test_utils import RealFsTestCase + +is_windows = sys.platform == 'win32' + + +def skip_if_pathlib_36_not_available(): + if sys.version_info < (3, 6) and not pathlib2: + raise unittest.SkipTest('Changed behavior in Python 3.6') + + +def skip_if_pathlib_36_is_available(): + if sys.version_info >= (3, 6) or pathlib2: + raise unittest.SkipTest('Changed behavior in Python 3.6') + + +@unittest.skipIf(pathlib is None, 'Not running without pathlib') +class RealPathlibTestCase(RealFsTestCase): + def __init__(self, methodName='runTest'): + super(RealPathlibTestCase, self).__init__(methodName) + self.pathlib = pathlib or pathlib2 + self.path = None + + def setUp(self): + super(RealPathlibTestCase, self).setUp() + if not self.use_real_fs(): + self.pathlib = fake_pathlib.FakePathlibModule(self.filesystem) + self.path = self.pathlib.Path + + +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()) + self.assertRaises(NotImplementedError, self.pathlib.PosixPath) + else: + self.assertTrue(isinstance(path, self.pathlib.PosixPath)) + self.assertTrue(isinstance(path, self.pathlib.PurePosixPath)) + self.assertTrue(self.pathlib.PureWindowsPath()) + self.assertRaises(NotImplementedError, + 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-specifc 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.""" + + @unittest.skipIf(is_windows, 'POSIX specific behavior') + def test_is_reserved_posix(self): + 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() + 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')) + self.assertRaises(ValueError, self.path('passwd').relative_to, + '/usr') + + 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')) + 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 FakePathlibFileObjectPropertyTest(RealPathlibTestCase): + def setUp(self): + super(FakePathlibFileObjectPropertyTest, self).setUp() + 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: + self.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 test_exists(self): + 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): + 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): + 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): + 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): + 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): + self.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): + self.skip_if_symlink_not_supported() + self.check_lstat(0) + + @unittest.skipIf(is_windows, 'Linux specific behavior') + def test_chmod(self): + self.check_linux_only() + file_stat = self.os.stat(self.file_path) + self.assertEqual(file_stat.st_mode, stat.S_IFREG | 0o666) + link_stat = self.os.lstat(self.file_link_path) + # we get stat.S_IFLNK | 0o755 under MacOs + self.assertEqual(link_stat.st_mode, stat.S_IFLNK | 0o777) + + @unittest.skipIf(sys.platform == 'darwin', + 'Different behavior under MacOs') + def test_lchmod(self): + 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(os, "lchmod"): + self.assertRaises(NotImplementedError, + self.path(self.file_link_path).lchmod, 0o444) + else: + self.path(self.file_link_path).lchmod(0o444) + self.assertEqual(file_stat.st_mode, stat.S_IFREG | 0o666) + # we get stat.S_IFLNK | 0o755 under MacOs + self.assertEqual(link_stat.st_mode, stat.S_IFLNK | 0o444) + + 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.assertEqual(self.path().resolve(), + self.path( + self.os.path.realpath( + self.make_path('antoine')))) + self.assertEqual( + self.path( + self.os.path.join('docs', '..', 'setup.py')).resolve(), + self.path( + self.os.path.realpath( + self.make_path('antoine', 'setup.py')))) + + def test_resolve_nonexisting_file(self): + skip_if_pathlib_36_is_available() + path = self.path('/foo/bar') + self.assert_raises_os_error(errno.ENOENT, path.resolve) + + 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) + iter = self.path(dir_path).iterdir() + if not is_root(): + self.assert_raises_os_error(errno.EACCES, list, iter) + else: + path = str(list(iter)[0]) + self.assertTrue(path.endswith('some_file')) + + @unittest.skipIf(not is_windows, 'Windows specific behavior') + def test_resolve_file_as_parent_windows(self): + skip_if_pathlib_36_is_available() + self.check_windows_only() + self.create_file(self.make_path('a_file')) + path = self.path(self.make_path('a_file', 'this can not exist')) + self.assert_raises_os_error(errno.ENOENT, path.resolve) + + @unittest.skipIf(is_windows, 'POSIX specific behavior') + def test_resolve_file_as_parent_posix(self): + skip_if_pathlib_36_is_available() + self.check_posix_only() + self.create_file(self.make_path('a_file')) + path = self.path( + self.make_path('', 'a_file', 'this can not exist')) + self.assert_raises_os_error(errno.ENOTDIR, path.resolve) + + def test_resolve_nonexisting_file_after_36(self): + skip_if_pathlib_36_not_available() + 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.assertEqual(self.path.cwd(), + self.path(self.os.path.realpath(dir_path))) + + def test_expanduser(self): + if is_windows: + self.assertEqual(self.path('~').expanduser(), + self.path( + os.environ['USERPROFILE'].replace('\\', + '/'))) + else: + self.assertEqual(self.path('~').expanduser(), + self.path(os.environ['HOME'])) + + def test_home(self): + if is_windows: + self.assertEqual(self.path.home(), + self.path( + os.environ['USERPROFILE'].replace('\\', + '/'))) + else: + self.assertEqual(self.path.home(), + self.path(os.environ['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): + 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')) + self.assertRaises(OSError, + self.path(self.make_path('foo', 'bar.txt')).open) + self.path(self.make_path('foo', 'bar.txt')).open('w').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(), 'foo') + + 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(str('foo')) + 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')) + + 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) + + 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) + 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') + self.assertRaises(OSError, path.samefile, other_name) + 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): + 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)) + # file_obj = self.filesystem.ResolveObject(file_name) + # linked_file_obj = self.filesystem.ResolveObject(link_name) + # self.assertEqual(file_obj, linked_file_obj) + # link__obj = self.filesystem.LResolveObject(link_name) + self.assertTrue(path.is_symlink()) + + 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) + + 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')) + 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(sorted(path.glob('*.py')), + [self.path(self.make_path('foo', 'all_tests.py')), + self.path(self.make_path('foo', 'setup.py'))]) + + @unittest.skipIf(not is_windows, 'Windows specific test') + def test_glob_case_windows(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', 'example.Py')) + path = self.path(self.make_path('foo')) + self.assertEqual(sorted(path.glob('*.py')), + [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'))]) + + @unittest.skipIf(is_windows, 'Posix specific test') + def test_glob_case_posix(self): + self.check_posix_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(sorted(path.glob('*.py')), + [self.path(self.make_path('foo', 'setup.py'))]) + + +class RealPathlibPathFileOperationTest(FakePathlibPathFileOperationTest): + def use_real_fs(self): + return True + + +@unittest.skipIf(sys.version_info < (3, 6), + 'path-like objects new in Python 3.6') +class FakePathlibUsageInOsFunctionsTest(RealPathlibTestCase): + """Test that many os / os.path functions accept a path-like object + since Python 3.6. 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' + dir = self.os.path.join(dir1, dir2) + self.assertEqual(dir, self.os.path.join(self.path(dir1), dir2)) + self.assertEqual(dir, self.os.path.join(dir1, self.path(dir2))) + self.assertEqual(dir, + 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_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.assertEqual(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): + 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, 'os.readlink seems not to support ' + 'path-like objects under Windows') + def test_readlink(self): + link_path = self.make_path('foo', 'bar', 'baz') + target = self.make_path('tarJAY') + self.create_symlink(link_path, target) + self.assertEqual(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): + 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.os.stat(self.path(path))) + + 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) + + +class RealPathlibUsageInOsFunctionsTest(FakePathlibUsageInOsFunctionsTest): + def use_real_fs(self): + return True + + +@unittest.skipIf(sys.version_info < (3, 6), + 'Path-like objects new in Python 3.6') +class FakeFilesystemPathLikeObjectTest(unittest.TestCase): + + def setUp(self): + self.filesystem = fake_filesystem.FakeFilesystem( + path_separator='/') + self.pathlib = fake_pathlib.FakePathlibModule(self.filesystem) + self.os = fake_filesystem.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) + + +if __name__ == '__main__': + if pathlib: + unittest.main() diff --git a/pyfakefs/tests/fake_stat_time_test.py b/pyfakefs/tests/fake_stat_time_test.py new file mode 100644 index 0000000..4521365 --- /dev/null +++ b/pyfakefs/tests/fake_stat_time_test.py @@ -0,0 +1,611 @@ +# 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(FakeStatTestBase, self).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) + # sleep a bit so in the next call the time has changed + time.sleep(self.sleep_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.open(self.file_path, self.mode): + 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.open(self.file_path, self.mode) 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): + self.create_file(self.file_path) + + before = self.stat_time(self.file_path) + with self.open(self.file_path, self.mode): + opened = self.stat_time(self.file_path) + closed = self.stat_time(self.file_path) + + return before, opened, closed + + def open_write_close(self): + self.create_file(self.file_path) + + before = self.stat_time(self.file_path) + with self.open(self.file_path, self.mode) 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): + self.create_file(self.file_path) + + before = self.stat_time(self.file_path) + with self.open(self.file_path, self.mode) 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): + self.create_file(self.file_path) + + before = self.stat_time(self.file_path) + with self.open(self.file_path, self.mode) 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): + self.create_file(self.file_path) + + before = self.stat_time(self.file_path) + with self.open(self.file_path, 'r') 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.open(self.file_path, self.mode) 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): + self.create_file(self.file_path) + + before = self.stat_time(self.file_path) + with self.open(self.file_path, self.mode) 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.assertLessExceptWindows(before.st_ctime, opened.st_ctime) + self.assertLessExceptWindows(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.assertEqual(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.assertEqual(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(TestFakeModeW, self).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') 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(TestFakeModeWPlus, self).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(TestFakeModeA, self).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') 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(TestFakeModeAPlus, self).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(TestFakeModeR, self).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(TestFakeModeRPlus, self).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() diff --git a/pyfakefs/tests/fake_tempfile_test.py b/pyfakefs/tests/fake_tempfile_test.py new file mode 100644 index 0000000..8b34b81 --- /dev/null +++ b/pyfakefs/tests/fake_tempfile_test.py @@ -0,0 +1,115 @@ +# 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() + 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', 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 + 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(os.sep, '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() diff --git a/pyfakefs/tests/fixtures/__init__.py b/pyfakefs/tests/fixtures/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pyfakefs/tests/fixtures/__init__.py diff --git a/pyfakefs/tests/fixtures/module_with_attributes.py b/pyfakefs/tests/fixtures/module_with_attributes.py new file mode 100644 index 0000000..02c5694 --- /dev/null +++ b/pyfakefs/tests/fixtures/module_with_attributes.py @@ -0,0 +1,30 @@ +# 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' diff --git a/pyfakefs/tests/import_as_example.py b/pyfakefs/tests/import_as_example.py new file mode 100644 index 0000000..3d000d7 --- /dev/null +++ b/pyfakefs/tests/import_as_example.py @@ -0,0 +1,104 @@ +# 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 +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 +from os.path import exists as my_exists + +from builtins import open as bltn_open + +import sys + +try: + from pathlib import Path +except ImportError: + try: + from pathlib2 import Path + except ImportError: + Path = None + + +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) + + +if Path: + 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 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) as f: + return f.read() + + +def file_contents2(filepath): + with io_open(filepath) as f: + return f.read() + + +def exists_this_file(): + "Returns True in real fs only" + return exists(__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) diff --git a/pyfakefs/tests/mox3_stubout_example.py b/pyfakefs/tests/mox3_stubout_example.py new file mode 100644 index 0000000..44dfee9 --- /dev/null +++ b/pyfakefs/tests/mox3_stubout_example.py @@ -0,0 +1,31 @@ +# 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) diff --git a/pyfakefs/tests/mox3_stubout_test.py b/pyfakefs/tests/mox3_stubout_test.py new file mode 100644 index 0000000..4c09246 --- /dev/null +++ b/pyfakefs/tests/mox3_stubout_test.py @@ -0,0 +1,146 @@ +# 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(StubOutForTestingTest, self).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() diff --git a/pyfakefs/tests/test_utils.py b/pyfakefs/tests/test_utils.py new file mode 100644 index 0000000..da52fbe --- /dev/null +++ b/pyfakefs/tests/test_utils.py @@ -0,0 +1,381 @@ +# 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. + +"""Common helper classes used in tests, or as test class base.""" + +import errno +import os +import platform +import shutil +import stat +import sys +import tempfile +import unittest + +from pyfakefs import fake_filesystem +from pyfakefs.helpers import is_byte_string, to_string + + +class DummyTime: + """Mock replacement for time.time. Increases returned time on access.""" + + def __init__(self, curr_time, increment): + self.curr_time = curr_time + self.increment = increment + self.started = False + + def start(self): + self.started = True + + def __call__(self, *args, **kwargs): + if self.started: + self.curr_time += self.increment + return self.curr_time + + +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' + symlinks_can_be_tested = None + + def assert_mode_equal(self, expected, actual): + return self.assertEqual(stat.S_IMODE(expected), stat.S_IMODE(actual)) + + 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: + self.assertEqual(subtype, exc.errno) + + def assert_equal_paths(self, actual, expected): + if self.is_windows: + self.assertEqual(actual.replace('\\\\?\\', ''), + expected.replace('\\\\?\\', '')) + elif self.is_macos: + self.assertEqual(actual.replace('/private/var/', '/var/'), + expected.replace('/private/var/', '/var/')) + else: + self.assertEqual(actual, expected) + + +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 + if self.use_real_fs(): + self.base_path = tempfile.mkdtemp() + + @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 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) + + 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 symlink_can_be_tested(self): + """Used to check if symlinks and hard links can be tested under + Windows. All tests are skipped under Windows for Python versions + not supporting links, and real tests are skipped if running without + administrator rights. + """ + if not TestCase.is_windows or not self.use_real_fs(): + return True + if TestCase.symlinks_can_be_tested is None: + link_path = self.make_path('link') + try: + self.os.symlink(self.base_path, link_path) + TestCase.symlinks_can_be_tested = True + self.os.remove(link_path) + except (OSError, NotImplementedError): + TestCase.symlinks_can_be_tested = False + return TestCase.symlinks_can_be_tested + + def skip_if_symlink_not_supported(self): + """If called at test start, tests are skipped if symlinks are not + supported.""" + if not self.symlink_can_be_tested(): + raise unittest.SkipTest( + 'Symlinks under Windows need admin privileges') + + 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): + """Create the directory at `dir_path`, including subdirectories. + `dir_path` shall be composed using `make_path()`. + """ + 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) + 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) + + def create_file(self, file_path, contents=None, encoding=None): + """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') + + if encoding is not None and contents is not None: + contents = contents.encode(encoding) + with self.open(file_path, mode) as f: + if contents is not None: + f.write(contents) + self.os.chmod(file_path, 0o666) + + 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' + with self.open(file_path, mode) as f: + self.assertEqual(contents, f.read()) + + def not_dir_error(self): + error = errno.ENOTDIR + return error + + 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.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() + + +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): + self.cwd = os.getcwd() + if not self.use_real_fs(): + self.filesystem = fake_filesystem.FakeFilesystem( + path_separator=self.path_separator()) + self.open = fake_filesystem.FakeFileOpen(self.filesystem) + self.os = fake_filesystem.FakeOsModule(self.filesystem) + self.create_basepath() + elif not os.environ.get('TEST_REAL_FS'): + self.skip_real_fs() + + self.setUpFileSystem() + + def setUpFileSystem(self): + pass + + @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 + + 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) + self.os.chdir(self.cwd) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3bfb271 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pytest>=2.8.6 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..f19282a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[metadata] +description-file = README.md +[bdist_wheel] +universal=0 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..bcf4e7f --- /dev/null +++ b/setup.py @@ -0,0 +1,86 @@ +#! /usr/bin/env python + +# Copyright 2009 Google Inc. All Rights Reserved. +# Copyright 2014 Altera Corporation. All Rights Reserved. +# Copyright 2014-2018 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. +import os + +from setuptools import setup, find_packages + +from pyfakefs.fake_filesystem import __version__ + +NAME = 'pyfakefs' +REQUIRES = [] +DESCRIPTION = ('pyfakefs implements a fake file system that mocks ' + 'the Python file system modules.') + +URL = "http://pyfakefs.org" + +BASE_PATH = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(BASE_PATH, 'README.md')) as f: + LONG_DESCRIPTION = f.read() + +CLASSIFIERS = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + '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', +] + +AUTHOR = 'Google' +AUTHOR_EMAIL = 'google-pyfakefs@google.com' +MAINTAINER = 'John McGehee' +MAINTAINER_EMAIL = 'pyfakefs@johnnado.com' +KEYWORDS = ("testing test file os shutil glob mocking unittest " + "fakes filesystem unit").split(' ') + +params = dict( + name=NAME, + entry_points={ + 'pytest11': ['pytest_fakefs = pyfakefs.pytest_plugin'], + }, + version=__version__, + install_requires=REQUIRES, + + # metadata for upload to PyPI + author=AUTHOR, + author_email=AUTHOR_EMAIL, + maintainer=MAINTAINER, + maintainer_email=MAINTAINER_EMAIL, + description=DESCRIPTION, + long_description=LONG_DESCRIPTION, + long_description_content_type='text/markdown', + keywords=KEYWORDS, + url=URL, + classifiers=CLASSIFIERS, + python_requires='>=3.5', + test_suite='pyfakefs.tests', + packages=find_packages(exclude=['docs']) +) + +setup(**params) @@ -0,0 +1,12 @@ +[tox] +envlist=py35,py36,py37,py38,pypy + +[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 |