aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-03-05 04:59:10 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-03-05 04:59:10 +0000
commit5adabf99ca25018559c47d21eeba484d7a8d643f (patch)
tree9b3d6367f664e2fceb8dd436509a4e8b1ad360bb
parentc2735ebbddcf5e4c314188c0930967a3d9428c33 (diff)
parentd9c173f66be41c93e5b0a4e408b9265eb0465837 (diff)
downloadpyfakefs-5adabf99ca25018559c47d21eeba484d7a8d643f.tar.gz
Initial merge with upstream am: dbd4f42b65 am: b3e480f35d am: 79f1e8a2f8 am: d9c173f66b
Change-Id: I1d930ab9b5bedaf80084ec0da7712242169f27c3
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.md24
-rw-r--r--.github/ISSUE_TEMPLATE/feature_request.md17
-rw-r--r--.gitignore20
-rw-r--r--.travis.yml148
-rwxr-xr-x.travis/docker_tests.sh10
-rw-r--r--.travis/dockerfiles/Dockerfile_centos41
-rw-r--r--.travis/dockerfiles/Dockerfile_debian46
-rw-r--r--.travis/dockerfiles/Dockerfile_fedora40
-rw-r--r--.travis/dockerfiles/Dockerfile_ubuntu46
-rwxr-xr-x.travis/install.sh47
-rwxr-xr-x.travis/run_flake.sh6
-rwxr-xr-x.travis/run_pytest_fixture_param_test.sh7
-rwxr-xr-x.travis/run_pytest_fixture_test.sh7
-rwxr-xr-x.travis/run_pytest_plugin_test.sh9
-rwxr-xr-x.travis/run_tests.sh17
-rw-r--r--CHANGES.md549
-rw-r--r--CONTRIBUTING.md56
-rw-r--r--COPYING175
-rw-r--r--Dockerfile53
l---------LICENSE1
-rwxr-xr-xMANIFEST.in3
-rw-r--r--METADATA19
-rw-r--r--MODULE_LICENSE_APACHE20
l---------NOTICE1
-rw-r--r--README.md129
-rw-r--r--appveyor.yml24
-rw-r--r--docs/Makefile225
-rw-r--r--docs/api.rst40
-rw-r--r--docs/autopatch.rst118
-rw-r--r--docs/conf.py348
-rw-r--r--docs/index.rst25
-rw-r--r--docs/intro.rst106
-rw-r--r--docs/make.bat281
-rw-r--r--docs/modules.rst58
-rw-r--r--docs/pyfakefs_theme/static/pyfakefs.css10
-rw-r--r--docs/pyfakefs_theme/theme.conf3
-rw-r--r--docs/usage.rst634
-rw-r--r--extra_requirements.txt13
-rwxr-xr-xpyfakefs/__init__.py0
-rw-r--r--pyfakefs/deprecator.py69
-rw-r--r--pyfakefs/extra_packages.py44
-rw-r--r--pyfakefs/fake_filesystem.py5111
-rwxr-xr-xpyfakefs/fake_filesystem_shutil.py64
-rw-r--r--pyfakefs/fake_filesystem_unittest.py778
-rw-r--r--pyfakefs/fake_pathlib.py705
-rw-r--r--pyfakefs/fake_scandir.py311
-rw-r--r--pyfakefs/helpers.py432
-rw-r--r--pyfakefs/mox3_stubout.py162
-rw-r--r--pyfakefs/pytest_plugin.py43
-rw-r--r--pyfakefs/pytest_tests/__init__.py0
-rw-r--r--pyfakefs/pytest_tests/conftest.py43
-rw-r--r--pyfakefs/pytest_tests/example.py25
-rw-r--r--pyfakefs/pytest_tests/pytest_check_failed_plugin_test.py16
-rw-r--r--pyfakefs/pytest_tests/pytest_doctest_test.py48
-rw-r--r--pyfakefs/pytest_tests/pytest_fixture_param_test.py43
-rw-r--r--pyfakefs/pytest_tests/pytest_fixture_test.py51
-rw-r--r--pyfakefs/pytest_tests/pytest_plugin_failing_test.py6
-rw-r--r--pyfakefs/pytest_tests/pytest_plugin_test.py39
-rw-r--r--pyfakefs/tests/__init__.py0
-rw-r--r--pyfakefs/tests/all_tests.py66
-rw-r--r--pyfakefs/tests/all_tests_without_extra_packages.py42
-rw-r--r--pyfakefs/tests/dynamic_patch_test.py72
-rw-r--r--pyfakefs/tests/example.py148
-rw-r--r--pyfakefs/tests/example_test.py180
-rw-r--r--pyfakefs/tests/fake_filesystem_glob_test.py74
-rw-r--r--pyfakefs/tests/fake_filesystem_shutil_test.py511
-rw-r--r--pyfakefs/tests/fake_filesystem_test.py2186
-rw-r--r--pyfakefs/tests/fake_filesystem_unittest_test.py650
-rw-r--r--pyfakefs/tests/fake_filesystem_vs_real_test.py637
-rw-r--r--pyfakefs/tests/fake_open_test.py1647
-rw-r--r--pyfakefs/tests/fake_os_test.py5182
-rw-r--r--pyfakefs/tests/fake_pathlib_test.py1040
-rw-r--r--pyfakefs/tests/fake_stat_time_test.py611
-rw-r--r--pyfakefs/tests/fake_tempfile_test.py115
-rw-r--r--pyfakefs/tests/fixtures/__init__.py0
-rw-r--r--pyfakefs/tests/fixtures/module_with_attributes.py30
-rw-r--r--pyfakefs/tests/import_as_example.py104
-rw-r--r--pyfakefs/tests/mox3_stubout_example.py31
-rw-r--r--pyfakefs/tests/mox3_stubout_test.py146
-rw-r--r--pyfakefs/tests/test_utils.py381
-rw-r--r--requirements.txt1
-rw-r--r--setup.cfg4
-rw-r--r--setup.py86
-rw-r--r--tox.ini12
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!
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..67db858
--- /dev/null
+++ b/COPYING
@@ -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"]
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..d24842f
--- /dev/null
+++ b/LICENSE
@@ -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
diff --git a/NOTICE b/NOTICE
new file mode 120000
index 0000000..7a694c9
--- /dev/null
+++ b/NOTICE
@@ -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('&#1593;&#1604;&#1610; &#1576;&#1575;&#1576;&#1575;',
+ 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)
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..0bc891a
--- /dev/null
+++ b/tox.ini
@@ -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