aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-04-06 20:35:56 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-04-06 20:35:56 +0000
commit3d2e1e6b9a0bc5bfc22f1302e113526ebf407f51 (patch)
treeb2ed9b3dbe896aabfeda4c6547afecbebadbb2d5
parent279b54ccf366ac884bd56db062358a9667e49544 (diff)
parent51da2dffc8843695b24cbea669a3cc68695efd98 (diff)
downloadpyfakefs-android13-qpr1-s5-release.tar.gz
Change-Id: I67caea09ff1eeb1fdf5d214ed33ddba8297b1c17
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.md2
-rw-r--r--.github/workflows/builddocs.yml53
-rw-r--r--.github/workflows/dockerfiles/Dockerfile_centos (renamed from .travis/dockerfiles/Dockerfile_centos)0
-rw-r--r--.github/workflows/dockerfiles/Dockerfile_debian (renamed from .travis/dockerfiles/Dockerfile_debian)0
-rw-r--r--.github/workflows/dockerfiles/Dockerfile_fedora (renamed from .travis/dockerfiles/Dockerfile_fedora)2
-rw-r--r--.github/workflows/dockerfiles/Dockerfile_ubuntu (renamed from .travis/dockerfiles/Dockerfile_ubuntu)0
-rw-r--r--.github/workflows/dockertests.yml19
-rw-r--r--.github/workflows/pythonpackage.yml162
-rwxr-xr-x.github/workflows/run_pytest.sh8
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml148
-rwxr-xr-x.travis/docker_tests.sh10
-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.md317
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--METADATA4
-rw-r--r--README.md24
-rw-r--r--appveyor.yml24
-rw-r--r--docs/conf.py6
-rw-r--r--docs/intro.rst19
-rw-r--r--docs/usage.rst499
-rw-r--r--extra_requirements.txt12
-rw-r--r--mypy.ini31
-rwxr-xr-xpyfakefs/__init__.py1
-rw-r--r--pyfakefs/_version.py1
-rw-r--r--pyfakefs/deprecator.py16
-rw-r--r--pyfakefs/extra_packages.py7
-rw-r--r--pyfakefs/fake_filesystem.py2520
-rw-r--r--pyfakefs/fake_filesystem_unittest.py670
-rw-r--r--pyfakefs/fake_pathlib.py255
-rw-r--r--pyfakefs/fake_scandir.py13
-rw-r--r--pyfakefs/helpers.py374
-rw-r--r--pyfakefs/patched_packages.py135
-rw-r--r--pyfakefs/pytest_plugin.py18
-rw-r--r--pyfakefs/pytest_tests/conftest.py19
-rw-r--r--pyfakefs/pytest_tests/example.py12
-rw-r--r--pyfakefs/pytest_tests/pytest_check_failed_plugin_test.py7
-rw-r--r--pyfakefs/pytest_tests/pytest_doctest_test.py2
-rw-r--r--pyfakefs/pytest_tests/pytest_fixture_param_test.py9
-rw-r--r--pyfakefs/pytest_tests/pytest_plugin_failing_helper.py (renamed from pyfakefs/pytest_tests/pytest_plugin_failing_test.py)0
-rw-r--r--pyfakefs/pytest_tests/pytest_plugin_test.py13
-rw-r--r--pyfakefs/tests/all_tests.py37
-rw-r--r--pyfakefs/tests/all_tests_without_extra_packages.py10
-rw-r--r--pyfakefs/tests/dynamic_patch_test.py3
-rw-r--r--pyfakefs/tests/example.py2
-rw-r--r--pyfakefs/tests/example_test.py2
-rw-r--r--pyfakefs/tests/fake_filesystem_glob_test.py6
-rw-r--r--pyfakefs/tests/fake_filesystem_shutil_test.py77
-rw-r--r--pyfakefs/tests/fake_filesystem_test.py585
-rw-r--r--pyfakefs/tests/fake_filesystem_unittest_test.py527
-rw-r--r--pyfakefs/tests/fake_filesystem_vs_real_test.py85
-rw-r--r--pyfakefs/tests/fake_open_test.py556
-rw-r--r--pyfakefs/tests/fake_os_test.py384
-rw-r--r--pyfakefs/tests/fake_pathlib_test.py271
-rw-r--r--pyfakefs/tests/fake_stat_time_test.py158
-rw-r--r--pyfakefs/tests/fake_tempfile_test.py6
-rw-r--r--pyfakefs/tests/fixtures/config_module.py1
-rw-r--r--pyfakefs/tests/fixtures/deprecated_property.py29
-rw-r--r--pyfakefs/tests/fixtures/excel_test.xlsxbin0 -> 4790 bytes
-rw-r--r--pyfakefs/tests/import_as_example.py41
-rw-r--r--pyfakefs/tests/logsio.py22
-rw-r--r--pyfakefs/tests/patched_packages_test.py73
-rw-r--r--pyfakefs/tests/performance_test.py72
-rw-r--r--pyfakefs/tests/test_utils.py137
-rw-r--r--setup.cfg2
-rw-r--r--setup.py13
-rw-r--r--tox.ini2
72 files changed, 5780 insertions, 2830 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 6f02963..84ac229 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -15,7 +15,7 @@ Please provide a stack trace if available.
Please provide a unit test or a minimal code snippet that reproduces the
problem.
-**Your enviroment**
+**Your environment**
Please run the following and paste the output.
```bash
python -c "import platform; print(platform.platform())"
diff --git a/.github/workflows/builddocs.yml b/.github/workflows/builddocs.yml
new file mode 100644
index 0000000..9092b1a
--- /dev/null
+++ b/.github/workflows/builddocs.yml
@@ -0,0 +1,53 @@
+name: DocBuild
+
+on:
+ push:
+ branches: master
+
+defaults:
+ run:
+ shell: bash
+
+jobs:
+ build_docs:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ python-version: [3.8]
+ steps:
+ - name: Checkout master
+ uses: actions/checkout@v2
+ with:
+ path: main
+ - name: Get last commit message
+ run: |
+ cd main
+ echo "LAST_COMMIT=$(echo `git log -1 --pretty=%B`)" >> $GITHUB_ENV
+ cd ..
+ - name: Install needed packages
+ run: |
+ pip3 install wheel
+ pip3 install pytest
+ sudo apt update
+ sudo apt-get install python3-sphinx
+ cd main/docs
+ make html
+ - name: Checkout gh-pages
+ uses: actions/checkout@v2
+ with:
+ ref: gh-pages
+ path: doc
+ - name: Copy and commit changes
+ run: |
+ cp -r main/gh-pages/* doc/master
+ cd doc
+ git config user.name "CI Build"
+ git config user.email "pyfakefs@gmail.com"
+ git add master/*
+ if [ `git status -s | wc -l` = 0 ]; then
+ echo "No changes in built documentation, skipping"
+ exit 0
+ fi
+ git commit -a -m "$LAST_COMMIT"
+ git push
diff --git a/.travis/dockerfiles/Dockerfile_centos b/.github/workflows/dockerfiles/Dockerfile_centos
index f22fb63..f22fb63 100644
--- a/.travis/dockerfiles/Dockerfile_centos
+++ b/.github/workflows/dockerfiles/Dockerfile_centos
diff --git a/.travis/dockerfiles/Dockerfile_debian b/.github/workflows/dockerfiles/Dockerfile_debian
index 0012e77..0012e77 100644
--- a/.travis/dockerfiles/Dockerfile_debian
+++ b/.github/workflows/dockerfiles/Dockerfile_debian
diff --git a/.travis/dockerfiles/Dockerfile_fedora b/.github/workflows/dockerfiles/Dockerfile_fedora
index 528ef6d..5cb9dee 100644
--- a/.travis/dockerfiles/Dockerfile_fedora
+++ b/.github/workflows/dockerfiles/Dockerfile_fedora
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-FROM fedora
+FROM fedora:32
MAINTAINER jmcgeheeiv@users.noreply.github.com
ENV LANG en_US.UTF-8
diff --git a/.travis/dockerfiles/Dockerfile_ubuntu b/.github/workflows/dockerfiles/Dockerfile_ubuntu
index 2e235cd..2e235cd 100644
--- a/.travis/dockerfiles/Dockerfile_ubuntu
+++ b/.github/workflows/dockerfiles/Dockerfile_ubuntu
diff --git a/.github/workflows/dockertests.yml b/.github/workflows/dockertests.yml
new file mode 100644
index 0000000..af77346
--- /dev/null
+++ b/.github/workflows/dockertests.yml
@@ -0,0 +1,19 @@
+name: Dockertests
+
+on:
+ [push]
+
+jobs:
+ dockertests:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ docker-image: [centos, debian, fedora, ubuntu]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup docker container
+ run: |
+ docker build -t pyfakefs -f $GITHUB_WORKSPACE/.github/workflows/dockerfiles/Dockerfile_${{ matrix.docker-image }} . --build-arg github_repo=$GITHUB_REPOSITORY --build-arg github_branch=$(basename $GITHUB_REF)
+ - name: Run tests
+ run: docker run -t pyfakefs
diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml
new file mode 100644
index 0000000..25f0527
--- /dev/null
+++ b/.github/workflows/pythonpackage.yml
@@ -0,0 +1,162 @@
+name: Testsuite
+
+on:
+ [push, pull_request]
+
+jobs:
+ linter:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ python-version: [3.8]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install linter
+ run: |
+ uname -a
+ python -m pip install flake8 mypy
+ - name: Check syntax and style
+ run: flake8 . --exclude get-pip.py --max-complexity=13 --statistics
+
+ mypy:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ python-version: [3.8]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: |
+ python -m pip install -r requirements.txt -r extra_requirements.txt
+ python -m pip install mypy==0.812
+ - name: Run typing checks
+ run: python -m mypy .
+
+ tests:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macOS-latest, windows-latest]
+ python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
+ include:
+ - python-version: pypy3
+ os: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Get pip cache dir
+ id: pip-cache
+ run: |
+ python -m pip install --upgrade pip
+ echo "::set-output name=dir::$(pip cache dir)"
+
+ - name: Cache dependencies
+ id: cache-dep
+ uses: actions/cache@v2
+ with:
+ path: ${{ steps.pip-cache.outputs.dir }}
+ key: ${{ matrix.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('**/requirements.txt') }}-${{ hashFiles('**/extra_requirements.txt') }}
+ restore-keys: |
+ ${{ matrix.os }}-${{ matrix.python-version }}-pip-
+
+ - name: Install dependencies
+ run: |
+ pip install wheel
+ pip install -r requirements.txt
+ pip install .
+ - name: Run unit tests without extra packages as non-root user
+ run: |
+ export TEST_REAL_FS=1
+ python -bb -m pyfakefs.tests.all_tests_without_extra_packages
+ shell: bash
+ - name: Run setup.py test (uses pytest)
+ run: |
+ python setup.py test
+ shell: bash
+ - name: Run unit tests without extra packages as root
+ run: |
+ if [[ '${{ matrix.os }}' != 'windows-latest' ]]; then
+ # provide the same path as non-root to get the correct virtualenv
+ sudo env "PATH=$PATH" python -m pyfakefs.tests.all_tests_without_extra_packages
+ fi
+ shell: bash
+ - name: Install extra dependencies
+ run: |
+ pip install -r extra_requirements.txt
+ shell: bash
+ - name: Run unit tests with extra packages as non-root user
+ run: |
+ python -m pyfakefs.tests.all_tests
+ shell: bash
+ - name: Run performance tests
+ run: |
+ if [[ '${{ matrix.os }}' != 'macOS-latest' ]]; then
+ export TEST_PERFORMANCE=1
+ python -m pyfakefs.tests.performance_test
+ fi
+ shell: bash
+
+ pytest-test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest]
+ python-version: [3.9]
+ pytest-version: [3.0.0, 3.5.1, 4.0.2, 4.5.0, 5.0.1, 5.4.3, 6.0.2, 6.2.5, 7.0.1, 7.1.0]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: |
+ pip install -r requirements.txt
+ pip install -U pytest==${{ matrix.pytest-version }}
+ if [[ '${{ matrix.pytest-version }}' == '4.0.2' ]]; then
+ pip install -U attrs==19.1.0
+ fi
+ shell: bash
+ - name: Run pytest tests
+ run: |
+ export PY_VERSION=${{ matrix.python-version }}
+ $GITHUB_WORKSPACE/.github/workflows/run_pytest.sh
+ shell: bash
+
+ dependency-check:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest]
+ python-version: [3.9]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: |
+ pip install -r requirements.txt
+ pip install -r extra_requirements.txt
+ pip install pytest-find-dependencies
+ - name: Check dependencies
+ run: python -m pytest --find-dependencies pyfakefs/tests
+ shell: bash
diff --git a/.github/workflows/run_pytest.sh b/.github/workflows/run_pytest.sh
new file mode 100755
index 0000000..725deb9
--- /dev/null
+++ b/.github/workflows/run_pytest.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+python -m pytest pyfakefs/pytest_tests/pytest_plugin_test.py
+if [[ $PY_VERSION == '3.6' ]] || [[ $PY_VERSION == '3.7' ]] || [[ $PY_VERSION == '3.8' ]] || [[ $PY_VERSION == '3.9' ]] ; then
+ python -m pytest pyfakefs/pytest_tests/pytest_fixture_test.py
+fi
+python -m pytest pyfakefs/pytest_tests/pytest_plugin_failing_helper.py > ./testresult.txt
+python -m pytest pyfakefs/pytest_tests/pytest_check_failed_plugin_test.py
diff --git a/.gitignore b/.gitignore
index f83e65a..33daaae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,9 +12,11 @@
# pytest
.cache/
+.pytest_cache/
# autodoc created by sphinx
gh-pages/
# Distribution creation
dist/
+build/
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index b01f1f7..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,148 +0,0 @@
-# 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
deleted file mode 100755
index 8f5f7e7..0000000
--- a/.travis/docker_tests.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/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/install.sh b/.travis/install.sh
deleted file mode 100755
index c280a7a..0000000
--- a/.travis/install.sh
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/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
deleted file mode 100755
index f1cf807..0000000
--- a/.travis/run_flake.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/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
deleted file mode 100755
index c57de65..0000000
--- a/.travis/run_pytest_fixture_param_test.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/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
deleted file mode 100755
index 6b0c9e7..0000000
--- a/.travis/run_pytest_fixture_test.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/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
deleted file mode 100755
index 0d49ec6..0000000
--- a/.travis/run_pytest_plugin_test.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/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
deleted file mode 100755
index d1a0a97..0000000
--- a/.travis/run_tests.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/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
index a167639..1d59511 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,29 +1,286 @@
# pyfakefs Release Notes
The released versions correspond to PyPi releases.
-## Version 4.1.0 (as yet unreleased)
+## Unreleased
+
+## [Version 4.5.6](https://pypi.python.org/pypi/pyfakefs/4.5.6) (2022-03-17)
+Fixes a regression which broke tests with older pytest versions (< 3.9).
+
+### Changes
+* minimum supported pytest version is now 3.0 (older versions do not work
+ properly with current Python versions)
+
+### Fixes
+* only skip `_pytest.pathlib` in pytest versions where it is actually present
+ (see [#669](../../issues/669))
+
+### Infrastructure
+* add tests with different pytest versions, starting with 3.0
+
+## [Version 4.5.5](https://pypi.python.org/pypi/pyfakefs/4.5.5) (2022-02-14)
+Bugfix release, needed for compatibility with pytest 7.0.
+
+### Fixes
+* correctly handle file system space for files opened in write mode
+ (see [#660](../../issues/660))
+* correctly handle reading/writing pipes via file
+ (see [#661](../../issues/661))
+* disallow `encoding` argument on binary `open()`
+ (see [#664](../../issues/664))
+* fixed compatibility issue with pytest 7.0.0
+ (see [#666](../../issues/666))
+
+## [Version 4.5.4](https://pypi.python.org/pypi/pyfakefs/4.5.4) (2022-01-12)
+Minor bugfix release.
+
+### Fixes
+* added missing mocked functions for fake pipe (see [#650](../../issues/650))
+* fixed some bytes warnings (see [#651](../../issues/651))
+
+## [Version 4.5.3](https://pypi.python.org/pypi/pyfakefs/4.5.3) (2021-11-08)
+Reverts a change in the previous release that could cause a regression.
+
+### Changes
+* `os.listdir`, `os.scandir` and `pathlib.Path.listdir` now return the
+ directory list in a random order only if explicitly configured in the
+ file system (use `fs.shuffle_listdir_results = True` with `fs` being the
+ file system). In a future version, the default may be changed to better
+ reflect the real filesystem behavior (see [#647](../../issues/647))
+
+## [Version 4.5.2](https://pypi.python.org/pypi/pyfakefs/4.5.2) (2021-11-07)
+This is a bugfix release.
+
+### Changes
+* `os.listdir`, `os.scandir` and `pathlib.Path.listdir` now return the
+ directory list in a random order (see [#638](../../issues/638))
+* the `fcntl` module under Unix is now mocked, e.g. all functions have no
+ effect (this may be changed in the future if needed,
+ see [#645](../../issues/645))
+
+### Fixes
+* fixed handling of alternative path separator in `os.path.split`,
+ `os.path.splitdrive` and `glob.glob`
+ (see [#632](../../issues/632))
+* fixed handling of failed rename due to permission error
+ (see [#643](../../issues/643))
+
+
+## [Version 4.5.1](https://pypi.python.org/pypi/pyfakefs/4.5.1) (2021-08-29)
+This is a bugfix release.
+
+### Fixes
+* added handling of path-like where missing
+* improved handling of `str`/`bytes` paths
+* suppress all warnings while inspecting loaded modules
+ (see [#614](../../issues/614))
+* do not import pandas and related modules if it is not patched
+ (see [#627](../../issues/627))
+* handle `pathlib.Path.owner()` and `pathlib.Path.group` by returning
+ the current user/group name (see [#629](../../issues/629))
+* fixed handling of `use_known_patches=False` (could cause an exception)
+* removed Python 3.5 from metadata to disable installation for that version
+ (see [#615](../../issues/615))
+
+### Infrastructure
+* added test dependency check (see [#608](../../issues/608))
+* skip tests failing with ASCII locale
+ (see [#623](../../issues/623))
+
+## [Version 4.5.0](https://pypi.python.org/pypi/pyfakefs/4.5.0) (2021-06-04)
+Adds some support for Python 3.10 and basic type checking.
+
+_Note_: This version has been yanked from PyPi as it erroneously allowed
+installation under Python 3.5.
+
+### New Features
+ * added support for some Python 3.10 features:
+ * new method `pathlib.Path.hardlink_to`
+ * new `newline` argument in `pathlib.Path.write_text`
+ * new `follow_symlinks` argument in `pathlib.Path.stat` and
+ `pathlib.Path.chmod`
+ * new 'strict' argument in `os.path.realpath`
+
+### Changes
+ * Python 3.5 has reached its end of life in September 2020 and is no longer
+ supported
+ * `pathlib2` is still supported, but considered to have the same
+ functionality as `pathlib` and is no longer tested separately;
+ the previous behavior broke newer `pathlib` features if `pathlib2`
+ was installed (see [#592](../../issues/592))
+
+### Fixes
+ * correctly handle byte paths in `os.path.exists`
+ (see [#595](../../issues/595))
+ * Update `fake_pathlib` to support changes coming in Python 3.10
+ ([see](https://github.com/python/cpython/pull/19342)
+ * correctly handle UNC paths in `os.path.split` and in directory path
+ evaluation (see [#606](../../issues/606))
+
+### Infrastructure
+ * added mypy checks in CI (see [#599](../../issues/599))
+
+## [Version 4.4.0](https://pypi.python.org/pypi/pyfakefs/4.4.0) (2021-02-24)
+Adds better support for Python 3.8 / 3.9.
+
+### New Features
+ * added support for `pathlib.Path.link_to` (new in Python 3.8)
+ (see [#580](../../issues/580))
+ * added support for `pathlib.Path.readlink` (new in Python 3.9)
+ (see [#584](../../issues/584))
+ * added `FakeFilesystem.create_link` convenience method which creates
+ intermittent directories (see [#580](../../issues/580))
+
+### Fixes
+ * fixed handling of pipe descriptors in the fake filesystem
+ (see [#581](../../issues/581))
+ * added non-functional argument `effective_ids` to `os.access`
+ (see [#585](../../issues/585))
+ * correctly handle `os.file` for unreadable files
+ (see [#588](../../issues/588))
+
+### Infrastructure
+ * added automatic documentation build and check-in
+
+## [Version 4.3.3](https://pypi.python.org/pypi/pyfakefs/4.3.3) (2020-12-20)
+
+Another bugfix release.
+
+### Fixes
+* Reverted one Windows-specific optimization that can break tests under some
+ conditions (see [#573](../../issues/573))
+* Setting `os` did not reset `os.sep` and related variables,
+ fixed null device name, added `os.pathsep` and missing `os.path` variables
+ (see [#572](../../issues/572))
+
+## [Version 4.3.2](https://pypi.python.org/pypi/pyfakefs/4.3.2) (2020-11-26)
+
+This is a bugfix release that fixes a regression introduced in version 4.2.0.
+
+### Fixes
+* `open` calls had not been patched for modules with a name ending with "io"
+ (see [#569](../../issues/569))
+
+## [Version 4.3.1](https://pypi.python.org/pypi/pyfakefs/4.3.1) (2020-11-23)
+
+This is an update to the performance release, with more setup caching and the
+possibility to disable it.
+
+### Changes
+* Added caching of patched modules to avoid lookup overhead
+* Added `use_cache` option and `clear_cache` method to be able
+ to deal with unwanted side effects of the newly introduced caching
+
+### Infrastructure
+* Moved CI builds to GitHub Actions for performance reasons
+
+## [Version 4.3.0](https://pypi.python.org/pypi/pyfakefs/4.3.0) (2020-11-19)
+
+This is mostly a performance release. The performance of the pyfakefs setup has
+been decreasing sufficiently, especially with the 4.x releases. This release
+corrects that by making the most expansive feature optional, and by adding some
+other performance improvements. This shall decrease the setup time by about a
+factor of 20, and it shall now be comparable to the performance of the 3.4
+release.
+
+### Changes
+ * The `patchfs` decorator now expects a positional argument instead of the
+ keyword arguments `fs`. This avoids confusion with the pytest `fs`
+ fixture and conforms to the behavior of `mock.patch`. You may have to
+ adapt the argument order if you use the `patchfs` and `mock.patch`
+ decorators together (see [#566](../../issues/566))
+ * Default arguments that are file system functions are now _not_ patched by
+ default to avoid a large performance impact. An additional parameter
+ `patch_default_args` has been added that switches this behavior on
+ (see [#567](../../issues/567)).
+ * Added performance improvements in the test setup, including caching the
+ the unpatched modules
+
+## [Version 4.2.1](https://pypi.python.org/pypi/pyfakefs/4.2.1) (2020-11-02)
+
+This is a bugfix release that fixes a regression issue.
+
+### Fixes
+ * remove dependency of pyfakefs on `pytest` (regression,
+ see [#565](../../issues/565))
+
+## [Version 4.2.0](https://pydpi.python.org/pypi/pyfakefs/4.2.0) (2020-11-01)
+
+#### New Features
+ * add support for the `buffering` parameter in `open`
+ (see [#549](../../issues/549))
+ * add possibility to patch `io.open_code` using the new argument
+ `patch_open_code` (since Python 3.8)
+ (see [#554](../../issues/554))
+ * add possibility to set file system OS via `FakeFilesystem.os`
+
+#### Fixes
+ * fix check for link in `os.walk` (see [#559](../../issues/559))
+ * fix handling of real files in combination with `home` if simulating
+ Posix under Windows (see [#558](../../issues/558))
+ * do not call fake `open` if called from skipped module
+ (see [#552](../../issues/552))
+ * do not call fake `pathlib.Path` if called from skipped module
+ (see [#553](../../issues/553))
+ * fixed handling of `additional_skip_names` with several module components
+ * allow to open existing pipe file descriptor
+ (see [#493](../../issues/493))
+ * do not truncate file on failed flush
+ (see [#548](../../issues/548))
+ * suppress deprecation warnings while collecting modules
+ (see [#542](../../issues/542))
+ * add support for `os.truncate` and `os.ftruncate`
+ (see [#545](../../issues/545))
+
+#### Infrastructure
+ * fixed another problem with CI test scripts not always propagating errors
+ * make sure pytest will work without pyfakefs installed
+ (see [#550](../../issues/550))
+
+## [Version 4.1.0](https://pypi.python.org/pypi/pyfakefs/4.1.0) (2020-07-12)
-## [Version 4.0.2](https://pypi.python.org/pypi/pyfakefs/4.0.2)
+#### New Features
+ * Added some support for pandas (`read_csv`, `read_excel` and more), and
+ for django file locks to work with the fake filesystem
+ (see [#531](../../issues/531))
+
+#### Fixes
+ * `os.expanduser` now works with a bytes path
+ * Do not override global warnings setting in `Deprecator`
+ (see [#526](../../issues/526))
+ * Make sure filesystem modules in `pathlib` are patched
+ (see [#527](../../issues/527))
+ * Make sure that alternative path separators are correctly handled under Windows
+ (see [#530](../../issues/530))
+
+#### Infrastructure
+ * Make sure all temporary files from real fs tests are removed
+
+## [Version 4.0.2](https://pypi.python.org/pypi/pyfakefs/4.0.2) (2020-03-04)
This as a patch release that only builds for Python 3. Note that
-versions 4.0.0 and 4.0.1 will be removed from PyPi to not to be able to
-install them under Python 2.
+versions 4.0.0 and 4.0.1 will be removed from PyPi to disable
+installing them under Python 2.
#### Fixes
* Do not build for Python 2 (see [#524](../../issues/524))
-## [Version 4.0.1](https://pypi.python.org/pypi/pyfakefs/4.0.1)
+## [Version 4.0.1](https://pypi.python.org/pypi/pyfakefs/4.0.1) (2020-03-03)
This as a bug fix release for a regression bug.
+_Note_: This version has been yanked from PyPi as it erroneously allowed
+installation under Python 2. This has been fixed in version 4.0.2.
+
#### Fixes
* Avoid exception if using `flask-restx` (see [#523](../../issues/523))
-## [Version 4.0.0](https://pypi.python.org/pypi/pyfakefs/4.0.0)
+## [Version 4.0.0](https://pypi.python.org/pypi/pyfakefs/4.0.0) (2020-03-03)
+pyfakefs 4.0.0 drops support for Python 2.7. If you still need
+Python 2.7, you can continue to use pyfakefs 3.7.x.
+
+_Note_: This version has been yanked from PyPi as it erroneously allowed
+installation under Python 2. This has been fixed in version 4.0.2.
- * 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))
@@ -52,7 +309,7 @@ This as a bug fix release for a regression bug.
* 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)
+## [Version 3.7.2](https://pypi.python.org/pypi/pyfakefs/3.7.2) (2020-03-02)
This version backports some fixes from master.
@@ -68,7 +325,7 @@ This version backports some fixes from master.
* 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)
+## [Version 3.7.1](https://pypi.python.org/pypi/pyfakefs/3.7.1) (2020-02-14)
This version adds support for Python 3.7.6 and 3.8.1.
@@ -76,7 +333,7 @@ This version adds support for Python 3.7.6 and 3.8.1.
* 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)
+## [Version 3.7](https://pypi.python.org/pypi/pyfakefs/3.7) (2019-11-23)
This version adds support for Python 3.8.
@@ -106,7 +363,7 @@ and Python 3.4 (possible bug fix releases notwithstanding).
* 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)
+## [Version 3.6.1](https://pypi.python.org/pypi/pyfakefs/3.6.1) (2019-10-07)
#### Fixes
* avoid rare side effect during module iteration in test setup
@@ -114,7 +371,7 @@ and Python 3.4 (possible bug fix releases notwithstanding).
* 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)
+## [Version 3.6](https://pypi.python.org/pypi/pyfakefs/3.6) (2019-06-30)
#### Changes
* removed unneeded parameter `use_dynamic_patch`
@@ -143,7 +400,7 @@ and Python 3.4 (possible bug fix releases notwithstanding).
* 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)
+## [Version 3.5.8](https://pypi.python.org/pypi/pyfakefs/3.5.8) (2019-06-21)
Another bug-fix release that mainly fixes a regression wih Python 2 that has
been introduced in version 3.5.3.
@@ -159,7 +416,7 @@ been introduced in version 3.5.3.
* 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)
+## [Version 3.5.7](https://pypi.python.org/pypi/pyfakefs/3.5.7) (2019-02-08)
This is mostly a bug-fix release.
@@ -174,19 +431,19 @@ This is mostly a bug-fix release.
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)
+## [Version 3.5.6](https://pypi.python.org/pypi/pyfakefs/3.5.6) (2019-01-13)
#### Changes
* import external `pathlib2` and `scandir` packages first if present
(see [#462](../../issues/462))
-## [Version 3.5.5](https://pypi.python.org/pypi/pyfakefs/3.5.5)
+## [Version 3.5.5](https://pypi.python.org/pypi/pyfakefs/3.5.5) (2018-12-20)
#### Fixes
* removed shebang from test files to avoid packaging warnings
(see [#461](../../issues/461))
-## [Version 3.5.4](https://pypi.python.org/pypi/pyfakefs/3.5.4)
+## [Version 3.5.4](https://pypi.python.org/pypi/pyfakefs/3.5.4) (2018-12-19)
#### New Features
* added context manager class `Pause` for pause/resume
@@ -199,7 +456,7 @@ This is mostly a bug-fix release.
* 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)
+## [Version 3.5.3](https://pypi.python.org/pypi/pyfakefs/3.5.3) (2018-11-22)
This is a minor release to have a version with passing tests for OpenSUSE
packaging.
@@ -213,7 +470,7 @@ packaging.
* 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)
+## [Version 3.5.2](https://pypi.python.org/pypi/pyfakefs/3.5.2) (2018-11-11)
This is mostly a bug-fix release.
@@ -229,7 +486,7 @@ This is mostly a bug-fix release.
([#445](../../issues/445))
* allow trailing path in `add_real_directory` ([#446](../../issues/446))
-## [Version 3.5](https://pypi.python.org/pypi/pyfakefs/3.5)
+## [Version 3.5](https://pypi.python.org/pypi/pyfakefs/3.5) (2018-10-22)
#### Changes
* This version of pyfakefs does not support Python 3.3. Python 3.3 users
@@ -274,7 +531,7 @@ This is mostly a bug-fix release.
* 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)
+## [Version 3.4.3](https://pypi.python.org/pypi/pyfakefs/3.4.3) (2018-06-13)
This is mostly a bug fix release, mainly for bugs found by
[@agroce](https://github.com/agroce) using [tstl](https://github.com/agroce/tstl).
@@ -326,7 +583,7 @@ This is mostly a bug fix release, mainly for bugs found by
* `os.readlink` ([#359](../../issues/359), [#372](../../issues/372),
[#392](../../issues/392))
-## [Version 3.4.1](https://pypi.python.org/pypi/pyfakefs/3.4.1)
+## [Version 3.4.1](https://pypi.python.org/pypi/pyfakefs/3.4.1) (2018-03-18)
This is a bug fix only release.
@@ -335,7 +592,7 @@ This is a bug fix only release.
`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)
+## [Version 3.4](https://pypi.python.org/pypi/pyfakefs/3.4) (2018-03-08)
This version of pyfakefs does not support Python 2.6. Python 2.6 users
must use pyfakefs 3.3 or earlier.
@@ -377,7 +634,7 @@ must use pyfakefs 3.3 or earlier.
* 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)
+## [Version 3.3](https://pypi.python.org/pypi/pyfakefs/3.3) (2017-11-12)
This is the last release that supports Python 2.6.
@@ -427,7 +684,7 @@ This is the last release that supports Python 2.6.
([#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)
+## [Version 3.2](https://pypi.python.org/pypi/pyfakefs/3.2) (2017-05-27)
#### New Features
* The `errors` argument is supported for `io.open()` and `os.open()`
@@ -453,7 +710,7 @@ This is the last release that supports Python 2.6.
* 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)
+## [Version 3.1](https://pypi.python.org/pypi/pyfakefs/3.1) (2017-02-11)
#### New Features
* Added helper method `TestCase.copyRealFile()` to copy a file from
@@ -465,7 +722,7 @@ This is the last release that supports Python 2.6.
#### Fixes
* Incorrect disk usage calculation if too large file created ([#155](../../issues/155))
-## [Version 3.0](https://pypi.python.org/pypi/pyfakefs/3.0)
+## [Version 3.0](https://pypi.python.org/pypi/pyfakefs/3.0) (2017-01-18)
#### New Features
* Support for path-like objects as arguments in fake `os`
@@ -490,7 +747,7 @@ This is the last release that supports Python 2.6.
* 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)
+## [Version 2.9](https://pypi.python.org/pypi/pyfakefs/2.9) (2016-10-02)
#### New Features
* `io.open`, `os.open`: support for `encoding` argument ([#120](../../issues/120))
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 228b25e..9d2daf5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -48,7 +48,7 @@ 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.
+ located in the `docs` directory (call `make html` from that directory).
For building the documentation, you will need [sphinx](http://sphinx.pocoo.org/).
* [this file](https://github.com/jmcgeheeiv/pyfakefs/blob/master/CONTRIBUTING.md)
if you want to enhance the contributing guide itself
diff --git a/METADATA b/METADATA
index ea4f24e..f58e4d3 100644
--- a/METADATA
+++ b/METADATA
@@ -14,7 +14,7 @@ third_party {
type: GIT
value: "https://github.com/jmcgeheeiv/pyfakefs.git"
}
- version: "v3.7"
+ version: "9a387966d65e20e465ed03af5b3bfb632984adc3"
license_type: NOTICE
- last_upgrade_date { year: 2019 month: 12 day: 18 }
+ last_upgrade_date { year: 2022 month: 03 day: 30 }
}
diff --git a/README.md b/README.md
index da06bef..559cb12 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# 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 [![PyPI version](https://badge.fury.io/py/pyfakefs.svg)](https://badge.fury.io/py/pyfakefs) [![Python version](https://img.shields.io/pypi/pyversions/pyfakefs.svg)](https://img.shields.io/pypi/pyversions/pyfakefs.svg) ![Testsuite](https://github.com/jmcgeheeiv/pyfakefs/workflows/Testsuite/badge.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
@@ -14,7 +14,7 @@ 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
+ most relevant 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
@@ -43,10 +43,10 @@ using convenience functions.
## Compatibility
-pyfakefs works with CPython 3.5 and above, on Linux, Windows and OSX
+pyfakefs works with CPython 3.6 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 works with [pytest](http://doc.pytest.org) version 3.0.0 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'
@@ -59,13 +59,9 @@ For example, pyfakefs will not work with [`lxml`](http://lxml.de/). In this cas
### 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)
+pyfakefs is currently automatically tested on Linux, MacOS and Windows, with
+Python 3.6 to 3.10, and with PyPy3 on Linux, using
+[GitHub Actions](https://github.com/jmcgeheeiv/pyfakefs/actions).
### Running pyfakefs unit tests
@@ -90,7 +86,7 @@ $ tox
#### In a Docker container
-The `Dockerfile` at the top of the repository will run the tests on the latest
+The `Dockerfile` at the repository root will run the tests on the latest
Ubuntu version. Build the container:
```bash
cd pyfakefs/
@@ -103,8 +99,8 @@ 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)
+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
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index da82a82..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-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/conf.py b/docs/conf.py
index c69b951..73a59d8 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -58,7 +58,7 @@ master_doc = 'index'
project = 'pyfakefs'
copyright = '''2009 Google Inc. All Rights Reserved.
© Copyright 2014 Altera Corporation. All Rights Reserved.
-© Copyright 2014-2019 John McGehee'''
+© Copyright 2014-2021 John McGehee'''
author = 'John McGehee'
# The version info for the project you're documenting, acts as replacement for
@@ -66,9 +66,9 @@ author = 'John McGehee'
# built documents.
#
# The short X.Y version.
-version = '4.1'
+version = '4.6'
# The full version, including alpha/beta/rc tags.
-release = '4.1dev'
+release = '4.6.dev0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/docs/intro.rst b/docs/intro.rst
index 052cb19..683e132 100644
--- a/docs/intro.rst
+++ b/docs/intro.rst
@@ -6,10 +6,10 @@ 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
+pyfakefs works with CPython 3.6 and above, on Linux, Windows and OSX
(MacOS), and with PyPy3.
-pyfakefs works with `PyTest <doc.pytest.org>`__ version 2.8.6 or above.
+pyfakefs works with `pytest <doc.pytest.org>`__ version 3.0.0 or above.
Installation
------------
@@ -47,18 +47,22 @@ Features
- pyfakefs keeps track of the filesystem size if configured. The file system
size can be configured arbitrarily.
+- it is possible to pause and resume using the fake filesystem, if the
+ real file system has to be used in a test step
+
- 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:
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.
+ 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.
@@ -70,7 +74,7 @@ Limitations
between binary and textual file objects).
- pyfakefs is only tested with CPython and the newest PyPy versions, other
- Python implementations will probably not work.
+ 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
@@ -78,7 +82,12 @@ Limitations
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.
+ considered as reference systems, additionally the tests are run in Docker
+ containers with the latest CentOS, Debian, Fedora and Ubuntu images.
+
+- pyfakefs may not work correctly if file system functions are patched by
+ other means (e.g. using `unittest.mock.patch`) - see
+ :ref:`usage_with_mock_open` for more information
History
-------
diff --git a/docs/usage.rst b/docs/usage.rst
index cd9a9cd..b2ec2fa 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -3,7 +3,7 @@ Usage
Test Scenarios
--------------
-There are several approaches to implementing tests using ``pyfakefs``.
+There are several approaches for implementing tests using ``pyfakefs``.
Patch using fake_filesystem_unittest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -29,16 +29,18 @@ with the fake file system functions and modules:
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``.
+demonstrated in the files `example.py`_ and `example_test.py`_.
-Patch using the PyTest plugin
+Patch using the pytest plugin
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If you use `PyTest <https://doc.pytest.org>`__, you will be interested in
-the PyTest plugin in ``pyfakefs``.
+If you use `pytest`_, 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:
+The pytest plugin provides the ``fs`` fixture for use in your test. The plugin
+is registered for pytest on installing ``pyfakefs`` as usual for pytest
+plugins, so you can just use it:
.. code:: python
@@ -47,11 +49,28 @@ The PyTest plugin provides the ``fs`` fixture for use in your test. For example:
fs.create_file('/var/data/xx1.txt')
assert os.path.exists('/var/data/xx1.txt')
+If you are bothered by the ``pylint`` warning,
+``C0103: Argument name "fs" doesn't conform to snake_case naming style
+(invalid-name)``,
+you can define a longer name in your ``conftest.py`` and use that in your
+tests:
+
+.. code:: python
+
+ @pytest.fixture
+ def fake_filesystem(fs): # pylint:disable=invalid-name
+ """Variable name 'fs' causes a pylint warning. Provide a longer name
+ acceptable to pylint for use in tests.
+ """
+ yield fs
+
+
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.
+If you are using other means of testing like `nose`_,
+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:
@@ -81,21 +100,46 @@ You can also initialize ``Patcher`` manually:
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:
+If you are not using ``pytest`` and 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')
+ def test_something(fake_fs):
+ # access the fake_filesystem object via fake_fs
+ fake_fs.create_file('/foo/bar', contents='test')
+
+Note that ``fake_fs`` is a positional argument and the argument name does
+not matter. If there are additional ``mock.patch`` decorators that also
+create positional arguments, the argument order is the same as the decorator
+order, as shown here:
+
+.. code:: python
+
+ @patchfs
+ @mock.patch('foo.bar')
+ def test_something(fake_fs, mocked_bar):
+ ...
+
+ @mock.patch('foo.bar')
+ @patchfs
+ def test_something(mocked_bar, fake_fs):
+ ...
-Note the argument name ``fs``, which is mandatory.
+.. note::
+ Avoid writing the ``patchfs`` decorator *between* ``mock.patch`` operators,
+ as the order will not be what you expect. Due to implementation details,
+ all arguments created by ``mock.patch`` decorators are always expected to
+ be contiguous, regardless of other decorators positioned between them.
-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.
+.. caution::
+ In previous versions, the keyword argument `fs` has been used instead,
+ which had to be positioned *after* all positional arguments regardless of
+ the decorator order. If you upgrade from a version before pyfakefs 4.2,
+ you may have to adapt the argument order.
You can also use this to make a single unit test use the fake fs:
@@ -107,52 +151,111 @@ You can also use this to make a single unit test use the fake fs:
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:
+
+.. _customizing_patcher:
+
+Customizing patching
+--------------------
+
+``fake_filesystem_unittest.Patcher`` provides a few arguments to adapt
+patching for cases where it does not work out of the box. These arguments
+can also be used with ``unittest`` and ``pytest``.
+
+Using custom arguments
+~~~~~~~~~~~~~~~~~~~~~~
+The following sections describe how to apply these arguments in different
+scenarios, using the argument :ref:`allow_root_user` as an example.
+
+Patcher
+.......
+If you use the ``Patcher`` directly, you can just pass the arguments in the
+constructor:
.. code:: python
- @patchfs(allow_root_user=False)
- def test_something(fs):
- # now always called as non-root user
- os.makedirs('/foo/bar')
+ from pyfakefs.fake_filesystem_unittest import Patcher
-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.
+ with Patcher(allow_root_user=False) as patcher:
+ ...
-.. _customizing_patcher:
+Unittest
+........
+If you are using ``fake_filesystem_unittest.TestCase``, the arguments can be
+passed to ``setUpPyfakefs()``, which will pass them to the ``Patcher``
+instance:
+
+.. code:: python
+
+ from pyfakefs.fake_filesystem_unittest import TestCase
+
+ class SomeTest(TestCase):
+ def setUp(self):
+ self.setUpPyfakefs(allow_root_user=False)
+
+ def testSomething(self):
+ ...
+
+Pytest
+......
+
+In case of ``pytest``, you have two possibilities:
+
+- The standard way to customize the ``fs`` fixture is to write your own
+ fixture which uses the ``Patcher`` with arguments as has been shown above:
+
+.. code:: python
+
+ import pytest
+ from pyfakefs.fake_filesystem_unittest import Patcher
+
+ @pytest.fixture
+ def fs_no_root():
+ with Patcher(allow_root_user=False) as patcher:
+ yield patcher.fs
+
+ def test_something(fs_no_root):
+ ...
+
+- You can also pass the arguments using ``@pytest.mark.parametrize``. Note that
+ you have to provide `all Patcher arguments`_ before the needed ones, as
+ keyword arguments cannot be used, and you have to add ``indirect=True``.
+ This makes it less readable, but gives you a quick possibility to adapt a
+ single test:
-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.
+.. code:: python
+
+ import pytest
+
+ @pytest.mark.parametrize('fs', [[None, None, None, False]], indirect=True)
+ def test_something(fs):
+ ...
+
+
+patchfs
+.......
+If you use the ``patchfs`` decorator, you can pass the arguments directly to
+the decorator:
+
+.. code:: python
+
+ from pyfakefs.fake_filesystem_unittest import patchfs
+
+ @patchfs(allow_root_user=False)
+ def test_something(fake_fs):
+ ...
+
+
+List of custom arguments
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Following is a description of the optional arguments that can be used to
+customize ``pyfakefs``.
+
+.. _modules_to_reload:
modules_to_reload
-~~~~~~~~~~~~~~~~~
-Pyfakefs patches modules that are imported before starting the test by
+.................
+``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:
@@ -193,8 +296,12 @@ This also works if importing the functions as another name:
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:
+There are a few cases where automatic patching does not work. We know of at
+least two specific cases where this is the case:
+
+Initializing a default argument with a file system function is not patched
+automatically due to performance reasons (though it can be switched on using
+:ref:`patch_default_args`):
.. code:: python
@@ -203,8 +310,6 @@ automatically:
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:
@@ -216,16 +321,27 @@ initialization will be done using the real file system:
path = Path("/example_home")
In this case, ``path`` will hold the real file system path inside the test.
+The same is true, if a file system function is used in a decorator (this is
+an example from a related issue):
+
+.. code:: python
+
+ import pathlib
+
+ @click.command()
+ @click.argument('foo', type=click.Path(path_type=pathlib.Path))
+ def hello(foo):
+ pass
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
+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:
+Given that the example function ``check_if_exists`` shown above is located in
+the file ``example/sut.py``, the following code will work:
.. code:: python
@@ -264,16 +380,17 @@ Given that the example code shown above is located in the file
modules_to_patch
-~~~~~~~~~~~~~~~~
+................
Sometimes there are file system modules in other packages that are not
-patched in standard pyfakefs. To allow patching such modules,
+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
+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:
+fake a module in Django that uses OS file system functions (note that this
+has now been been integrated into ``pyfakefs``):
.. code:: python
@@ -320,11 +437,11 @@ fake a module in Django that uses OS file system functions:
# test code using patchfs decorator
@patchfs(modules_to_patch={'django.core.files.locks': FakeLocks})
- def test_django_stuff(fs):
+ def test_django_stuff(fake_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
@@ -345,36 +462,118 @@ Alternatively to the module names, the modules themselves may be used:
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:
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``.
+use_known_patches
+.................
+Some libraries are known to require patching in order to work with
+``pyfakefs``.
+If ``use_known_patches`` is set to ``True`` (the default), ``pyfakefs`` patches
+these libraries so that they will work with the fake filesystem. Currently, this
+includes patches for ``pandas`` read methods like ``read_csv`` and
+``read_excel``, and for ``Django`` file locks--more may follow. Ordinarily,
+the default value of ``use_known_patches`` should be used, but it is present
+to allow users to disable this patching in case it causes any problems. It
+may be removed or replaced by more fine-grained arguments in future releases.
+
+patch_open_code
+...............
+Since Python 3.8, the ``io`` module has the function ``open_code``, which
+opens a file read-only and is used to open Python code files. By default, this
+function is not patched, because the files it opens usually belong to the
+executed library code and are not present in the fake file system.
+Under some circumstances, this may not be the case, and the opened file
+lives in the fake filesystem. For these cases, you can set ``patch_open_code``
+to ``PatchMode.ON``. If you just want to patch ``open_case`` for files that
+live in the fake filesystem, and use the real function for the rest, you can
+set ``patch_open_code`` to ``PatchMode.AUTO``:
+
+.. code:: python
+
+ from pyfakefs.fake_filesystem_unittest import PatchMode
+
+ @patchfs(patch_open_code=PatchMode.AUTO)
+ def test_something(fs):
+ ...
+
+.. note:: This argument is subject to change or removal in future
+ versions of ``pyfakefs``, depending on the upcoming use cases.
+
+.. _patch_default_args:
+
+patch_default_args
+..................
+As already mentioned, a default argument that is initialized with a file
+system function is not patched automatically:
+
+.. code:: python
+
+ import os
+
+ def check_if_exists(filepath, file_exists=os.path.exists):
+ return file_exists(filepath)
+
+As this is rarely needed, and the check to patch this automatically is quite
+expansive, it is not done by default. Using ``patch_default_args`` will
+search for this kind of default arguments and patch them automatically.
+You could also use the :ref:`modules_to_reload` option with the module that
+contains the default argument instead, if you want to avoid the overhead.
+
+use_cache
+.........
+If True (the default), patched and non-patched modules are cached between tests
+to avoid the performance hit of the file system function lookup (the
+patching itself is reverted after each test). As this is a new
+feature, this argument allows to turn it off in case it causes any problems:
+
+.. code:: python
+
+ @patchfs(use_cache=False)
+ def test_something(fake_fs):
+ fake_fs.create_file("foo", contents="test")
+ ...
+
+Please write an issue if you encounter any problem that can be fixed by using
+this parameter. Note that this argument may be removed in a later version, if
+no problems come up.
+
+If you want to clear the cache just for a specific test instead, you can call
+``clear_cache`` on the ``Patcher`` or the ``fake_filesystem`` instance:
+
+.. code:: python
+
+ def test_something(fs): # using pytest fixture
+ fs.clear_cache()
+ ...
+
+
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``.
+fixture in pytest, ``TestCase.fs`` for ``unittest``, and the ``fs`` argument
+for the ``patchfs`` decorator.
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.
+in the path, you may use ``create_file()``, ``create_dir()``,
+``create_symlink()`` and ``create_link()``, 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).
+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, see also :ref:`set-fs-size`).
.. code:: python
@@ -391,6 +590,13 @@ with large files).
self.assertEqual('test', f.read())
``create_dir()`` behaves like ``os.makedirs()``.
+``create_symlink`` and ``create_link`` behave like ``os.symlink`` and
+``os.link``, with any missing parent directories of the link created
+automatically.
+
+.. caution::
+ The first two arguments in ``create_symlink`` are reverted in relation to
+ ``os.symlink`` for historical reasons.
Access to files in the real file system
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -482,6 +688,8 @@ 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.
+.. _set-fs-size:
+
Setting the file system size
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you need to know the file system size in your tests (for example for
@@ -490,8 +698,9 @@ testing cleanup scripts), you can set the fake file system size using
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,
+By default, the size of the fake file system is set to 1 TB (which
+for most tests can be considered as 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
@@ -506,13 +715,15 @@ and you may fail to create new files if the fake file system is full.
def test_disk_full(self):
with open('/foo/bar.txt', 'w') as f:
- self.assertRaises(OSError, f.write, 'a' * 200)
+ with self.assertRaises(OSError):
+ f.write('a' * 200)
+ f.flush()
To get the file system size, you may use ``get_disk_usage()``, which is
modeled after ``shutil.disk_usage()``.
-Pausing patching
-~~~~~~~~~~~~~~~~
+Suspending 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``,
@@ -520,7 +731,7 @@ functions, which exist in ``fake_filesystem_unittest.Patcher``,
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:
+Here is an example that tests the usage with the ``pyfakefs`` pytest fixture:
.. code:: python
@@ -553,6 +764,44 @@ Here is the same code using a context manager:
assert not os.path.exists(real_temp_file.name)
assert os.path.exists(fake_temp_file.name)
+Simulating other file systems
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``Pyfakefs`` supports Linux, MacOS and Windows operating systems. By default,
+the file system of the OS where the tests run is assumed, but it is possible
+to simulate other file systems to some extent. To set a specific file
+system, you can change ``pyfakefs.FakeFilesystem.os`` to one of
+``OSType.LINUX``, ``OSType.MACOS`` and ``OSType.WINDOWS``. On doing so, the
+behavior of ``pyfakefs`` is adapted to the respective file system. Note that
+setting this causes the fake file system to be reset, so you should call it
+before adding any files.
+
+Setting the ``os`` attributes changes a number of ``pyfakefs.FakeFilesystem``
+attributes, which can also be set separately if needed:
+
+ - ``is_windows_fs`` - if ``True`` a Windows file system (NTFS) is assumed
+ - ``is_macos`` - if ``True`` and ``is_windows_fs`` is ``False``, the
+ standard MacOS file system (HFS+) is assumed
+ - if ``is_windows_fs`` and ``is_macos`` are ``False``, a Linux file system
+ (something like ext3) is assumed
+ - ``is_case_sensitive`` is set to ``True`` under Linux and to ``False``
+ under Windows and MacOS by default - you can change it to change the
+ respective behavior
+ - ``path_separator`` is set to ``\`` under Windows and to ``/`` under Posix,
+ ``alternative_path_separator`` is set to ``/`` under Windows and to
+ ``None`` under Posix--these can also be adapted if needed
+
+The following test works both under Windows and Linux:
+
+.. code:: python
+
+ from pyfakefs.fake_filesystem import OSType
+
+ def test_windows_paths(fs):
+ fs.os = OSType.WINDOWS
+ assert r"C:\foo\bar" == os.path.join('C:\\', 'foo', 'bar'))
+ assert os.path.splitdrive(r"C:\foo\bar") == ("C:", r"\foo\bar")
+ assert os.path.ismount("C:")
+
Troubleshooting
---------------
@@ -587,25 +836,53 @@ reasons:
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.
+ parameter (see the example for file locks in Django above), or by using
+ ``unittest.patch`` if you don't need to simulate the functions. We
+ added some of these patches to ``pyfakefs``, so that they are applied
+ automatically (currently done for some ``pandas`` and ``Django``
+ functionality).
- 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``,
+ 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).
+- `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.
+- `subprocess`_ has very similar problems and cannot be used with
+ ``pyfakefs`` to start a process. ``subprocess`` can either be mocked, if
+ the process is not needed for the test, or patching can be paused to start
+ a process if needed, and resumed afterwards
+ (see `this issue <https://github.com/jmcgeheeiv/pyfakefs/issues/447>`__).
+- Modules that rely on ``subprocess`` or ``multiprocessing`` to work
+ correctly, e.g. need to start other executables. Examples that have shown
+ this problem include `GitPython`_ and `plumbum`_.
+- the `Pillow`_ image library does not work with pyfakefs at least if writing
+ JPEG files (see `this issue <https://github.com/jmcgeheeiv/pyfakefs/issues/529>`__)
+- `pandas`_ (the Python data analysis library) uses its own internal file
+ system access written in C. Thus much of ``pandas`` will not work with
+ ``pyfakefs``. Having said that, ``pyfakefs`` patches ``pandas`` so that many
+ of the ``read_xxx`` functions, including ``read_csv`` and ``read_excel``,
+ as well as some writer functions, do work with the fake file system. If
+ you use only these functions, ``pyfakefs`` will work with ``pandas``.
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!
+Pyfakefs behaves differently than the real filesystem
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+There are basically two kinds of deviations from the actual behavior:
+
+- unwanted deviations that we didn't notice--if you find any of these, please
+ write an issue and will try to fix it
+- behavior that depends on different OS versions and editions--as mentioned
+ in :ref:`limitations`, ``pyfakefs`` uses the TravisCI systems as reference
+ system and will not replicate all system-specific behavior
+
OS temporary directories
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -615,20 +892,48 @@ 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
+``/var/folders/<hash>/T`` when running on MacOs, or
``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
+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
+``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
+``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.
+Another possibility to run tests as non-root user in a root user environment
+is the convenience argument :ref:`allow_root_user`.
+
+.. _usage_with_mock_open:
+
+Pyfakefs and mock_open
+~~~~~~~~~~~~~~~~~~~~~~
+If you patch ``open`` using ``mock_open`` before the initialization of
+``pyfakefs``, it will not work properly, because the ``pyfakefs``
+initialization relies on ``open`` working correctly.
+Generally, you should not need ``mock_open`` if using ``pyfakefs``, because you
+always can create the files with the needed content using ``create_file``.
+This is true for patching any filesystem functions - avoid patching them
+while working with ``pyfakefs``.
+If you still want to use ``mock_open``, make sure it is only used while
+patching is in progress. For example, if you are using ``pytest`` with the
+``mocker`` fixture used to patch ``open``, make sure that the ``fs`` fixture is
+passed before the ``mocker`` fixture to ensure this.
+
+.. _`example.py`: https://github.com/jmcgeheeiv/pyfakefs/blob/master/pyfakefs/tests/example.py
+.. _`example_test.py`: https://github.com/jmcgeheeiv/pyfakefs/blob/master/pyfakefs/tests/example_test.py
+.. _`pytest`: https://doc.pytest.org
+.. _`nose`: https://docs.nose2.io/en/latest/
+.. _`all Patcher arguments`: https://jmcgeheeiv.github.io/pyfakefs/master/modules.html#pyfakefs.fake_filesystem_unittest.Patcher
+.. _`multiprocessing`: https://docs.python.org/3/library/multiprocessing.html
+.. _`subprocess`: https://docs.python.org/3/library/subprocess.html
+.. _`GitPython`: https://pypi.org/project/GitPython/
+.. _`plumbum`: https://pypi.org/project/plumbum/
+.. _`Pillow`: https://pypi.org/project/Pillow/
+.. _`pandas`: https://pypi.org/project/pandas/ \ No newline at end of file
diff --git a/extra_requirements.txt b/extra_requirements.txt
index 9352552..eeaaad2 100644
--- a/extra_requirements.txt
+++ b/extra_requirements.txt
@@ -7,7 +7,15 @@
#
# 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
+
+# pandas + xlrd are used to test pandas-specific patches to allow
+# pyfakefs to work with pandas
+# we use the latest version to see any problems with new versions
+pandas==1.1.5; python_version <= '3.6'
+pandas==1.3.5; python_version == '3.7'
+pandas==1.4.1; python_version > '3.7'
+xlrd==1.2.0; python_version <= '3.6'
+xlrd==2.0.1; python_version > '3.6'
+openpyxl==3.0.9
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 0000000..aa34b11
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,31 @@
+[mypy]
+show_error_codes = True
+warn_unused_configs = True
+exclude=(docs|pyfakefs/tests)
+
+[mypy-django.*]
+ignore_missing_imports = True
+
+[mypy-hotshot.*]
+ignore_missing_imports = True
+
+[mypy-openpyxl.*]
+ignore_missing_imports = True
+
+[mypy-pandas.*]
+ignore_missing_imports = True
+
+[mypy-pathlib2.*]
+ignore_missing_imports = True
+
+[mypy-psyco.*]
+ignore_missing_imports = True
+
+[mypy-scandir.*]
+ignore_missing_imports = True
+
+[mypy-setuptools.*]
+ignore_missing_imports = True
+
+[mypy-xlrd.*]
+ignore_missing_imports = True
diff --git a/pyfakefs/__init__.py b/pyfakefs/__init__.py
index e69de29..3a8d6d5 100755
--- a/pyfakefs/__init__.py
+++ b/pyfakefs/__init__.py
@@ -0,0 +1 @@
+from ._version import __version__ # noqa: F401
diff --git a/pyfakefs/_version.py b/pyfakefs/_version.py
new file mode 100644
index 0000000..37846a0
--- /dev/null
+++ b/pyfakefs/_version.py
@@ -0,0 +1 @@
+__version__ = '4.6.dev0'
diff --git a/pyfakefs/deprecator.py b/pyfakefs/deprecator.py
index 25a5caa..99d0ae6 100644
--- a/pyfakefs/deprecator.py
+++ b/pyfakefs/deprecator.py
@@ -39,14 +39,14 @@ class Deprecator(object):
@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)
+ with warnings.catch_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)
return func(*args, **kwargs)
return _new_func
diff --git a/pyfakefs/extra_packages.py b/pyfakefs/extra_packages.py
index ae84c74..d45c854 100644
--- a/pyfakefs/extra_packages.py
+++ b/pyfakefs/extra_packages.py
@@ -16,16 +16,9 @@ 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
diff --git a/pyfakefs/fake_filesystem.py b/pyfakefs/fake_filesystem.py
index 45fa0fb..29bd1ba 100644
--- a/pyfakefs/fake_filesystem.py
+++ b/pyfakefs/fake_filesystem.py
@@ -98,25 +98,30 @@ import heapq
import io
import locale
import os
+import random
import sys
-import time
+import traceback
import uuid
from collections import namedtuple
+from doctest import TestResults
+from enum import Enum
from stat import (
S_IFREG, S_IFDIR, S_ISLNK, S_IFMT, S_ISDIR, S_IFLNK, S_ISREG, S_IFSOCK
)
-
+from types import ModuleType, TracebackType
+from typing import (
+ List, Optional, Callable, Union, Any, Dict, Tuple, cast, AnyStr, overload,
+ NoReturn, ClassVar, IO, Iterator, TextIO, Type
+)
from pyfakefs.deprecator import Deprecator
from pyfakefs.extra_packages import use_scandir
-from pyfakefs.fake_scandir import scandir, walk
+from pyfakefs.fake_scandir import scandir, walk, ScanDirIter
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'
+ FakeStatResult, BinaryBufferIO, TextBufferIO,
+ is_int_type, is_byte_string, is_unicode_string, make_string_path,
+ IS_PYPY, to_string, matching_string, real_encoding, now, AnyPath, to_bytes
+)
+from pyfakefs import __version__ # noqa: F401 for upwards compatibility
PERM_READ = 0o400 # Read permission bit.
PERM_WRITE = 0o200 # Write permission bit.
@@ -126,7 +131,7 @@ PERM_DEF_FILE = 0o666 # Default permission bits (regular file)
PERM_ALL = 0o7777 # All permission bits.
_OpenModes = namedtuple(
- 'open_modes',
+ '_OpenModes',
'must_exist can_read can_write truncate append must_not_exist'
)
@@ -139,10 +144,19 @@ _OPEN_MODE_MAP = {
'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, False, True, False, False, True),
'x+': (False, True, True, False, False, True)
}
+AnyFileWrapper = Union[
+ "FakeFileWrapper", "FakeDirWrapper",
+ "StandardStreamWrapper", "FakePipeWrapper"
+]
+
+AnyString = Union[str, bytes]
+
+AnyFile = Union["FakeFile", "FakeDirectory"]
+
if sys.platform.startswith('linux'):
# on newer Linux system, the default maximum recursion depth is 40
# we ignore older systems here
@@ -152,11 +166,31 @@ else:
_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()
+if sys.platform == 'win32':
+ USER_ID = 1
+ GROUP_ID = 1
+else:
+ USER_ID = os.getuid()
+ GROUP_ID = os.getgid()
+
+
+class OSType(Enum):
+ """Defines the real or simulated OS of the underlying file system."""
+ LINUX = "linux"
+ MACOS = "macos"
+ WINDOWS = "windows"
-def set_uid(uid):
+class PatchMode(Enum):
+ """Defines if patching shall be on, off, or in automatic mode.
+ Currently only used for `patch_open_code` option.
+ """
+ OFF = 1
+ AUTO = 2
+ ON = 3
+
+
+def set_uid(uid: int) -> None:
"""Set the global user id. This is used as st_uid for new files
and to differentiate between a normal user and the root user (uid 0).
For the root user, some permission restrictions are ignored.
@@ -168,7 +202,7 @@ def set_uid(uid):
USER_ID = uid
-def set_gid(gid):
+def set_gid(gid: int) -> None:
"""Set the global group id. This is only used to set st_gid for new files,
no permision checks are performed.
@@ -179,13 +213,17 @@ def set_gid(gid):
GROUP_ID = gid
-def reset_ids():
+def reset_ids() -> None:
"""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())
+ if sys.platform == 'win32':
+ set_uid(1)
+ set_gid(1)
+ else:
+ set_uid(os.getuid())
+ set_gid(os.getgid())
-def is_root():
+def is_root() -> bool:
"""Return True if the current user is the root user."""
return USER_ID == 0
@@ -195,17 +233,18 @@ class FakeLargeFileIoException(Exception):
Fake large files have a size with no real content.
"""
- def __init__(self, file_path):
+ def __init__(self, file_path: str) -> None:
super(FakeLargeFileIoException, self).__init__(
'Read and write operations not supported for '
'fake large file: %s' % file_path)
-def _copy_module(old):
+def _copy_module(old: ModuleType) -> ModuleType:
"""Recompiles and creates new module object."""
saved = sys.modules.pop(old.__name__, None)
new = __import__(old.__name__)
- sys.modules[old.__name__] = saved
+ if saved is not None:
+ sys.modules[old.__name__] = saved
return new
@@ -244,17 +283,24 @@ class FakeFile:
'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):
+ def __init__(self, name: AnyStr,
+ st_mode: int = S_IFREG | PERM_DEF_FILE,
+ contents: Optional[AnyStr] = None,
+ filesystem: Optional["FakeFilesystem"] = None,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ side_effect: Optional[Callable[["FakeFile"], None]] = None):
"""
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)
+ stat.S_IFREG, stat.S_IFDIR), and the file permissions.
+ If no file type is set (e.g. permission flags only), a
+ regular file type is assumed.
contents: The contents of the filesystem object; should be a string
- or byte object for regular files, and a list of other
- FakeFile or FakeDirectory objects for FakeDirectory objects
+ or byte object for regular files, and a dict of other
+ FakeFile or FakeDirectory objects wih the file names as
+ keys for FakeDirectory objects
filesystem: The fake filesystem where the file is created.
encoding: If contents is a unicode string, the encoding used
for serialization.
@@ -265,67 +311,71 @@ class FakeFile:
# 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.filesystem: FakeFilesystem = filesystem
+ self._side_effect: Optional[Callable] = side_effect
+ self.name: AnyStr = name # type: ignore[assignment]
self.stat_result = FakeStatResult(
- filesystem.is_windows_fs, USER_ID, GROUP_ID, time.time())
+ filesystem.is_windows_fs, USER_ID, GROUP_ID, now())
+ if st_mode >> 12 == 0:
+ st_mode |= S_IFREG
self.stat_result.st_mode = st_mode
- self.encoding = encoding
- self.errors = errors or 'strict'
- self._byte_contents = self._encode_contents(contents)
+ self.st_size: int = 0
+ self.encoding: Optional[str] = real_encoding(encoding)
+ self.errors: str = errors or 'strict'
+ self._byte_contents: Optional[bytes] = self._encode_contents(contents)
self.stat_result.st_size = (
len(self._byte_contents) if self._byte_contents is not None else 0)
- self.epoch = 0
- self.parent_dir = None
+ self.epoch: int = 0
+ self.parent_dir: Optional[FakeDirectory] = None
# Linux specific: extended file system attributes
- self.xattr = {}
+ self.xattr: Dict = {}
+ self.opened_as: AnyString = ''
@property
- def byte_contents(self):
+ def byte_contents(self) -> Optional[bytes]:
"""Return the contents as raw byte array."""
return self._byte_contents
@property
- def contents(self):
+ def contents(self) -> Optional[str]:
"""Return the contents as string with the original encoding."""
if isinstance(self.byte_contents, bytes):
return self.byte_contents.decode(
self.encoding or locale.getpreferredencoding(False),
errors=self.errors)
- return self.byte_contents
+ return None
@property
- def st_ctime(self):
+ def st_ctime(self) -> float:
"""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):
+ def st_ctime(self, val: float) -> None:
"""Set the creation time of the fake file."""
self.stat_result.st_ctime = val
+ @property
+ def st_atime(self) -> float:
+ """Return the access time of the fake file."""
+ return self.stat_result.st_atime
+
@st_atime.setter
- def st_atime(self, val):
+ def st_atime(self, val: float) -> None:
"""Set the access time of the fake file."""
self.stat_result.st_atime = val
+ @property
+ def st_mtime(self) -> float:
+ """Return the modification time of the fake file."""
+ return self.stat_result.st_mtime
+
@st_mtime.setter
- def st_mtime(self, val):
+ def st_mtime(self, val: float) -> None:
"""Set the modification time of the fake file."""
self.stat_result.st_mtime = val
- def set_large_file_size(self, st_size):
+ def set_large_file_size(self, st_size: int) -> None:
"""Sets the self.st_size attribute and replaces self.content with None.
Provided specifically to simulate very large files without regards
@@ -348,25 +398,27 @@ class FakeFile:
self.st_size = st_size
self._byte_contents = None
- def _check_positive_int(self, size):
+ def _check_positive_int(self, size: int) -> None:
# the size should be an positive integer value
if not is_int_type(size) or size < 0:
self.filesystem.raise_os_error(errno.ENOSPC, self.name)
- def is_large_file(self):
- """Return `True` if this file was initialized with size but no contents.
+ def is_large_file(self) -> bool:
+ """Return `True` if this file was initialized with size
+ but no contents.
"""
return self._byte_contents is None
- def _encode_contents(self, contents):
+ def _encode_contents(
+ self, contents: Union[str, bytes, None]) -> Optional[bytes]:
if is_unicode_string(contents):
contents = bytes(
- contents,
+ cast(str, contents),
self.encoding or locale.getpreferredencoding(False),
self.errors)
- return contents
+ return cast(bytes, contents)
- def _set_initial_contents(self, contents):
+ def set_initial_contents(self, contents: AnyStr) -> bool:
"""Sets the file contents and size.
Called internally after initial file creation.
@@ -380,78 +432,50 @@ class FakeFile:
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)
+ byte_contents = self._encode_contents(contents)
+ changed = self._byte_contents != byte_contents
+ st_size = len(byte_contents) if byte_contents else 0
- 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._byte_contents = byte_contents
self.st_size = st_size
self.epoch += 1
return changed
- def set_contents(self, contents, encoding=None):
+ def set_contents(self, contents: AnyStr,
+ encoding: Optional[str] = None) -> bool:
"""Sets the file contents and size and increases the modification time.
Also executes the side_effects if available.
Args:
- contents: (str, bytes, unicode) new content of file.
+ contents: (str, bytes) new content of file.
encoding: (str) the encoding to be used for writing the contents
if they are a unicode string.
If not given, the locale preferred encoding is used.
+ Returns:
+ True if the contents have been changed.
+
Raises:
OSError: if `st_size` is not a non-negative integer,
or if it exceeds the available file system space.
"""
- self.encoding = encoding
- changed = self._set_initial_contents(contents)
+ self.encoding = real_encoding(encoding)
+ changed = self.set_initial_contents(contents)
if self._side_effect is not None:
self._side_effect(self)
return changed
@property
- def size(self):
+ def size(self) -> int:
"""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):
+ def size(self, st_size: int) -> None:
"""Resizes file content, padding with nulls if new size exceeds the
old size.
@@ -475,6 +499,36 @@ class FakeFile:
self.st_size = st_size
self.epoch += 1
+ @property
+ def path(self) -> AnyStr:
+ """Return the full path of the current object."""
+ names: List[AnyStr] = []
+ obj: Optional[FakeFile] = self
+ while obj:
+ names.insert(
+ 0, matching_string(self.name, obj.name)) # type: ignore
+ obj = obj.parent_dir
+ sep = self.filesystem.get_path_separator(names[0])
+ if names[0] == sep:
+ names.pop(0)
+ dir_path = sep.join(names)
+ drive = self.filesystem.splitdrive(dir_path)[0]
+ # if a Windows path already starts with a drive or UNC path,
+ # no extra separator is needed
+ if not drive:
+ dir_path = sep + dir_path
+ else:
+ dir_path = sep.join(names)
+ return self.filesystem.absnormpath(dir_path)
+
+ @Deprecator('property path')
+ def GetPath(self):
+ return self.path
+
+ @Deprecator('property size')
+ def GetSize(self):
+ return self.size
+
@Deprecator('property size')
def SetSize(self, value):
self.size = value
@@ -506,20 +560,20 @@ class FakeFile:
"""
self.st_ctime = st_ctime
- def __getattr__(self, item):
+ def __getattr__(self, item: str) -> Any:
"""Forward some properties to stat_result."""
if item in self.stat_types:
return getattr(self.stat_result, item)
- return super(FakeFile, self).__getattr__(item)
+ return super().__getattribute__(item)
- def __setattr__(self, key, value):
+ def __setattr__(self, key: str, value: Any) -> None:
"""Forward some properties to stat_result."""
if key in self.stat_types:
return setattr(self.stat_result, key, value)
- return super(FakeFile, self).__setattr__(key, value)
+ return super().__setattr__(key, value)
- def __str__(self):
- return '%s(%o)' % (self.name, self.st_mode)
+ def __str__(self) -> str:
+ return '%r(%o)' % (self.name, self.st_mode)
@Deprecator('st_ino')
def SetIno(self, st_ino):
@@ -535,17 +589,17 @@ class FakeFile:
class FakeNullFile(FakeFile):
- def __init__(self, filesystem):
- devnull = '/dev/nul' if filesystem.is_windows_fs else '/dev/nul'
+ def __init__(self, filesystem: "FakeFilesystem") -> None:
+ devnull = 'nul' if filesystem.is_windows_fs else '/dev/null'
super(FakeNullFile, self).__init__(
- devnull, filesystem=filesystem, contents=b'')
+ devnull, filesystem=filesystem, contents='')
@property
- def byte_contents(self):
+ def byte_contents(self) -> bytes:
return b''
- def _set_initial_contents(self, contents):
- pass
+ def set_initial_contents(self, contents: AnyStr) -> bool:
+ return False
Deprecator.add(FakeFile, FakeFile.set_large_file_size, 'SetLargeFileSize')
@@ -559,7 +613,8 @@ class FakeFileFromRealFile(FakeFile):
The contents of the file are read on demand only.
"""
- def __init__(self, file_path, filesystem, side_effect=None):
+ def __init__(self, file_path: str, filesystem: "FakeFilesystem",
+ side_effect: Optional[Callable] = None) -> None:
"""
Args:
file_path: Path to the existing file.
@@ -569,13 +624,13 @@ class FakeFileFromRealFile(FakeFile):
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__(
+ super().__init__(
name=os.path.basename(file_path), filesystem=filesystem,
side_effect=side_effect)
self.contents_read = False
@property
- def byte_contents(self):
+ def byte_contents(self) -> Optional[bytes]:
if not self.contents_read:
self.contents_read = True
with io.open(self.file_path, 'rb') as f:
@@ -596,7 +651,8 @@ class FakeFileFromRealFile(FakeFile):
class FakeDirectory(FakeFile):
"""Provides the appearance of a real directory."""
- def __init__(self, name, perm_bits=PERM_DEF, filesystem=None):
+ def __init__(self, name: str, perm_bits: int = PERM_DEF,
+ filesystem: Optional["FakeFilesystem"] = None):
"""
Args:
name: name of the file/directory, without parent path information
@@ -605,28 +661,30 @@ class FakeDirectory(FakeFile):
is created
"""
FakeFile.__init__(
- self, name, S_IFDIR | perm_bits, {}, filesystem=filesystem)
+ self, name, S_IFDIR | perm_bits, '', filesystem=filesystem)
# directories have the link count of contained entries,
- # inclusing '.' and '..'
+ # including '.' and '..'
self.st_nlink += 1
+ self._entries: Dict[str, AnyFile] = {}
- def set_contents(self, contents, encoding=None):
+ def set_contents(self, contents: AnyStr,
+ encoding: Optional[str] = None) -> bool:
raise self.filesystem.raise_os_error(errno.EISDIR, self.path)
@property
- def contents(self):
+ def entries(self) -> Dict[str, FakeFile]:
"""Return the list of contained directory entries."""
- return self.byte_contents
+ return self._entries
@property
- def ordered_dirs(self):
+ def ordered_dirs(self) -> List[str]:
"""Return the list of contained directory entry names ordered by
creation order.
"""
return [item[0] for item in sorted(
- self.byte_contents.items(), key=lambda entry: entry[1].st_ino)]
+ self._entries.items(), key=lambda entry: entry[1].st_ino)]
- def add_entry(self, path_object):
+ def add_entry(self, path_object: FakeFile) -> None:
"""Adds a child FakeFile to this directory.
Args:
@@ -640,11 +698,11 @@ class FakeDirectory(FakeFile):
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:
+ path_object_name: str = to_string(path_object.name)
+ if path_object_name in self.entries:
self.filesystem.raise_os_error(errno.EEXIST, self.path)
- self.contents[path_object_name] = path_object
+ self._entries[path_object_name] = path_object
path_object.parent_dir = self
if path_object.st_ino is None:
self.filesystem.last_ino += 1
@@ -656,7 +714,7 @@ class FakeDirectory(FakeFile):
self.filesystem.change_disk_usage(
path_object.size, path_object.name, self.st_dev)
- def get_entry(self, pathname_name):
+ def get_entry(self, pathname_name: str) -> AnyFile:
"""Retrieves the specified child file or directory entry.
Args:
@@ -669,17 +727,17 @@ class FakeDirectory(FakeFile):
KeyError: if no child exists by the specified name.
"""
pathname_name = self._normalized_entryname(pathname_name)
- return self.contents[to_string(pathname_name)]
+ return self.entries[to_string(pathname_name)]
- def _normalized_entryname(self, pathname_name):
+ def _normalized_entryname(self, pathname_name: str) -> str:
if not self.filesystem.is_case_sensitive:
- matching_names = [name for name in self.contents
+ matching_names = [name for name in self.entries
if name.lower() == pathname_name.lower()]
if matching_names:
pathname_name = matching_names[0]
return pathname_name
- def remove_entry(self, pathname_name, recursive=True):
+ def remove_entry(self, pathname_name: str, recursive: bool = True) -> None:
"""Removes the specified child file or directory.
Args:
@@ -706,8 +764,8 @@ class FakeDirectory(FakeFile):
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])
+ while entry.entries:
+ entry.remove_entry(list(entry.entries)[0])
elif entry.st_nlink == 1:
self.filesystem.change_disk_usage(
-entry.size, pathname_name, entry.st_dev)
@@ -716,32 +774,37 @@ class FakeDirectory(FakeFile):
entry.st_nlink -= 1
assert entry.st_nlink >= 0
- del self.contents[to_string(pathname_name)]
+ del self.entries[to_string(pathname_name)]
@property
- def size(self):
+ def size(self) -> int:
"""Return the total size of all files contained in this directory tree.
"""
- return sum([item[1].size for item in self.contents.items()])
+ return sum([item[1].size for item in self.entries.items()])
+
+ @size.setter
+ def size(self, st_size: int) -> None:
+ """Setting the size is an error for a directory."""
+ raise self.filesystem.raise_os_error(errno.EISDIR, self.path)
@Deprecator('property size')
def GetSize(self):
return self.size
- def has_parent_object(self, dir_object):
+ def has_parent_object(self, dir_object: "FakeDirectory") -> bool:
"""Return `True` if dir_object is a direct or indirect parent
directory, or if both are the same object."""
- obj = self
+ obj: Optional[FakeDirectory] = self
while obj:
if obj == dir_object:
return True
obj = obj.parent_dir
return False
- def __str__(self):
+ def __str__(self) -> str:
description = super(FakeDirectory, self).__str__() + ':\n'
- for item in self.contents:
- item_desc = self.contents[item].__str__()
+ for item in self.entries:
+ item_desc = self.entries[item].__str__()
for line in item_desc.split('\n'):
if line:
description = description + ' ' + line + '\n'
@@ -760,8 +823,8 @@ class FakeDirectoryFromRealDirectory(FakeDirectory):
The contents of the directory are read on demand only.
"""
- def __init__(self, source_path, filesystem, read_only,
- target_path=None):
+ def __init__(self, source_path: AnyPath, filesystem: "FakeFilesystem",
+ read_only: bool, target_path: Optional[AnyPath] = None):
"""
Args:
source_path: Full directory path.
@@ -779,7 +842,7 @@ class FakeDirectoryFromRealDirectory(FakeDirectory):
target_path = target_path or source_path
real_stat = os.stat(source_path)
super(FakeDirectoryFromRealDirectory, self).__init__(
- name=os.path.split(target_path)[1],
+ name=to_string(os.path.split(target_path)[1]),
perm_bits=real_stat.st_mode,
filesystem=filesystem)
@@ -788,12 +851,12 @@ class FakeDirectoryFromRealDirectory(FakeDirectory):
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.source_path = source_path # type: ignore
self.read_only = read_only
self.contents_read = False
@property
- def contents(self):
+ def entries(self) -> Dict[str, FakeFile]:
"""Return the list of contained directory entries, loading them
if not already loaded."""
if not self.contents_read:
@@ -801,7 +864,7 @@ class FakeDirectoryFromRealDirectory(FakeDirectory):
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)
+ target_path = os.path.join(base, entry) # type: ignore
if os.path.islink(source_path):
self.filesystem.add_real_symlink(source_path, target_path)
elif os.path.isdir(source_path):
@@ -810,15 +873,19 @@ class FakeDirectoryFromRealDirectory(FakeDirectory):
else:
self.filesystem.add_real_file(
source_path, self.read_only, target_path=target_path)
- return self.byte_contents
+ return self._entries
@property
- def size(self):
+ def size(self) -> int:
# we cannot get the size until the contents are loaded
if not self.contents_read:
return 0
return super(FakeDirectoryFromRealDirectory, self).size
+ @size.setter
+ def size(self, st_size: int) -> None:
+ raise self.filesystem.raise_os_error(errno.EISDIR, self.path)
+
class FakeFilesystem:
"""Provides the appearance of a real directory tree for unit testing.
@@ -836,8 +903,9 @@ class FakeFilesystem:
to the patcher object if using the pytest fs fixture.
"""
- def __init__(self, path_separator=os.path.sep, total_size=None,
- patcher=None):
+ def __init__(self, path_separator: str = os.path.sep,
+ total_size: int = None,
+ patcher: Any = None) -> None:
"""
Args:
path_separator: optional substitute for os.path.sep
@@ -849,8 +917,8 @@ class FakeFilesystem:
>>> filesystem = FakeFilesystem(path_separator='/')
"""
- self.path_separator = path_separator
- self.alternative_path_separator = os.path.altsep
+ self.path_separator: str = path_separator
+ self.alternative_path_separator: Optional[str] = os.path.altsep
self.patcher = patcher
if path_separator != os.sep:
self.alternative_path_separator = None
@@ -877,22 +945,44 @@ class FakeFilesystem:
# A list of open file objects. Their position in the list is their
# file descriptor number
- self.open_files = []
+ self.open_files: List[Optional[List[AnyFileWrapper]]] = []
# A heap containing all free positions in self.open_files list
- self._free_fd_heap = []
+ self._free_fd_heap: List[int] = []
# last used numbers for inodes (st_ino) and devices (st_dev)
self.last_ino = 0
self.last_dev = 0
- self.mount_points = {}
+ self.mount_points: Dict[AnyString, Dict] = {}
self.add_mount_point(self.root.name, total_size)
self._add_standard_streams()
self.dev_null = FakeNullFile(self)
+ # set from outside if needed
+ self.patch_open_code = PatchMode.OFF
+ self.shuffle_listdir_results = False
@property
- def is_linux(self):
+ def is_linux(self) -> bool:
return not self.is_windows_fs and not self.is_macos
- def reset(self, total_size=None):
+ @property
+ def os(self) -> OSType:
+ """Return the real or simulated type of operating system."""
+ return (OSType.WINDOWS if self.is_windows_fs else
+ OSType.MACOS if self.is_macos else OSType.LINUX)
+
+ @os.setter
+ def os(self, value: OSType) -> None:
+ """Set the simulated type of operating system underlying the fake
+ file system."""
+ self.is_windows_fs = value == OSType.WINDOWS
+ self.is_macos = value == OSType.MACOS
+ self.is_case_sensitive = value == OSType.LINUX
+ self.path_separator = '\\' if value == OSType.WINDOWS else '/'
+ self.alternative_path_separator = ('/' if value == OSType.WINDOWS
+ else None)
+ self.reset()
+ FakePathModule.reset(self)
+
+ def reset(self, total_size: Optional[int] = None):
"""Remove all file system contents and reset the root."""
self.root = FakeDirectory(self.path_separator, filesystem=self)
self.cwd = self.root.name
@@ -904,8 +994,10 @@ class FakeFilesystem:
self.mount_points = {}
self.add_mount_point(self.root.name, total_size)
self._add_standard_streams()
+ from pyfakefs import fake_pathlib
+ fake_pathlib.init_module(self)
- def pause(self):
+ def pause(self) -> None:
"""Pause the patching of the file system modules until `resume` is
called. After that call, all file system calls are executed in the
real file system.
@@ -921,7 +1013,7 @@ class FakeFilesystem:
'system object created by a Patcher object')
self.patcher.pause()
- def resume(self):
+ def resume(self) -> None:
"""Resume the patching of the file system modules if `pause` has
been called before. After that call, all file system calls are
executed in the fake file system.
@@ -934,13 +1026,20 @@ class FakeFilesystem:
'system object created by a Patcher object')
self.patcher.resume()
- def line_separator(self):
+ def clear_cache(self) -> None:
+ """Clear the cache of non-patched modules."""
+ if self.patcher:
+ self.patcher.clear_cache()
+
+ def line_separator(self) -> str:
return '\r\n' if self.is_windows_fs else '\n'
- def _error_message(self, errno):
- return os.strerror(errno) + ' in the fake filesystem'
+ def _error_message(self, err_no: int) -> str:
+ return os.strerror(err_no) + ' in the fake filesystem'
- def raise_os_error(self, errno, filename=None, winerror=None):
+ def raise_os_error(self, err_no: int,
+ filename: Optional[AnyString] = None,
+ winerror: Optional[int] = None) -> NoReturn:
"""Raises OSError.
The error message is constructed from the given error code and shall
start with the error string issued in the real system.
@@ -949,36 +1048,34 @@ class FakeFilesystem:
real file system.
Args:
- errno: A numeric error code from the C variable errno.
+ err_no: A numeric error code from the C variable errno.
filename: The name of the affected file, if any.
winerror: Windows only - the specific Windows error code.
"""
- message = self._error_message(errno)
+ message = self._error_message(err_no)
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
+ raise OSError(err_no, message, filename, winerror)
+ raise OSError(err_no, message, filename)
- def _path_separator(self, path):
+ def get_path_separator(self, path: AnyStr) -> AnyStr:
"""Return the path separator as the same type as path"""
- return self._matching_string(path, self.path_separator)
+ return matching_string(path, self.path_separator)
- def _alternative_path_separator(self, path):
+ def _alternative_path_separator(
+ self, path: AnyStr) -> Optional[AnyStr]:
"""Return the alternative path separator as the same type as path"""
- return self._matching_string(path, self.alternative_path_separator)
+ return matching_string(path, self.alternative_path_separator)
+
+ def _starts_with_sep(self, path: AnyStr) -> bool:
+ """Return True if path starts with a path separator."""
+ sep = self.get_path_separator(path)
+ altsep = self._alternative_path_separator(path)
+ return (path.startswith(sep) or altsep is not None and
+ path.startswith(altsep))
- def add_mount_point(self, path, total_size=None):
+ def add_mount_point(self, path: AnyStr,
+ total_size: Optional[int] = None) -> Dict:
"""Add a new mount point for a filesystem device.
The mount point gets a new unique device number.
@@ -1011,44 +1108,42 @@ class FakeFilesystem:
root_dir.st_dev = self.last_dev
return self.mount_points[path]
- def _auto_mount_drive_if_needed(self, path, force=False):
+ def _auto_mount_drive_if_needed(self, path: AnyStr,
+ force: bool = False) -> Optional[Dict]:
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)
+ return None
- 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))
-
+ def _mount_point_for_path(self, path: AnyStr) -> Dict:
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 mount_path in self.mount_points:
+ if path == matching_string(path, mount_path):
+ return self.mount_points[mount_path]
+ mount_path = matching_string(path, '')
+ drive = self.splitdrive(path)[0]
for root_path in self.mount_points:
- root_path = self._matching_string(path, root_path)
+ root_path = matching_string(path, root_path)
if drive and not root_path.startswith(drive):
continue
if path.startswith(root_path) and len(root_path) > len(mount_path):
mount_path = root_path
if mount_path:
- return self.mount_points[to_str(mount_path)]
+ return self.mount_points[to_string(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):
+ def _mount_point_for_device(self, idev: int) -> Optional[Dict]:
for mount_point in self.mount_points.values():
if mount_point['idev'] == idev:
return mount_point
+ return None
- def get_disk_usage(self, path=None):
+ def get_disk_usage(
+ self, path: AnyStr = None) -> Tuple[int, int, int]:
"""Return the total, used and free disk space in bytes as named tuple,
or placeholder values simulating unlimited space if not set.
@@ -1059,7 +1154,7 @@ class FakeFilesystem:
`path` resides.
Defaults to the root path (e.g. '/' on Unix systems).
"""
- DiskUsage = namedtuple('usage', 'total, used, free')
+ DiskUsage = namedtuple('DiskUsage', 'total, used, free')
if path is None:
mount_point = self.mount_points[self.root.name]
else:
@@ -1072,8 +1167,10 @@ class FakeFilesystem:
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.
+ def set_disk_usage(
+ self, total_size: int, path: Optional[AnyStr] = None) -> None:
+ """Changes the total size of the file system, preserving the
+ used space.
Example usage: set the size of an auto-mounted Windows drive.
Args:
@@ -1086,15 +1183,16 @@ class FakeFilesystem:
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)
+ file_path: AnyStr = (path if path is not None # type: ignore
+ else self.root.name)
+ mount_point = self._mount_point_for_path(file_path)
if (mount_point['total_size'] is not None and
mount_point['used_size'] > total_size):
self.raise_os_error(errno.ENOSPC, path)
mount_point['total_size'] = total_size
- def change_disk_usage(self, usage_change, file_path, st_dev):
+ def change_disk_usage(self, usage_change: int,
+ file_path: AnyStr, st_dev: int) -> None:
"""Change the used disk space by the given amount.
Args:
@@ -1116,7 +1214,8 @@ class FakeFilesystem:
self.raise_os_error(errno.ENOSPC, file_path)
mount_point['used_size'] += usage_change
- def stat(self, entry_path, follow_symlinks=True):
+ def stat(self, entry_path: AnyStr,
+ follow_symlinks: bool = True):
"""Return the os.stat-like tuple for the FakeFile object of entry_path.
Args:
@@ -1131,24 +1230,28 @@ class FakeFilesystem:
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)
+ try:
+ file_object = self.resolve(
+ entry_path, follow_symlinks,
+ allow_fd=True, check_read_perm=False)
+ except TypeError:
+ file_object = self.resolve(entry_path)
if not is_root():
# make sure stat raises if a parent dir is not readable
parent_dir = file_object.parent_dir
if parent_dir:
- self.get_object(parent_dir.path)
+ self.get_object(parent_dir.path) # type: ignore[arg-type]
self.raise_for_filepath_ending_with_separator(
entry_path, file_object, follow_symlinks)
return file_object.stat_result.copy()
- def raise_for_filepath_ending_with_separator(self, entry_path,
- file_object,
- follow_symlinks=True,
- macos_handling=False):
+ def raise_for_filepath_ending_with_separator(
+ self, entry_path: AnyStr,
+ file_object: FakeFile,
+ follow_symlinks: bool = True,
+ macos_handling: bool = False) -> None:
if self.ends_with_path_separator(entry_path):
if S_ISLNK(file_object.st_mode):
try:
@@ -1172,7 +1275,8 @@ class FakeFilesystem:
else errno.ENOTDIR)
self.raise_os_error(error_nr, entry_path)
- def chmod(self, path, mode, follow_symlinks=True):
+ def chmod(self, path: AnyStr, mode: int,
+ follow_symlinks: bool = True) -> None:
"""Change the permissions of a file as encoded in integer mode.
Args:
@@ -1190,9 +1294,12 @@ class FakeFilesystem:
else:
file_object.st_mode = ((file_object.st_mode & ~PERM_ALL) |
(mode & PERM_ALL))
- file_object.st_ctime = time.time()
+ file_object.st_ctime = now()
- def utime(self, path, times=None, *, ns=None, follow_symlinks=True):
+ def utime(self, path: AnyStr,
+ times: Optional[Tuple[Union[int, float], Union[int, float]]] =
+ None, *, ns: Optional[Tuple[int, int]] = None,
+ follow_symlinks: bool = True) -> None:
"""Change the access and modified times of a file.
Args:
@@ -1230,11 +1337,13 @@ class FakeFilesystem:
file_object.st_atime_ns = ns[0]
file_object.st_mtime_ns = ns[1]
else:
- current_time = time.time()
+ current_time = now()
file_object.st_atime = current_time
file_object.st_mtime = current_time
- def _handle_utime_arg_errors(self, ns, times):
+ def _handle_utime_arg_errors(
+ self, ns: Optional[Tuple[int, int]],
+ times: Optional[Tuple[Union[int, float], Union[int, float]]]):
if times is not None and ns is not None:
raise ValueError(
"utime: you may specify either 'times' or 'ns' but not both")
@@ -1257,7 +1366,9 @@ class FakeFilesystem:
"""
self.get_object(path).st_ino = st_ino
- def _add_open_file(self, file_obj):
+ def _add_open_file(
+ self,
+ file_obj: AnyFileWrapper) -> int:
"""Add file_obj to the list of open files on the filesystem.
Used internally to manage open files.
@@ -1277,7 +1388,7 @@ class FakeFilesystem:
self.open_files.append([file_obj])
return len(self.open_files) - 1
- def _close_open_file(self, file_des):
+ def _close_open_file(self, file_des: int) -> None:
"""Remove file object with given descriptor from the list
of open files.
@@ -1290,7 +1401,7 @@ class FakeFilesystem:
self.open_files[file_des] = None
heapq.heappush(self._free_fd_heap, file_des)
- def get_open_file(self, file_des):
+ def get_open_file(self, file_des: int) -> AnyFileWrapper:
"""Return an open file.
Args:
@@ -1305,12 +1416,14 @@ class FakeFilesystem:
"""
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):
+ valid = file_des < len(self.open_files)
+ if valid:
+ file_list = self.open_files[file_des]
+ if file_list is not None:
+ return file_list[0]
+ self.raise_os_error(errno.EBADF, str(file_des))
+
+ def has_open_file(self, file_object: FakeFile) -> bool:
"""Return True if the given file object is in the list of open files.
Args:
@@ -1322,13 +1435,13 @@ class FakeFilesystem:
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 _normalize_path_sep(self, path: AnyStr) -> AnyStr:
+ alt_sep = self._alternative_path_separator(path)
+ if alt_sep is not None:
+ return path.replace(alt_sep, self.get_path_separator(path))
+ return path
- def normcase(self, path):
+ def normcase(self, path: AnyStr) -> AnyStr:
"""Replace all appearances of alternative path separator
with path separator.
@@ -1340,10 +1453,10 @@ class FakeFilesystem:
Returns:
The normalized path that will be used internally.
"""
- path = make_string_path(path)
- return self._normalize_path_sep(path)
+ file_path = make_string_path(path)
+ return self._normalize_path_sep(file_path)
- def normpath(self, path):
+ def normpath(self, path: AnyStr) -> AnyStr:
"""Mimic os.path.normpath using the specified path_separator.
Mimics os.path.normpath using the path_separator that was specified
@@ -1365,14 +1478,14 @@ class FakeFilesystem:
(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, '..')
+ path_str = self.normcase(path)
+ drive, path_str = self.splitdrive(path_str)
+ sep = self.get_path_separator(path_str)
+ is_absolute_path = path_str.startswith(sep)
+ path_components: List[AnyStr] = path_str.split(sep)
+ collapsed_path_components: List[AnyStr] = []
+ dot = matching_string(path_str, '.')
+ dotdot = matching_string(path_str, '..')
for component in path_components:
if (not component) or (component == dot):
continue
@@ -1392,7 +1505,7 @@ class FakeFilesystem:
collapsed_path = sep + collapsed_path
return drive + collapsed_path or dot
- def _original_path(self, path):
+ def _original_path(self, path: AnyStr) -> AnyStr:
"""Return a normalized case version of the given path for
case-insensitive file systems. For case-sensitive file systems,
return path unchanged.
@@ -1407,10 +1520,12 @@ class FakeFilesystem:
def components_to_path():
if len(path_components) > len(normalized_components):
normalized_components.extend(
- path_components[len(normalized_components):])
- sep = self._path_separator(path)
+ to_string(p) for p in path_components[len(
+ normalized_components):])
+ sep = self.path_separator
normalized_path = sep.join(normalized_components)
- if path.startswith(sep) and not normalized_path.startswith(sep):
+ if (self._starts_with_sep(path)
+ and not self._starts_with_sep(normalized_path)):
normalized_path = sep + normalized_path
return normalized_path
@@ -1422,17 +1537,18 @@ class FakeFilesystem:
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):
+ dir_name, directory = self._directory_content(
+ current_dir, to_string(component))
+ if directory is None or (
+ isinstance(directory, FakeDirectory) and
+ directory._byte_contents is None and
+ directory.st_size == 0):
return components_to_path()
+ current_dir = cast(FakeDirectory, directory)
normalized_components.append(dir_name)
return components_to_path()
- def absnormpath(self, path):
+ def absnormpath(self, path: AnyStr) -> AnyStr:
"""Absolutize and minimalize the given path.
Forces all relative paths to be absolute, and normalizes the path to
@@ -1446,25 +1562,25 @@ class FakeFilesystem:
or the root directory if path is empty.
"""
path = self.normcase(path)
- cwd = self._matching_string(path, self.cwd)
+ cwd = matching_string(path, self.cwd)
if not path:
- path = self.path_separator
- if path == self._matching_string(path, '.'):
+ path = matching_string(path, self.path_separator)
+ if path == matching_string(path, '.'):
path = cwd
elif not self._starts_with_root_path(path):
# Prefix relative paths with cwd, if cwd is not root.
- root_name = self._matching_string(path, self.root.name)
- empty = self._matching_string(path, '')
- path = self._path_separator(path).join(
+ root_name = matching_string(path, self.root.name)
+ empty = matching_string(path, '')
+ path = self.get_path_separator(path).join(
(cwd != root_name and cwd or empty, path))
- if path == self._matching_string(path, '.'):
+ if path == matching_string(path, '.'):
path = cwd
return self.normpath(path)
- def splitpath(self, path):
- """Mimic os.path.splitpath using the specified path_separator.
+ def splitpath(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]:
+ """Mimic os.path.split using the specified path_separator.
- Mimics os.path.splitpath using the path_separator that was specified
+ Mimics os.path.split using the path_separator that was specified
for this FakeFilesystem.
Args:
@@ -1474,38 +1590,20 @@ class FakeFilesystem:
(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):
+ path = make_string_path(path)
+ sep = self.get_path_separator(path)
+ alt_sep = self._alternative_path_separator(path)
+ seps = sep if alt_sep is None else sep + alt_sep
+ drive, path = self.splitdrive(path)
+ i = len(path)
+ while i and path[i-1] not in seps:
+ i -= 1
+ head, tail = path[:i], path[i:] # now tail has no slashes
+ # remove trailing slashes from head, unless it's all slashes
+ head = head.rstrip(seps) or head
+ return drive + head, tail
+
+ def splitdrive(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]:
"""Splits the path into the drive part and the rest of the path.
Taken from Windows specific implementation in Python 3.5
@@ -1519,35 +1617,36 @@ class FakeFilesystem:
an empty string and the full path if drive letters are
not supported or no drive is present.
"""
- path = make_string_path(path)
+ path_str = 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
+ if len(path_str) >= 2:
+ norm_str = self.normcase(path_str)
+ sep = self.get_path_separator(path_str)
+ # UNC path_str handling
+ if (norm_str[0:2] == sep * 2) and (
+ norm_str[2:3] != sep):
+ # UNC path_str handling - splits off the mount point
# instead of the drive
- sep_index = path.find(sep, 2)
+ sep_index = norm_str.find(sep, 2)
if sep_index == -1:
- return path[:0], path
- sep_index2 = path.find(sep, sep_index + 1)
+ return path_str[:0], path_str
+ sep_index2 = norm_str.find(sep, sep_index + 1)
if sep_index2 == sep_index + 1:
- return path[:0], path
+ return path_str[:0], path_str
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):
+ sep_index2 = len(path_str)
+ return path_str[:sep_index2], path_str[sep_index2:]
+ if path_str[1:2] == matching_string(path_str, ':'):
+ return path_str[:2], path_str[2:]
+ return path_str[:0], path_str
+
+ def _join_paths_with_drive_support(
+ self, *all_paths: AnyStr) -> AnyStr:
"""Taken from Python 3.5 os.path.join() code in ntpath.py
and slightly adapted"""
base_path = all_paths[0]
paths_to_add = all_paths[1:]
- sep = self._path_separator(base_path)
+ sep = self.get_path_separator(base_path)
seps = [sep, self._alternative_path_separator(base_path)]
result_drive, result_path = self.splitdrive(base_path)
for path in paths_to_add:
@@ -1572,13 +1671,13 @@ class FakeFilesystem:
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, ':')
+ colon = matching_string(base_path, ':')
if (result_path and result_path[:1] not in seps and
result_drive and result_drive[-1:] != colon):
return result_drive + sep + result_path
return result_drive + result_path
- def joinpaths(self, *paths):
+ def joinpaths(self, *paths: AnyStr) -> AnyStr:
"""Mimic os.path.join using the specified path_separator.
Args:
@@ -1588,15 +1687,14 @@ class FakeFilesystem:
(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:
+ file_paths = [os.fspath(path) for path in paths]
+ if len(file_paths) == 1:
return paths[0]
if self.is_windows_fs:
- return self._join_paths_with_drive_support(*paths)
+ return self._join_paths_with_drive_support(*file_paths)
joined_path_segments = []
- sep = self._path_separator(paths[0])
- for path_segment in paths:
+ sep = self.get_path_separator(file_paths[0])
+ for path_segment in file_paths:
if self._starts_with_root_path(path_segment):
# An absolute path
joined_path_segments = [path_segment]
@@ -1606,9 +1704,15 @@ class FakeFilesystem:
joined_path_segments.append(sep)
if path_segment:
joined_path_segments.append(path_segment)
- return self._matching_string(paths[0], '').join(joined_path_segments)
+ return matching_string(file_paths[0], '').join(joined_path_segments)
+
+ @overload
+ def _path_components(self, path: str) -> List[str]: ...
- def _path_components(self, path):
+ @overload
+ def _path_components(self, path: bytes) -> List[bytes]: ...
+
+ def _path_components(self, path: AnyStr) -> List[AnyStr]:
"""Breaks the path into a list of component names.
Does not include the root directory as a component, as all paths
@@ -1621,7 +1725,7 @@ class FakeFilesystem:
path_components = self._path_components(file_path)
current_dir = self.root
for component in path_components:
- if component not in current_dir.contents:
+ if component not in current_dir.entries:
raise OSError
_do_stuff_with_component(current_dir, component)
current_dir = current_dir.get_entry(component)
@@ -1632,10 +1736,10 @@ class FakeFilesystem:
Returns:
The list of names split from path.
"""
- if not path or path == self._path_separator(path):
+ if not path or path == self.get_path_separator(path):
return []
drive, path = self.splitdrive(path)
- path_components = path.split(self._path_separator(path))
+ path_components = path.split(self.get_path_separator(path))
assert drive or path_components
if not path_components[0]:
if len(path_components) > 1 and not path_components[1]:
@@ -1647,7 +1751,7 @@ class FakeFilesystem:
path_components.insert(0, drive)
return path_components
- def _starts_with_drive_letter(self, file_path):
+ def _starts_with_drive_letter(self, file_path: AnyStr) -> bool:
"""Return True if file_path starts with a drive letter.
Args:
@@ -1657,62 +1761,77 @@ class FakeFilesystem:
`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)
+ colon = matching_string(file_path, ':')
+ if (len(file_path) >= 2 and
+ file_path[:1].isalpha and file_path[1:2] == colon):
+ if self.is_windows_fs:
+ return True
+ if os.name == 'nt':
+ # special case if we are emulating Posix under Windows
+ # check if the path exists because it has been mapped in
+ # this is not foolproof, but handles most cases
+ try:
+ self.get_object_from_normpath(file_path)
+ return True
+ except OSError:
+ return False
+ return False
- def _starts_with_root_path(self, file_path):
- root_name = self._matching_string(file_path, self.root.name)
+ def _starts_with_root_path(self, file_path: AnyStr) -> bool:
+ root_name = matching_string(file_path, self.root.name)
file_path = self._normalize_path_sep(file_path)
return (file_path.startswith(root_name) or
not self.is_case_sensitive and file_path.lower().startswith(
root_name.lower()) or
self._starts_with_drive_letter(file_path))
- def _is_root_path(self, file_path):
- root_name = self._matching_string(file_path, self.root.name)
+ def _is_root_path(self, file_path: AnyStr) -> bool:
+ root_name = 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):
+ def ends_with_path_separator(self, path: Union[int, AnyPath]) -> bool:
"""Return True if ``file_path`` ends with a valid path separator."""
- if is_int_type(file_path):
+ if isinstance(path, int):
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):
+ file_path = make_string_path(path)
+ if not file_path:
+ return False
+ sep = self.get_path_separator(file_path)
+ altsep = self._alternative_path_separator(file_path)
+ return (file_path not in (sep, altsep) and
+ (file_path.endswith(sep) or
+ altsep is not None and file_path.endswith(altsep)))
+
+ def is_filepath_ending_with_separator(self, path: AnyStr) -> bool:
if not self.ends_with_path_separator(path):
return False
return self.isfile(self._path_without_trailing_separators(path))
- def _directory_content(self, directory, component):
+ def _directory_content(self, directory: FakeDirectory,
+ component: str) -> Tuple[Optional[str],
+ Optional[AnyFile]]:
if not isinstance(directory, FakeDirectory):
return None, None
- if component in directory.contents:
- return component, directory.contents[component]
+ if component in directory.entries:
+ return component, directory.entries[component]
if not self.is_case_sensitive:
- matching_content = [(subdir, directory.contents[subdir]) for
- subdir in directory.contents
+ matching_content = [(subdir, directory.entries[subdir]) for
+ subdir in directory.entries
if subdir.lower() == component.lower()]
if matching_content:
return matching_content[0]
return None, None
- def exists(self, file_path, check_link=False):
+ def exists(self, file_path: AnyPath, check_link: bool = False) -> bool:
"""Return true if a path points to an existing file system object.
Args:
file_path: The path to examine.
+ check_link: If True, links are not followed
Returns:
(bool) True if the corresponding object exists.
@@ -1722,31 +1841,34 @@ class FakeFilesystem:
"""
if check_link and self.islink(file_path):
return True
- file_path = make_string_path(file_path)
- if file_path is None:
+ path = to_string(make_string_path(file_path))
+ if path is None:
raise TypeError
- if not file_path:
+ if not path:
return False
- if file_path == self.dev_null.name:
+ if 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):
+ if self.is_filepath_ending_with_separator(path):
return False
- file_path = self.resolve_path(file_path)
+ path = self.resolve_path(path)
except OSError:
return False
- if file_path == self.root.name:
+ if path == self.root.name:
return True
- path_components = self._path_components(file_path)
+ path_components: List[str] = self._path_components(path)
current_dir = self.root
for component in path_components:
- current_dir = self._directory_content(current_dir, component)[1]
- if not current_dir:
+ directory = self._directory_content(
+ current_dir, to_string(component))[1]
+ if directory is None:
return False
+ current_dir = cast(FakeDirectory, directory)
return True
- def resolve_path(self, file_path, allow_fd=False, raw_io=True):
+ def resolve_path(self,
+ file_path: AnyStr, allow_fd: bool = False) -> AnyStr:
"""Follow a path, resolving symlinks.
ResolvePath traverses the filesystem along the specified file path,
@@ -1775,10 +1897,9 @@ class FakeFilesystem:
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.
+ The resolved_path (str or byte).
Raises:
TypeError: if `file_path` is `None`.
@@ -1787,40 +1908,41 @@ class FakeFilesystem:
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:
+ path = make_string_path(file_path)
+ if path is None:
# file.open(None) raises TypeError, so mimic that.
raise TypeError('Expected file system path string, received None')
- if not file_path or not self._valid_relative_path(file_path):
+ if not path or not self._valid_relative_path(path):
# file.open('') raises OSError, so mimic that, and validate that
# all parts of a relative path exist.
- self.raise_os_error(errno.ENOENT, 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)
+ self.raise_os_error(errno.ENOENT, path)
+ path = self.absnormpath(self._original_path(path))
+ if self._is_root_path(path):
+ return path
+ if path == matching_string(path, self.dev_null.name):
+ return path
+ path_components = self._path_components(path)
+ resolved_components = self._resolve_components(path_components)
return self._components_to_path(resolved_components)
def _components_to_path(self, component_folders):
- sep = (self._path_separator(component_folders[0])
+ sep = (self.get_path_separator(component_folders[0])
if component_folders else self.path_separator)
path = sep.join(component_folders)
if not self._starts_with_root_path(path):
path = sep + path
return path
- def _resolve_components(self, path_components, raw_io):
+ def _resolve_components(self, components: List[AnyStr]) -> List[str]:
current_dir = self.root
link_depth = 0
- resolved_components = []
+ path_components = [to_string(comp) for comp in components]
+ resolved_components: List[str] = []
while path_components:
component = path_components.pop(0)
resolved_components.append(component)
- current_dir = self._directory_content(current_dir, component)[1]
- if current_dir is None:
+ directory = self._directory_content(current_dir, component)[1]
+ if directory is None:
# The component of the path at this point does not actually
# exist in the folder. We can't resolve the path any more.
# It is legal to link to a file that does not yet exist, so
@@ -1829,9 +1951,8 @@ class FakeFilesystem:
# return that.
resolved_components.extend(path_components)
break
-
# Resolve any possible symlinks in the current path component.
- if S_ISLNK(current_dir.st_mode):
+ elif S_ISLNK(directory.st_mode):
# This link_depth check is not really meant to be an accurate
# check. It is just a quick hack to prevent us from looping
# forever on cycles.
@@ -1839,7 +1960,7 @@ class FakeFilesystem:
self.raise_os_error(errno.ELOOP,
self._components_to_path(
resolved_components))
- link_path = self._follow_link(resolved_components, current_dir)
+ link_path = self._follow_link(resolved_components, directory)
# Following the link might result in the complete replacement
# of the current_dir, so we evaluate the entire resulting path.
@@ -1848,12 +1969,14 @@ class FakeFilesystem:
resolved_components = []
current_dir = self.root
link_depth += 1
+ else:
+ current_dir = cast(FakeDirectory, directory)
return resolved_components
- def _valid_relative_path(self, file_path):
+ def _valid_relative_path(self, file_path: AnyStr) -> bool:
if self.is_windows_fs:
return True
- slash_dotdot = self._matching_string(
+ slash_dotdot = matching_string(
file_path, self.path_separator + '..')
while file_path and slash_dotdot in file_path:
file_path = file_path[:file_path.rfind(slash_dotdot)]
@@ -1861,7 +1984,8 @@ class FakeFilesystem:
return False
return True
- def _follow_link(self, link_path_components, link):
+ def _follow_link(self, link_path_components: List[str],
+ link: AnyFile) -> str:
"""Follow a link w.r.t. a path resolved so far.
The component is either a real file, which is a no-op, or a
@@ -1885,27 +2009,31 @@ class FakeFilesystem:
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):
+ if link_path is not None:
+ # ignore UNC prefix for local files
+ if self.is_windows_fs and link_path.startswith('\\\\?\\'):
+ link_path = link_path[4:]
+ sep = self.get_path_separator(link_path)
+ # For links to absolute paths, we want to throw out everything
+ # in the path built so far and replace with the link. For relative
+ # links, we have to append the link to what we have so far,
+ if not self._starts_with_root_path(link_path):
+ # Relative path. Append remainder of path to what we have
+ # processed so far, excluding the name of the link itself.
+ # /a/b => ../c should yield /a/../c
+ # (which will normalize to /c)
+ # /a/b => d should yield a/d
+ components = link_path_components[:-1]
+ components.append(link_path)
+ link_path = sep.join(components)
+ # Don't call self.NormalizePath(), as we don't want to prepend
+ # self.cwd.
+ return self.normpath(link_path)
+ raise ValueError("Invalid link")
+
+ def get_object_from_normpath(self,
+ file_path: AnyPath,
+ check_read_perm: bool = True) -> AnyFile:
"""Search for the specified filesystem object within the fake
filesystem.
@@ -1921,32 +2049,35 @@ class FakeFilesystem:
Raises:
OSError: if the object is not found.
"""
- file_path = make_string_path(file_path)
- if file_path == self.root.name:
+ path = make_string_path(file_path)
+ if path == matching_string(path, self.root.name):
return self.root
- if file_path == self.dev_null.name:
+ if path == matching_string(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
+ path = self._original_path(path)
+ path_components = self._path_components(path)
+ target = 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 S_ISLNK(target.st_mode):
+ if target.contents:
+ target = cast(FakeDirectory,
+ self.resolve(target.contents))
+ if not S_ISDIR(target.st_mode):
if not self.is_windows_fs:
- self.raise_os_error(errno.ENOTDIR, 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)
+ self.raise_os_error(errno.ENOTDIR, path)
+ self.raise_os_error(errno.ENOENT, path)
+ target = target.get_entry(component) # type: ignore
+ if (not is_root() and check_read_perm and target and
+ not target.st_mode & PERM_READ):
+ self.raise_os_error(errno.EACCES, target.path)
except KeyError:
- self.raise_os_error(errno.ENOENT, file_path)
- return target_object
+ self.raise_os_error(errno.ENOENT, path)
+ return target
- def get_object(self, file_path, check_read_perm=True):
+ def get_object(self, file_path: AnyPath,
+ check_read_perm: bool = True) -> FakeFile:
"""Search for the specified filesystem object within the fake
filesystem.
@@ -1961,12 +2092,14 @@ class FakeFilesystem:
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)
+ path = make_string_path(file_path)
+ path = self.absnormpath(self._original_path(path))
+ return self.get_object_from_normpath(path, check_read_perm)
- def resolve(self, file_path, follow_symlinks=True, allow_fd=False,
- check_read_perm=True):
+ def resolve(self, file_path: AnyStr,
+ follow_symlinks: bool = True,
+ allow_fd: bool = False,
+ check_read_perm: bool = True) -> FakeFile:
"""Search for the specified filesystem object, resolving all links.
Args:
@@ -1987,15 +2120,14 @@ class FakeFilesystem:
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')
+ 'os.PathLike, 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):
+ def lresolve(self, path: AnyPath) -> FakeFile:
"""Search for the specified object, resolving only parent links.
This is analogous to the stat/lstat difference. This resolves links
@@ -2010,37 +2142,37 @@ class FakeFilesystem:
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:
+ path_str = make_string_path(path)
+ if not path_str:
+ raise OSError(errno.ENOENT, path_str)
+ if path_str == matching_string(path_str, self.root.name):
# The root directory will never be a link
return self.root
# remove trailing separator
- path = self._path_without_trailing_separators(path)
- if path == self._matching_string(path, '.'):
- path = self.cwd
- path = self._original_path(path)
+ path_str = self._path_without_trailing_separators(path_str)
+ if path_str == matching_string(path_str, '.'):
+ path_str = matching_string(path_str, self.cwd)
+ path_str = self._original_path(path_str)
- parent_directory, child_name = self.splitpath(path)
+ parent_directory, child_name = self.splitpath(path_str)
if not parent_directory:
- parent_directory = self.cwd
+ parent_directory = matching_string(path_str, self.cwd)
try:
parent_obj = self.resolve(parent_directory)
assert parent_obj
if not isinstance(parent_obj, FakeDirectory):
if not self.is_windows_fs and isinstance(parent_obj, FakeFile):
- self.raise_os_error(errno.ENOTDIR, path)
- self.raise_os_error(errno.ENOENT, path)
+ self.raise_os_error(errno.ENOTDIR, path_str)
+ self.raise_os_error(errno.ENOENT, path_str)
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
+ return (parent_obj.get_entry(to_string(child_name)) if child_name
else parent_obj)
except KeyError:
- self.raise_os_error(errno.ENOENT, path)
+ self.raise_os_error(errno.ENOENT, path_str)
- def add_object(self, file_path, file_object):
+ def add_object(self, file_path: AnyStr, file_object: AnyFile) -> None:
"""Add a fake file or directory into the filesystem at file_path.
Args:
@@ -2054,13 +2186,15 @@ class FakeFilesystem:
if not file_path:
target_directory = self.root
else:
- target_directory = self.resolve(file_path)
+ target_directory = cast(FakeDirectory, 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):
+ def rename(self, old_file_path: AnyPath,
+ new_file_path: AnyPath,
+ force_replace: bool = False) -> None:
"""Renames a FakeFile object at old_file_path to new_file_path,
preserving all properties.
@@ -2085,52 +2219,69 @@ class FakeFilesystem:
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)
+ old_path = make_string_path(old_file_path)
+ new_path = make_string_path(new_file_path)
+ ends_with_sep = self.ends_with_path_separator(old_path)
+ old_path = self.absnormpath(old_path)
+ new_path = self.absnormpath(new_path)
+ if not self.exists(old_path, check_link=True):
+ self.raise_os_error(errno.ENOENT, old_path, 2)
if ends_with_sep:
- self._handle_broken_link_with_trailing_sep(old_file_path)
+ self._handle_broken_link_with_trailing_sep(old_path)
- old_object = self.lresolve(old_file_path)
+ old_object = self.lresolve(old_path)
if not self.is_windows_fs:
self._handle_posix_dir_link_errors(
- new_file_path, old_file_path, ends_with_sep)
+ new_path, old_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,
+ if self.exists(new_path, check_link=True):
+ renamed_path = self._rename_to_existing_path(
+ force_replace, new_path, old_path,
old_object, ends_with_sep)
- if not new_file_path:
- return
+ if renamed_path is None:
+ return
+ else:
+ new_path = renamed_path
- old_dir, old_name = self.splitpath(old_file_path)
- new_dir, new_name = self.splitpath(new_file_path)
+ old_dir, old_name = self.splitpath(old_path)
+ new_dir, new_name = self.splitpath(new_path)
if not self.exists(new_dir):
self.raise_os_error(errno.ENOENT, new_dir)
old_dir_object = self.resolve(old_dir)
new_dir_object = self.resolve(new_dir)
if old_dir_object.st_dev != new_dir_object.st_dev:
- self.raise_os_error(errno.EXDEV, old_file_path)
+ self.raise_os_error(errno.EXDEV, old_path)
if not S_ISDIR(new_dir_object.st_mode):
self.raise_os_error(
errno.EACCES if self.is_windows_fs else errno.ENOTDIR,
- new_file_path)
+ new_path)
if new_dir_object.has_parent_object(old_object):
- self.raise_os_error(errno.EINVAL, new_file_path)
+ self.raise_os_error(errno.EINVAL, new_path)
+ self._do_rename(old_dir_object, old_name, new_dir_object, new_name)
+
+ def _do_rename(self, old_dir_object, old_name, new_dir_object, new_name):
object_to_rename = old_dir_object.get_entry(old_name)
old_dir_object.remove_entry(old_name, recursive=False)
object_to_rename.name = new_name
new_name = new_dir_object._normalized_entryname(new_name)
- 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)
+ old_entry = (new_dir_object.get_entry(new_name)
+ if new_name in new_dir_object.entries else None)
+ try:
+ if old_entry:
+ # in case of overwriting remove the old entry first
+ new_dir_object.remove_entry(new_name)
+ new_dir_object.add_entry(object_to_rename)
+ except OSError:
+ # adding failed, roll back the changes before re-raising
+ if old_entry and new_name not in new_dir_object.entries:
+ new_dir_object.add_entry(old_entry)
+ object_to_rename.name = old_name
+ old_dir_object.add_entry(object_to_rename)
+ raise
- def _handle_broken_link_with_trailing_sep(self, path):
+ def _handle_broken_link_with_trailing_sep(self, path: AnyStr) -> None:
# note that the check for trailing sep has to be done earlier
if self.islink(path):
if not self.exists(path):
@@ -2138,8 +2289,9 @@ class FakeFilesystem:
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):
+ def _handle_posix_dir_link_errors(self, new_file_path: AnyStr,
+ old_file_path: AnyStr,
+ ends_with_sep: bool) -> None:
if (self.isdir(old_file_path, follow_symlinks=False) and
self.islink(new_file_path)):
self.raise_os_error(errno.ENOTDIR, new_file_path)
@@ -2153,19 +2305,21 @@ class FakeFilesystem:
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):
+ def _rename_to_existing_path(self, force_replace: bool,
+ new_file_path: AnyStr,
+ old_file_path: AnyStr,
+ old_object: FakeFile,
+ ends_with_sep: bool) -> Optional[AnyStr]:
new_object = self.get_object(new_file_path)
if old_file_path == new_file_path:
if not S_ISLNK(new_object.st_mode) and ends_with_sep:
error = errno.EINVAL if self.is_windows_fs else errno.ENOTDIR
self.raise_os_error(error, old_file_path)
- return # Nothing to do here.
+ return None # 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)):
+ return self._rename_same_object(new_file_path, old_file_path)
+ if S_ISDIR(new_object.st_mode) or S_ISLNK(new_object.st_mode):
self._handle_rename_error_for_dir_or_link(
force_replace, new_file_path,
new_object, old_object, ends_with_sep)
@@ -2178,23 +2332,26 @@ class FakeFilesystem:
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):
+ def _handle_rename_error_for_dir_or_link(self, force_replace: bool,
+ new_file_path: AnyStr,
+ new_object: FakeFile,
+ old_object: FakeFile,
+ ends_with_sep: bool) -> None:
if self.is_windows_fs:
if force_replace:
self.raise_os_error(errno.EACCES, new_file_path)
else:
self.raise_os_error(errno.EEXIST, new_file_path)
if not S_ISLNK(new_object.st_mode):
- if new_object.contents:
+ if new_object.entries:
if (not S_ISLNK(old_object.st_mode) or
not ends_with_sep or not self.is_macos):
self.raise_os_error(errno.ENOTEMPTY, new_file_path)
if S_ISREG(old_object.st_mode):
self.raise_os_error(errno.EISDIR, new_file_path)
- def _rename_same_object(self, new_file_path, old_file_path):
+ def _rename_same_object(self, new_file_path: AnyStr,
+ old_file_path: AnyStr) -> Optional[AnyStr]:
do_rename = old_file_path.lower() == new_file_path.lower()
if not do_rename:
try:
@@ -2225,10 +2382,10 @@ class FakeFilesystem:
pass
if not do_rename:
# hard links to the same file - nothing to do
- new_file_path = None
+ return None
return new_file_path
- def remove_object(self, file_path):
+ def remove_object(self, file_path: AnyStr) -> None:
"""Remove an existing file or directory.
Args:
@@ -2251,13 +2408,14 @@ class FakeFilesystem:
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 make_string_path(self, path: AnyPath) -> AnyStr:
+ path_str = make_string_path(path)
+ os_sep = matching_string(path_str, os.sep)
+ fake_sep = matching_string(path_str, self.path_separator)
+ return path_str.replace(os_sep, fake_sep) # type: ignore[return-value]
- def create_dir(self, directory_path, perm_bits=PERM_DEF):
+ def create_dir(self, directory_path: AnyPath,
+ perm_bits: int = PERM_DEF) -> FakeDirectory:
"""Create `directory_path`, and all the parent directories.
Helper method to set up your test faster.
@@ -2272,17 +2430,19 @@ class FakeFilesystem:
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)
+ dir_path = self.make_string_path(directory_path)
+ dir_path = self.absnormpath(dir_path)
+ self._auto_mount_drive_if_needed(dir_path)
+ if (self.exists(dir_path, check_link=True) and
+ dir_path not in self.mount_points):
+ self.raise_os_error(errno.EEXIST, dir_path)
+ path_components = self._path_components(dir_path)
current_dir = self.root
new_dirs = []
- for component in path_components:
- directory = self._directory_content(current_dir, component)[1]
+ for component in [to_string(p) for p in path_components]:
+ directory = self._directory_content(
+ current_dir, to_string(component))[1]
if not directory:
new_dir = FakeDirectory(component, filesystem=self)
new_dirs.append(new_dir)
@@ -2290,8 +2450,10 @@ class FakeFilesystem:
current_dir = new_dir
else:
if S_ISLNK(directory.st_mode):
+ assert directory.contents
directory = self.resolve(directory.contents)
- current_dir = directory
+ assert directory
+ current_dir = cast(FakeDirectory, directory)
if directory.st_mode & S_IFDIR != S_IFDIR:
self.raise_os_error(errno.ENOTDIR, current_dir.path)
@@ -2302,10 +2464,15 @@ class FakeFilesystem:
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):
+ def create_file(self, file_path: AnyPath,
+ st_mode: int = S_IFREG | PERM_DEF_FILE,
+ contents: AnyString = '',
+ st_size: Optional[int] = None,
+ create_missing_dirs: bool = True,
+ apply_umask: bool = False,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ side_effect: Optional[Callable] = None) -> FakeFile:
"""Create `file_path`, including all the parent directories along
the way.
@@ -2339,7 +2506,9 @@ class FakeFilesystem:
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):
+ def add_real_file(self, source_path: AnyPath,
+ read_only: bool = True,
+ target_path: Optional[AnyPath] = None) -> FakeFile:
"""Create `file_path`, including all the parent directories along the
way, for an existing real file. The contents of the real file are read
only on demand.
@@ -2365,9 +2534,8 @@ class FakeFilesystem:
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)
+ source_path_str = make_string_path(source_path)
+ real_stat = os.stat(source_path_str)
fake_file = self.create_file_internally(target_path,
read_from_real_fs=True)
@@ -2375,12 +2543,13 @@ class FakeFilesystem:
fake_file.stat_result.set_from_stat_result(real_stat)
if read_only:
fake_file.st_mode &= 0o777444
- fake_file.file_path = source_path
+ fake_file.file_path = source_path_str
self.change_disk_usage(fake_file.size, fake_file.name,
fake_file.st_dev)
return fake_file
- def add_real_symlink(self, source_path, target_path=None):
+ def add_real_symlink(self, source_path: AnyPath,
+ target_path: Optional[AnyPath] = None) -> FakeFile:
"""Create a symlink at source_path (or target_path, if given). 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
@@ -2389,10 +2558,10 @@ class FakeFilesystem:
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`.
+ filesystem, otherwise, the same as `source_path`.
Returns:
- the newly created FakeDirectory object.
+ the newly created FakeFile object.
Raises:
OSError: if the directory does not exist in the real file system.
@@ -2400,19 +2569,25 @@ class FakeFilesystem:
(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)
+ source_path_str = make_string_path(source_path) # TODO: add test
+ source_path_str = self._path_without_trailing_separators(
+ source_path_str)
+ if (not os.path.exists(source_path_str) and
+ not os.path.islink(source_path_str)):
+ self.raise_os_error(errno.ENOENT, source_path_str)
- target = os.readlink(source_path)
+ target = os.readlink(source_path_str)
if target_path:
return self.create_symlink(target_path, target)
else:
- return self.create_symlink(source_path, target)
+ return self.create_symlink(source_path_str, target)
- def add_real_directory(self, source_path, read_only=True, lazy_read=True,
- target_path=None):
+ def add_real_directory(
+ self, source_path: AnyPath,
+ read_only: bool = True,
+ lazy_read: bool = True,
+ target_path: Optional[AnyPath] = None) -> FakeDirectory:
"""Create a fake directory corresponding to the real directory at the
specified path. Add entries in the fake directory corresponding to
the entries in the real directory. Symlinks are supported.
@@ -2440,10 +2615,13 @@ class FakeFilesystem:
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
+ source_path_str = make_string_path(source_path) # TODO: add test
+ source_path_str = self._path_without_trailing_separators(
+ source_path_str)
+ if not os.path.exists(source_path_str):
+ self.raise_os_error(errno.ENOENT, source_path_str)
+ target_path = target_path or source_path_str
+ new_dir: FakeDirectory
if lazy_read:
parent_path = os.path.split(target_path)[0]
if self.exists(parent_path):
@@ -2451,13 +2629,13 @@ class FakeFilesystem:
else:
parent_dir = self.create_dir(parent_path)
new_dir = FakeDirectoryFromRealDirectory(
- source_path, self, read_only, target_path)
+ source_path_str, 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 base, _, files in os.walk(source_path_str):
+ new_base = os.path.join(new_dir.path, # type: ignore[arg-type]
+ os.path.relpath(base, source_path_str))
for fileEntry in os.listdir(base):
abs_fileEntry = os.path.join(base, fileEntry)
@@ -2475,7 +2653,9 @@ class FakeFilesystem:
os.path.join(new_base, fileEntry))
return new_dir
- def add_real_paths(self, path_list, read_only=True, lazy_dir_read=True):
+ def add_real_paths(self, path_list: List[AnyStr],
+ read_only: bool = True,
+ lazy_dir_read: bool = True) -> None:
"""This convenience method adds multiple files and/or directories from
the real file system to the fake file system. See `add_real_file()` and
`add_real_directory()`.
@@ -2502,13 +2682,17 @@ class FakeFilesystem:
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):
+ def create_file_internally(
+ self, file_path: AnyPath,
+ st_mode: int = S_IFREG | PERM_DEF_FILE,
+ contents: AnyString = '',
+ st_size: Optional[int] = None,
+ create_missing_dirs: bool = True,
+ apply_umask: bool = False,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ read_from_real_fs: bool = False,
+ side_effect: Optional[Callable] = None) -> FakeFile:
"""Internal fake file creator that supports both normal fake files
and fake files based on real files.
@@ -2528,21 +2712,20 @@ class FakeFilesystem:
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)
+ path = self.make_string_path(file_path)
+ path = self.absnormpath(path)
if not is_int_type(st_mode):
raise TypeError(
'st_mode must be of int type - did you mean to set contents?')
- if self.exists(file_path, check_link=True):
- self.raise_os_error(errno.EEXIST, file_path)
- parent_directory, new_file = self.splitpath(file_path)
+ if self.exists(path, check_link=True):
+ self.raise_os_error(errno.EEXIST, path)
+ parent_directory, new_file = self.splitpath(path)
if not parent_directory:
- parent_directory = self.cwd
+ parent_directory = matching_string(path, self.cwd)
self._auto_mount_drive_if_needed(parent_directory)
if not self.exists(parent_directory):
if not create_missing_dirs:
@@ -2552,8 +2735,10 @@ class FakeFilesystem:
parent_directory = self._original_path(parent_directory)
if apply_umask:
st_mode &= ~self.umask
+ file_object: FakeFile
if read_from_real_fs:
- file_object = FakeFileFromRealFile(file_path, filesystem=self,
+ file_object = FakeFileFromRealFile(to_string(path),
+ filesystem=self,
side_effect=side_effect)
else:
file_object = FakeFile(new_file, st_mode, filesystem=self,
@@ -2570,15 +2755,16 @@ class FakeFilesystem:
if st_size is not None:
file_object.set_large_file_size(st_size)
else:
- file_object._set_initial_contents(contents)
+ file_object.set_initial_contents(contents) # type: ignore
except OSError:
- self.remove_object(file_path)
+ self.remove_object(path)
raise
return file_object
- # pylint: disable=unused-argument
- def create_symlink(self, file_path, link_target, create_missing_dirs=True):
+ def create_symlink(self, file_path: AnyPath,
+ link_target: AnyPath,
+ create_missing_dirs: bool = True) -> FakeFile:
"""Create the specified symlink, pointed at the specified link target.
Args:
@@ -2594,42 +2780,43 @@ class FakeFilesystem:
OSError: if the symlink could not be created
(see :py:meth:`create_file`).
"""
+ link_path = self.make_string_path(file_path)
+ link_target_path = self.make_string_path(link_target)
+ link_path = self.normcase(link_path)
# the link path cannot end with a path separator
- 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 self.ends_with_path_separator(link_path):
+ if self.exists(link_path):
+ self.raise_os_error(errno.EEXIST, link_path)
+ if self.exists(link_target_path):
if not self.is_windows_fs:
- self.raise_os_error(errno.ENOENT, file_path)
+ self.raise_os_error(errno.ENOENT, link_path)
else:
if self.is_windows_fs:
- self.raise_os_error(errno.EINVAL, link_target)
+ self.raise_os_error(errno.EINVAL, link_target_path)
if not self.exists(
- self._path_without_trailing_separators(file_path),
+ self._path_without_trailing_separators(link_path),
check_link=True):
- self.raise_os_error(errno.ENOENT, link_target)
+ self.raise_os_error(errno.ENOENT, link_target_path)
if self.is_macos:
# to avoid EEXIST exception, remove the link
# if it already exists
- if self.exists(file_path, check_link=True):
- self.remove_object(file_path)
+ if self.exists(link_path, check_link=True):
+ self.remove_object(link_path)
else:
- self.raise_os_error(errno.EEXIST, link_target)
+ self.raise_os_error(errno.EEXIST, link_target_path)
# resolve the link path only if it is not a link itself
- if not self.islink(file_path):
- file_path = self.resolve_path(file_path)
- link_target = make_string_path(link_target)
+ if not self.islink(link_path):
+ link_path = self.resolve_path(link_path)
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):
+ link_path, st_mode=S_IFLNK | PERM_DEF,
+ contents=link_target_path,
+ create_missing_dirs=create_missing_dirs)
+
+ def create_link(self, old_path: AnyPath,
+ new_path: AnyPath,
+ follow_symlinks: bool = True,
+ create_missing_dirs: bool = True) -> FakeFile:
"""Create a hard link at new_path, pointing at old_path.
Args:
@@ -2637,6 +2824,8 @@ class FakeFilesystem:
new_path: The destination path to create a new link at.
follow_symlinks: If False and old_path is a symlink, link the
symlink instead of the object it points to.
+ create_missing_dirs: If `True`, any missing parent directories of
+ file_path will be created
Returns:
The FakeFile object referred to by old_path.
@@ -2646,49 +2835,80 @@ class FakeFilesystem:
OSError: if old_path is a directory.
OSError: if the parent directory doesn't exist.
"""
- new_path_normalized = self.absnormpath(new_path)
+ old_path_str = make_string_path(old_path)
+ new_path_str = make_string_path(new_path)
+ new_path_normalized = self.absnormpath(new_path_str)
if self.exists(new_path_normalized, check_link=True):
- self.raise_os_error(errno.EEXIST, new_path)
+ self.raise_os_error(errno.EEXIST, new_path_str)
new_parent_directory, new_basename = self.splitpath(
new_path_normalized)
if not new_parent_directory:
- new_parent_directory = self.cwd
+ new_parent_directory = matching_string(new_path_str, self.cwd)
if not self.exists(new_parent_directory):
- self.raise_os_error(errno.ENOENT, new_parent_directory)
+ if create_missing_dirs:
+ self.create_dir(new_parent_directory)
+ else:
+ self.raise_os_error(errno.ENOENT, new_parent_directory)
- if self.ends_with_path_separator(old_path):
+ if self.ends_with_path_separator(old_path_str):
error = errno.EINVAL if self.is_windows_fs else errno.ENOTDIR
- self.raise_os_error(error, old_path)
+ self.raise_os_error(error, old_path_str)
if not self.is_windows_fs and self.ends_with_path_separator(new_path):
- self.raise_os_error(errno.ENOENT, old_path)
+ self.raise_os_error(errno.ENOENT, old_path_str)
# Retrieve the target file
try:
- old_file = self.resolve(old_path, follow_symlinks=follow_symlinks)
+ old_file = self.resolve(old_path_str,
+ follow_symlinks=follow_symlinks)
except OSError:
- self.raise_os_error(errno.ENOENT, old_path)
+ self.raise_os_error(errno.ENOENT, old_path_str)
if old_file.st_mode & S_IFDIR:
self.raise_os_error(
- errno.EACCES if self.is_windows_fs else errno.EPERM, old_path)
+ errno.EACCES if self.is_windows_fs
+ else errno.EPERM, old_path_str
+ )
# abuse the name field to control the filename of the
# newly created link
- old_file.name = new_basename
+ old_file.name = new_basename # type: ignore[assignment]
self.add_object(new_parent_directory, old_file)
return old_file
- def _is_circular_link(self, link_obj):
+ def link(self, old_path: AnyPath,
+ new_path: AnyPath,
+ follow_symlinks: bool = True) -> FakeFile:
+ """Create a hard link at new_path, pointing at old_path.
+
+ Args:
+ old_path: An existing link to the target file.
+ new_path: The destination path to create a new link at.
+ follow_symlinks: If False and old_path is a symlink, link the
+ symlink instead of the object it points to.
+
+ Returns:
+ The FakeFile object referred to by old_path.
+
+ Raises:
+ OSError: if something already exists at new_path.
+ OSError: if old_path is a directory.
+ OSError: if the parent directory doesn't exist.
+ """
+ return self.create_link(old_path, new_path, follow_symlinks,
+ create_missing_dirs=False)
+
+ def _is_circular_link(self, link_obj: FakeFile) -> bool:
try:
+ assert link_obj.contents
self.resolve_path(link_obj.contents)
except OSError as exc:
return exc.errno == errno.ELOOP
return False
- def readlink(self, path):
+ def readlink(self, path: AnyPath) -> str:
"""Read the target of a symlink.
Args:
@@ -2705,31 +2925,33 @@ class FakeFilesystem:
"""
if path is None:
raise TypeError
- link_obj = self.lresolve(path)
+ link_path = make_string_path(path)
+ link_obj = self.lresolve(link_path)
if S_IFMT(link_obj.st_mode) != S_IFLNK:
- self.raise_os_error(errno.EINVAL, path)
+ self.raise_os_error(errno.EINVAL, link_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.ends_with_path_separator(link_path):
+ if not self.is_windows_fs and self.exists(link_path):
+ self.raise_os_error(errno.EINVAL, link_path)
+ if not self.exists(link_obj.path): # type: ignore
if self.is_windows_fs:
error = errno.EINVAL
elif self._is_circular_link(link_obj):
if self.is_macos:
- return link_obj.path
+ return link_obj.path # type: ignore[return-value]
error = errno.ELOOP
else:
error = errno.ENOENT
self.raise_os_error(error, link_obj.path)
+ assert link_obj.contents
return link_obj.contents
- def makedir(self, dir_name, mode=PERM_DEF):
+ def makedir(self, dir_path: AnyPath, mode: int = PERM_DEF) -> None:
"""Create a leaf Fake directory.
Args:
- dir_name: (str) Name of directory to create.
+ dir_path: (str) Name of directory to create.
Relative paths are assumed to be relative to '/'.
mode: (int) Mode to create directory with. This argument defaults
to 0o777. The umask is applied to this mode.
@@ -2738,7 +2960,7 @@ class FakeFilesystem:
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)
+ dir_name = make_string_path(dir_path)
ends_with_sep = self.ends_with_path_separator(dir_name)
dir_name = self._path_without_trailing_separators(dir_name)
if not dir_name:
@@ -2749,8 +2971,7 @@ class FakeFilesystem:
parent_dir, _ = self.splitpath(dir_name)
if parent_dir:
base_dir = self.normpath(parent_dir)
- ellipsis = self._matching_string(
- parent_dir, self.path_separator + '..')
+ ellipsis = matching_string(parent_dir, self.path_separator + '..')
if parent_dir.endswith(ellipsis) and not self.is_windows_fs:
base_dir, dummy_dotdot, _ = parent_dir.partition(ellipsis)
if not self.exists(base_dir):
@@ -2770,14 +2991,17 @@ class FakeFilesystem:
head, tail = self.splitpath(dir_name)
self.add_object(
- head, FakeDirectory(tail, mode & ~self.umask, filesystem=self))
+ to_string(head),
+ FakeDirectory(to_string(tail), mode & ~self.umask,
+ filesystem=self))
- def _path_without_trailing_separators(self, path):
+ def _path_without_trailing_separators(self, path: AnyStr) -> AnyStr:
while self.ends_with_path_separator(path):
path = path[:-1]
return path
- def makedirs(self, dir_name, mode=PERM_DEF, exist_ok=False):
+ def makedirs(self, dir_name: AnyStr, mode: int = PERM_DEF,
+ exist_ok: bool = False) -> None:
"""Create a leaf Fake directory and create any non-existent
parent dirs.
@@ -2795,7 +3019,6 @@ class FakeFilesystem:
"""
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
@@ -2804,17 +3027,19 @@ class FakeFilesystem:
# to avoid EEXIST exception, remove the link
self.remove_object(dir_name)
- path_components = self._path_components(dir_name)
+ dir_name_str = to_string(dir_name)
+ path_components = self._path_components(dir_name_str)
- # Raise a permission denied error if thioe first existing directory
+ # Raise a permission denied error if the 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)):
+ if (not hasattr(current_dir, "entries") or
+ component not in current_dir.entries):
break
else:
- current_dir = current_dir.contents[component]
+ current_dir = cast(FakeDirectory,
+ current_dir.entries[component])
try:
self.create_dir(dir_name, mode & ~self.umask)
except OSError as e:
@@ -2827,7 +3052,9 @@ class FakeFilesystem:
e.errno = errno.ENOENT
self.raise_os_error(e.errno, e.filename)
- def _is_of_type(self, path, st_flag, follow_symlinks=True):
+ def _is_of_type(self, path: AnyPath, st_flag: int,
+ follow_symlinks: bool = True,
+ check_read_perm: bool = True) -> bool:
"""Helper function to implement isdir(), islink(), etc.
See the stat(2) man page for valid stat.S_I* flag values
@@ -2835,6 +3062,8 @@ class FakeFilesystem:
Args:
path: Path to file to stat and test
st_flag: The stat.S_I* flag checked for the file's st_mode
+ check_read_perm: If True (default) False is returned for
+ existing but unreadable file paths.
Returns:
(boolean) `True` if the st_flag is set in path's st_mode.
@@ -2842,20 +3071,21 @@ class FakeFilesystem:
Raises:
TypeError: if path is None
"""
- path = make_string_path(path)
if path is None:
raise TypeError
+ file_path = make_string_path(path)
try:
- obj = self.resolve(path, follow_symlinks)
+ obj = self.resolve(file_path, follow_symlinks,
+ check_read_perm=check_read_perm)
if obj:
self.raise_for_filepath_ending_with_separator(
- path, obj, macos_handling=not follow_symlinks)
+ file_path, obj, macos_handling=not follow_symlinks)
return S_IFMT(obj.st_mode) == st_flag
except OSError:
return False
return False
- def isdir(self, path, follow_symlinks=True):
+ def isdir(self, path: AnyPath, follow_symlinks: bool = True) -> bool:
"""Determine if path identifies a directory.
Args:
@@ -2869,7 +3099,7 @@ class FakeFilesystem:
"""
return self._is_of_type(path, S_IFDIR, follow_symlinks)
- def isfile(self, path, follow_symlinks=True):
+ def isfile(self, path: AnyPath, follow_symlinks: bool = True) -> bool:
"""Determine if path identifies a regular file.
Args:
@@ -2881,9 +3111,10 @@ class FakeFilesystem:
Raises:
TypeError: if path is None.
"""
- return self._is_of_type(path, S_IFREG, follow_symlinks)
+ return self._is_of_type(path, S_IFREG, follow_symlinks,
+ check_read_perm=False)
- def islink(self, path):
+ def islink(self, path: AnyPath) -> bool:
"""Determine if path identifies a symbolic link.
Args:
@@ -2897,7 +3128,7 @@ class FakeFilesystem:
"""
return self._is_of_type(path, S_IFLNK, follow_symlinks=False)
- def confirmdir(self, target_directory):
+ def confirmdir(self, target_directory: AnyStr) -> FakeDirectory:
"""Test that the target is actually a directory, raising OSError
if not.
@@ -2911,12 +3142,12 @@ class FakeFilesystem:
Raises:
OSError: if the target is not a directory.
"""
- directory = self.resolve(target_directory)
+ directory = cast(FakeDirectory, 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):
+ def remove(self, path: AnyStr) -> None:
"""Remove the FakeFile object at the specified file path.
Args:
@@ -2927,7 +3158,8 @@ class FakeFilesystem:
OSError: if path does not exist.
OSError: if removal failed.
"""
- norm_path = self.absnormpath(path)
+ norm_path = make_string_path(path)
+ norm_path = self.absnormpath(norm_path)
if self.ends_with_path_separator(path):
self._handle_broken_link_with_trailing_sep(norm_path)
if self.exists(norm_path):
@@ -2943,8 +3175,7 @@ class FakeFilesystem:
error = errno.EISDIR
self.raise_os_error(error, norm_path)
- norm_path = make_string_path(norm_path)
- if path.endswith(self.path_separator):
+ if path.endswith(matching_string(path, self.path_separator)):
if self.is_windows_fs:
error = errno.EACCES
elif self.is_macos:
@@ -2957,7 +3188,8 @@ class FakeFilesystem:
self.remove_object(norm_path)
- def rmdir(self, target_directory, allow_symlink=False):
+ def rmdir(self, target_directory: AnyStr,
+ allow_symlink: bool = False) -> None:
"""Remove a leaf Fake directory.
Args:
@@ -2971,7 +3203,7 @@ class FakeFilesystem:
OSError: if removal failed per FakeFilesystem.RemoveObject.
Cannot remove '.'.
"""
- if target_directory in (b'.', u'.'):
+ if target_directory == matching_string(target_directory, '.'):
error_nr = errno.EACCES if self.is_windows_fs else errno.EINVAL
self.raise_os_error(error_nr, target_directory)
ends_with_sep = self.ends_with_path_separator(target_directory)
@@ -2984,11 +3216,11 @@ class FakeFilesystem:
self.raise_os_error(errno.ENOTDIR, target_directory)
dir_object = self.resolve(target_directory)
- if dir_object.contents:
+ if dir_object.entries:
self.raise_os_error(errno.ENOTEMPTY, target_directory)
self.remove_object(target_directory)
- def listdir(self, target_directory):
+ def listdir(self, target_directory: AnyStr) -> List[AnyStr]:
"""Return a list of file names in target_directory.
Args:
@@ -2997,20 +3229,23 @@ class FakeFilesystem:
Returns:
A list of file names within the target directory in arbitrary
- order.
+ order. If `shuffle_listdir_results` is set, the order is not the
+ same in subsequent calls to avoid tests relying on any ordering.
Raises:
OSError: if the target is not a directory.
"""
target_directory = self.resolve_path(target_directory, allow_fd=True)
directory = self.confirmdir(target_directory)
- directory_contents = directory.contents
- return list(directory_contents.keys())
+ directory_contents = list(directory.entries.keys())
+ if self.shuffle_listdir_results:
+ random.shuffle(directory_contents)
+ return directory_contents # type: ignore[return-value]
- def __str__(self):
+ def __str__(self) -> str:
return str(self.root)
- def _add_standard_streams(self):
+ def _add_standard_streams(self) -> None:
self._add_open_file(StandardStreamWrapper(sys.stdin))
self._add_open_file(StandardStreamWrapper(sys.stdout))
self._add_open_file(StandardStreamWrapper(sys.stderr))
@@ -3073,10 +3308,16 @@ class FakePathModule:
FakePathModule should *only* be instantiated by FakeOsModule. See the
FakeOsModule docstring for details.
"""
- _OS_PATH_COPY = _copy_module(os.path)
+ _OS_PATH_COPY: Any = _copy_module(os.path)
+
+ devnull: ClassVar[str] = ''
+ sep: ClassVar[str] = ''
+ altsep: ClassVar[Optional[str]] = None
+ linesep: ClassVar[str] = ''
+ pathsep: ClassVar[str] = ''
@staticmethod
- def dir():
+ def dir() -> List[str]:
"""Return the list of patched function names. Used for patching
functions imported from the module.
"""
@@ -3087,7 +3328,7 @@ class FakePathModule:
'realpath', 'relpath', 'split', 'splitdrive', 'samefile'
]
- def __init__(self, filesystem, os_module):
+ def __init__(self, filesystem: FakeFilesystem, os_module: 'FakeOsModule'):
"""Init.
Args:
@@ -3095,11 +3336,18 @@ class FakePathModule:
"""
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):
+ self._os_path.os = self.os = os_module # type: ignore[attr-defined]
+ self.reset(filesystem)
+
+ @classmethod
+ def reset(cls, filesystem: FakeFilesystem) -> None:
+ cls.sep = filesystem.path_separator
+ cls.altsep = filesystem.alternative_path_separator
+ cls.linesep = filesystem.line_separator()
+ cls.devnull = 'nul' if filesystem.is_windows_fs else '/dev/null'
+ cls.pathsep = ';' if filesystem.is_windows_fs else ':'
+
+ def exists(self, path: AnyStr) -> bool:
"""Determine whether the file object exists within the fake filesystem.
Args:
@@ -3110,7 +3358,7 @@ class FakePathModule:
"""
return self.filesystem.exists(path)
- def lexists(self, path):
+ def lexists(self, path: AnyStr) -> bool:
"""Test whether a path exists. Returns True for broken symbolic links.
Args:
@@ -3121,7 +3369,7 @@ class FakePathModule:
"""
return self.filesystem.exists(path, check_link=True)
- def getsize(self, path):
+ def getsize(self, path: AnyStr):
"""Return the file object size in bytes.
Args:
@@ -3138,28 +3386,22 @@ class FakePathModule:
self.filesystem.raise_os_error(error_nr, path)
return file_obj.st_size
- def isabs(self, path):
+ def isabs(self, path: AnyStr) -> bool:
"""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))
+ return self.filesystem._starts_with_sep(path)
- def isdir(self, path):
+ def isdir(self, path: AnyStr) -> bool:
"""Determine if path identifies a directory."""
return self.filesystem.isdir(path)
- def isfile(self, path):
+ def isfile(self, path: AnyStr) -> bool:
"""Determine if path identifies a regular file."""
return self.filesystem.isfile(path)
- def islink(self, path):
+ def islink(self, path: AnyStr) -> bool:
"""Determine if path identifies a symbolic link.
Args:
@@ -3173,7 +3415,7 @@ class FakePathModule:
"""
return self.filesystem.islink(path)
- def getmtime(self, path):
+ def getmtime(self, path: AnyStr) -> float:
"""Returns the modification time of the fake file.
Args:
@@ -3192,7 +3434,7 @@ class FakePathModule:
except OSError:
self.filesystem.raise_os_error(errno.ENOENT, winerror=3)
- def getatime(self, path):
+ def getatime(self, path: AnyStr) -> float:
"""Returns the last access time of the fake file.
Note: Access time is not set automatically in fake filesystem
@@ -3214,7 +3456,7 @@ class FakePathModule:
self.filesystem.raise_os_error(errno.ENOENT)
return file_obj.st_atime
- def getctime(self, path):
+ def getctime(self, path: AnyStr) -> float:
"""Returns the creation time of the fake file.
Args:
@@ -3233,7 +3475,7 @@ class FakePathModule:
self.filesystem.raise_os_error(errno.ENOENT)
return file_obj.st_ctime
- def abspath(self, path):
+ def abspath(self, path: AnyStr) -> AnyStr:
"""Return the absolute version of a path."""
def getcwd():
@@ -3245,37 +3487,34 @@ class FakePathModule:
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)):
+ self.filesystem._starts_with_sep(path)):
cwd = getcwd()
if self.filesystem._starts_with_drive_letter(cwd):
path = self.join(cwd[:2], path)
return self.normpath(path)
- def join(self, *p):
+ def join(self, *p: AnyStr) -> AnyStr:
"""Return the completed path with a separator of the parts."""
return self.filesystem.joinpaths(*p)
- def split(self, path):
+ def split(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]:
"""Split the path into the directory and the filename of the path.
"""
return self.filesystem.splitpath(path)
- def splitdrive(self, path):
+ def splitdrive(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]:
"""Split the path into the drive part and the rest of the path, if
supported."""
return self.filesystem.splitdrive(path)
- def normpath(self, path):
+ def normpath(self, path: AnyStr) -> AnyStr:
"""Normalize path, eliminating double slashes, etc."""
return self.filesystem.normpath(path)
- def normcase(self, path):
+ def normcase(self, path: AnyStr) -> AnyStr:
"""Convert to lower case under windows, replaces additional path
separator."""
path = self.filesystem.normcase(path)
@@ -3283,7 +3522,7 @@ class FakePathModule:
path = path.lower()
return path
- def relpath(self, path, start=None):
+ def relpath(self, path: AnyStr, start: Optional[AnyStr] = None) -> AnyStr:
"""We mostly rely on the native implementation and adapt the
path separator."""
if not path:
@@ -3292,29 +3531,37 @@ class FakePathModule:
if start is not None:
start = make_string_path(start)
else:
- start = self.filesystem.cwd
+ start = matching_string(path, self.filesystem.cwd)
+ system_sep = matching_string(path, self._os_path.sep)
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)
+ altsep = matching_string(
+ path, self.filesystem.alternative_path_separator)
+ path = path.replace(altsep, system_sep)
+ start = start.replace(altsep, system_sep)
+ sep = matching_string(path, self.filesystem.path_separator)
+ path = path.replace(sep, system_sep)
+ start = start.replace(sep, system_sep)
path = self._os_path.relpath(path, start)
- return path.replace(self._os_path.sep, self.filesystem.path_separator)
+ return path.replace(system_sep, sep)
- def realpath(self, filename):
+ def realpath(self, filename: AnyStr, strict: bool = None) -> AnyStr:
"""Return the canonical path of the specified filename, eliminating any
symbolic links encountered in the path.
"""
+ if strict is not None and sys.version_info < (3, 10):
+ raise TypeError("realpath() got an unexpected "
+ "keyword argument 'strict'")
+ if strict:
+ # raises in strict mode if the file does not exist
+ self.filesystem.resolve(filename)
if self.filesystem.is_windows_fs:
return self.abspath(filename)
filename = make_string_path(filename)
- path, ok = self._joinrealpath(filename[:0], filename, {})
- return self.abspath(path)
+ path, ok = self._join_real_path(filename[:0], filename, {})
+ path = self.abspath(path)
+ return path
- def samefile(self, path1, path2):
+ def samefile(self, path1: AnyStr, path2: AnyStr) -> bool:
"""Return whether path1 and path2 point to the same file.
Args:
@@ -3330,15 +3577,30 @@ class FakePathModule:
return (stat1.st_ino == stat2.st_ino and
stat1.st_dev == stat2.st_dev)
- def _joinrealpath(self, path, rest, seen):
+ @overload
+ def _join_real_path(
+ self, path: str,
+ rest: str,
+ seen: Dict[str, Optional[str]]) -> Tuple[str, bool]: ...
+
+ @overload
+ def _join_real_path(
+ self, path: bytes,
+ rest: bytes,
+ seen: Dict[bytes, Optional[bytes]]) -> Tuple[bytes, bool]: ...
+
+ def _join_real_path(
+ self, path: AnyStr,
+ rest: AnyStr,
+ seen: Dict[AnyStr, Optional[AnyStr]]) -> Tuple[AnyStr, bool]:
"""Join two paths, normalizing and eliminating any symbolic links
encountered in the second path.
Taken from Python source and adapted.
"""
- curdir = self.filesystem._matching_string(path, '.')
- pardir = self.filesystem._matching_string(path, '..')
+ curdir = matching_string(path, '.')
+ pardir = matching_string(path, '..')
- sep = self.filesystem._path_separator(path)
+ sep = self.filesystem.get_path_separator(path)
if self.isabs(rest):
rest = rest[1:]
path = sep
@@ -3364,33 +3626,37 @@ class FakePathModule:
# Resolve the symbolic link
if newpath in seen:
# Already seen this path
- path = seen[newpath]
- if path is not None:
+ seen_path = seen[newpath]
+ if seen_path is not None:
# use cached value
+ path = seen_path
continue
# The symlink is not resolved, so we must have a symlink loop.
# Return already resolved part + rest of the path unchanged.
return self.filesystem.joinpaths(newpath, rest), False
seen[newpath] = None # not resolved symlink
- path, ok = self._joinrealpath(
- path, self.filesystem.readlink(newpath), seen)
+ path, ok = self._join_real_path(
+ path, matching_string(path, self.filesystem.readlink(
+ newpath)), seen)
if not ok:
return self.filesystem.joinpaths(path, rest), False
seen[newpath] = path # resolved symlink
return path, True
- def dirname(self, path):
+ def dirname(self, path: AnyStr) -> AnyStr:
"""Returns the first part of the result of `split()`."""
return self.split(path)[0]
- def expanduser(self, path):
+ def expanduser(self, path: AnyStr) -> AnyStr:
"""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)
+ path = self._os_path.expanduser(path)
+ return path.replace(
+ matching_string(path, self._os_path.sep),
+ matching_string(path, self.sep))
- def ismount(self, path):
+ def ismount(self, path: AnyStr) -> bool:
"""Return true if the given path is a mount point.
Args:
@@ -3401,15 +3667,16 @@ class FakePathModule:
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)
+ path_str = to_string(make_string_path(path))
+ normed_path = self.filesystem.absnormpath(path_str)
+ sep = self.filesystem.path_separator
if self.filesystem.is_windows_fs:
+ path_seps: Union[Tuple[str, Optional[str]], Tuple[str]]
if self.filesystem.alternative_path_separator is not None:
path_seps = (
- sep, self.filesystem._alternative_path_separator(path)
+ sep, self.filesystem.alternative_path_separator
)
else:
path_seps = (sep,)
@@ -3419,11 +3686,12 @@ class FakePathModule:
if rest in path_seps:
return True
for mount_point in self.filesystem.mount_points:
- if normed_path.rstrip(sep) == mount_point.rstrip(sep):
+ if (to_string(normed_path).rstrip(sep) ==
+ to_string(mount_point).rstrip(sep)):
return True
return False
- def __getattr__(self, name):
+ def __getattr__(self, name: str) -> Any:
"""Forwards any non-faked calls to the real os.path."""
return getattr(self._os_path, name)
@@ -3441,14 +3709,12 @@ class FakeOsModule:
my_os_module = fake_filesystem.FakeOsModule(filesystem)
"""
- devnull = None
-
@staticmethod
- def dir():
+ def dir() -> List[str]:
"""Return the list of patched function names. Used for patching
functions imported from the module.
"""
- dir = [
+ _dir = [
'access', 'chdir', 'chmod', 'chown', 'close', 'fstat', 'fsync',
'getcwd', 'lchmod', 'link', 'listdir', 'lstat', 'makedirs',
'mkdir', 'mknod', 'open', 'read', 'readlink', 'remove',
@@ -3456,30 +3722,45 @@ class FakeOsModule:
'unlink', 'utime', 'walk', 'write', 'getcwdb', 'replace'
]
if sys.platform.startswith('linux'):
- dir += [
+ _dir += [
'fdatasync', 'getxattr', 'listxattr',
'removexattr', 'setxattr'
]
if use_scandir:
- dir += ['scandir']
- return dir
+ _dir += ['scandir']
+ return _dir
- def __init__(self, filesystem):
+ def __init__(self, filesystem: FakeFilesystem):
"""Also exposes self.path (to fake os.path).
Args:
filesystem: FakeFilesystem used to provide file system information
"""
self.filesystem = filesystem
- self.sep = filesystem.path_separator
- self.altsep = filesystem.alternative_path_separator
- self.linesep = filesystem.line_separator()
- self._os_module = os
+ self._os_module: Any = 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):
+ @property
+ def devnull(self) -> str:
+ return self.path.devnull
+
+ @property
+ def sep(self) -> str:
+ return self.path.sep
+
+ @property
+ def altsep(self) -> Optional[str]:
+ return self.path.altsep
+
+ @property
+ def linesep(self) -> str:
+ return self.path.linesep
+
+ @property
+ def pathsep(self) -> str:
+ return self.path.pathsep
+
+ def fdopen(self, fd: int, *args: Any, **kwargs: Any) -> AnyFileWrapper:
"""Redirector to open() builtin function.
Args:
@@ -3497,7 +3778,7 @@ class FakeOsModule:
raise TypeError('an integer is required')
return FakeFileOpen(self.filesystem)(fd, *args, **kwargs)
- def _umask(self):
+ def _umask(self) -> int:
"""Return the current umask."""
if self.filesystem.is_windows_fs:
# windows always returns 0 - it has no real notion of umask
@@ -3513,7 +3794,8 @@ class FakeOsModule:
os.umask(mask)
return mask
- def open(self, path, flags, mode=None, *, dir_fd=None):
+ def open(self, path: AnyStr, flags: int, mode: Optional[int] = None, *,
+ dir_fd: Optional[int] = None) -> int:
"""Return the file descriptor for a FakeFile.
Args:
@@ -3558,7 +3840,7 @@ class FakeOsModule:
# 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()))
+ path, matching_string(path, str(uuid.uuid4())))
if (not self.filesystem.is_windows_fs and
self.filesystem.exists(path)):
@@ -3583,11 +3865,12 @@ class FakeOsModule:
fake_file = FakeFileOpen(
self.filesystem, delete_on_close=delete_on_close, raw_io=True)(
path, str_flags, open_modes=open_modes)
+ assert not isinstance(fake_file, StandardStreamWrapper)
if fake_file.file_object != self.filesystem.dev_null:
self.chmod(path, mode)
return fake_file.fileno()
- def close(self, fd):
+ def close(self, fd: int) -> None:
"""Close a file descriptor.
Args:
@@ -3600,7 +3883,7 @@ class FakeOsModule:
file_handle = self.filesystem.get_open_file(fd)
file_handle.close()
- def read(self, fd, n):
+ def read(self, fd: int, n: int) -> bytes:
"""Read number of bytes from a file descriptor, returns bytes read.
Args:
@@ -3615,10 +3898,13 @@ class FakeOsModule:
TypeError: if file descriptor is not an integer.
"""
file_handle = self.filesystem.get_open_file(fd)
- file_handle.raw_io = True
+ if isinstance(file_handle, FakeFileWrapper):
+ file_handle.raw_io = True
+ if isinstance(file_handle, FakeDirWrapper):
+ self.filesystem.raise_os_error(errno.EBADF, file_handle.file_path)
return file_handle.read(n)
- def write(self, fd, contents):
+ def write(self, fd: int, contents: bytes) -> int:
"""Write string to file descriptor, returns number of bytes written.
Args:
@@ -3632,7 +3918,7 @@ class FakeOsModule:
OSError: bad file descriptor.
TypeError: if file descriptor is not an integer.
"""
- file_handle = self.filesystem.get_open_file(fd)
+ file_handle = cast(FakeFileWrapper, self.filesystem.get_open_file(fd))
if isinstance(file_handle, FakeDirWrapper):
self.filesystem.raise_os_error(errno.EBADF, file_handle.file_path)
@@ -3646,18 +3932,18 @@ class FakeOsModule:
file_handle.flush()
return len(contents)
- def pipe(self):
+ def pipe(self) -> Tuple[int, int]:
read_fd, write_fd = os.pipe()
- read_wrapper = FakePipeWrapper(self.filesystem, read_fd)
+ read_wrapper = FakePipeWrapper(self.filesystem, read_fd, False)
file_des = self.filesystem._add_open_file(read_wrapper)
read_wrapper.filedes = file_des
- write_wrapper = FakePipeWrapper(self.filesystem, write_fd)
+ write_wrapper = FakePipeWrapper(self.filesystem, write_fd, True)
file_des = self.filesystem._add_open_file(write_wrapper)
write_wrapper.filedes = file_des
return read_wrapper.filedes, write_wrapper.filedes
@staticmethod
- def stat_float_times(newvalue=None):
+ def stat_float_times(newvalue: Optional[bool] = None) -> bool:
"""Determine whether a file's time stamps are reported as floats
or ints.
@@ -3670,7 +3956,7 @@ class FakeOsModule:
"""
return FakeStatResult.stat_float_times(newvalue)
- def fstat(self, fd):
+ def fstat(self, fd: int) -> FakeStatResult:
"""Return the os.stat-like tuple for the FakeFile object of file_des.
Args:
@@ -3684,9 +3970,10 @@ class FakeOsModule:
"""
# stat should return the tuple representing return value of os.stat
file_object = self.filesystem.get_open_file(fd).get_object()
+ assert isinstance(file_object, FakeFile)
return file_object.stat_result.copy()
- def umask(self, mask):
+ def umask(self, mask: int) -> int:
"""Change the current umask.
Args:
@@ -3704,7 +3991,7 @@ class FakeOsModule:
self.filesystem.umask = mask
return old_umask
- def chdir(self, path):
+ def chdir(self, path: AnyStr) -> None:
"""Change current working directory to target directory.
Args:
@@ -3714,26 +4001,30 @@ class FakeOsModule:
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)
+ try:
+ path = self.filesystem.resolve_path(
+ path, allow_fd=True)
+ except OSError as exc:
+ if self.filesystem.is_macos and exc.errno == errno.EBADF:
+ raise OSError(errno.ENOTDIR, "Not a directory: " + str(path))
+ raise
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
+ self.filesystem.raise_os_error(errno.EACCES, directory.name)
+ self.filesystem.cwd = path # type: ignore[assignment]
- def getcwd(self):
+ def getcwd(self) -> str:
"""Return current working directory."""
- return self.filesystem.cwd
+ return to_string(self.filesystem.cwd)
- def getcwdb(self):
+ def getcwdb(self) -> bytes:
"""Return current working directory as bytes."""
- return bytes(
- self.filesystem.cwd, locale.getpreferredencoding(False))
+ return to_bytes(self.filesystem.cwd)
- def listdir(self, path):
+ def listdir(self, path: AnyStr) -> List[AnyStr]:
"""Return a list of file names in target_directory.
Args:
@@ -3752,7 +4043,8 @@ class FakeOsModule:
XATTR_CREATE = 1
XATTR_REPLACE = 2
- def getxattr(self, path, attribute, *, follow_symlinks=True):
+ def getxattr(self, path: AnyStr, attribute: AnyString, *,
+ follow_symlinks: bool = True) -> Optional[bytes]:
"""Return the value of the given extended filesystem attribute for
`path`.
@@ -3780,7 +4072,8 @@ class FakeOsModule:
allow_fd=True)
return file_obj.xattr.get(attribute)
- def listxattr(self, path=None, *, follow_symlinks=True):
+ def listxattr(self, path: Optional[AnyStr] = None, *,
+ follow_symlinks: bool = True) -> List[str]:
"""Return a list of the extended filesystem attributes on `path`.
Args:
@@ -3799,13 +4092,13 @@ class FakeOsModule:
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)
+ path_str = self.filesystem.cwd if path is None else path
+ file_obj = self.filesystem.resolve(
+ cast(AnyStr, path_str), follow_symlinks, allow_fd=True)
return list(file_obj.xattr.keys())
- def removexattr(self, path, attribute, *, follow_symlinks=True):
+ def removexattr(self, path: AnyStr, attribute: AnyString, *,
+ follow_symlinks: bool = True) -> None:
"""Removes the extended filesystem attribute attribute from `path`.
Args:
@@ -3829,8 +4122,8 @@ class FakeOsModule:
if attribute in file_obj.xattr:
del file_obj.xattr[attribute]
- def setxattr(self, path, attribute, value,
- flags=0, *, follow_symlinks=True):
+ def setxattr(self, path: AnyStr, attribute: AnyString, value: bytes,
+ flags: int = 0, *, follow_symlinks: bool = True) -> None:
"""Sets the value of the given extended filesystem attribute for
`path`.
@@ -3863,24 +4156,25 @@ class FakeOsModule:
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.
+ def scandir(self, path: str = '.') -> ScanDirIter:
+ """Return an iterator of DirEntry objects corresponding to the
+ entries in the directory given by path.
- Args:
- path: Path to the target directory within the fake filesystem.
+ 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.
+ 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)
+ Raises:
+ OSError: if the target is not a directory.
+ """
+ return scandir(self.filesystem, path)
- def walk(self, top, topdown=True, onerror=None, followlinks=False):
+ def walk(self, top: AnyStr, topdown: bool = True,
+ onerror: Optional[bool] = None,
+ followlinks: bool = False):
"""Perform an os.walk operation over the fake filesystem.
Args:
@@ -3899,7 +4193,7 @@ class FakeOsModule:
"""
return walk(self.filesystem, top, topdown, onerror, followlinks)
- def readlink(self, path, dir_fd=None):
+ def readlink(self, path: AnyStr, dir_fd: Optional[int] = None) -> str:
"""Read the target of a symlink.
Args:
@@ -3918,7 +4212,8 @@ class FakeOsModule:
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):
+ def stat(self, path: AnyStr, *, dir_fd: Optional[int] = None,
+ follow_symlinks: bool = True) -> FakeStatResult:
"""Return the os.stat-like tuple for the FakeFile object of entry_path.
Args:
@@ -3938,7 +4233,8 @@ class FakeOsModule:
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):
+ def lstat(self, path: AnyStr, *,
+ dir_fd: Optional[int] = None) -> FakeStatResult:
"""Return the os.stat-like tuple for entry_path, not following symlinks.
Args:
@@ -3956,7 +4252,7 @@ class FakeOsModule:
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):
+ def remove(self, path: AnyStr, dir_fd: Optional[int] = None) -> None:
"""Remove the FakeFile object at the specified file path.
Args:
@@ -3972,7 +4268,7 @@ class FakeOsModule:
path = self._path_with_dir_fd(path, self.remove, dir_fd)
self.filesystem.remove(path)
- def unlink(self, path, *, dir_fd=None):
+ def unlink(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> None:
"""Remove the FakeFile object at the specified file path.
Args:
@@ -3988,7 +4284,9 @@ class FakeOsModule:
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):
+ def rename(self, src: AnyStr, dst: AnyStr, *,
+ src_dir_fd: Optional[int] = None,
+ dst_dir_fd: Optional[int] = None) -> None:
"""Rename a FakeFile object at old_file_path to new_file_path,
preserving all properties.
Also replaces existing new_file_path object, if one existed
@@ -4017,7 +4315,9 @@ class FakeOsModule:
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):
+ def replace(self, src: AnyStr, dst: AnyStr, *,
+ src_dir_fd: Optional[int] = None,
+ dst_dir_fd: Optional[int] = None) -> None:
"""Renames a FakeFile object at old_file_path to new_file_path,
preserving all properties.
Also replaces existing new_file_path object, if one existed.
@@ -4044,7 +4344,7 @@ class FakeOsModule:
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):
+ def rmdir(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> None:
"""Remove a leaf Fake directory.
Args:
@@ -4059,7 +4359,7 @@ class FakeOsModule:
path = self._path_with_dir_fd(path, self.rmdir, dir_fd)
self.filesystem.rmdir(path)
- def removedirs(self, name):
+ def removedirs(self, name: AnyStr) -> None:
"""Remove a leaf fake directory and all empty intermediate ones.
Args:
@@ -4071,7 +4371,7 @@ class FakeOsModule:
"""
name = self.filesystem.absnormpath(name)
directory = self.filesystem.confirmdir(name)
- if directory.contents:
+ if directory.entries:
self.filesystem.raise_os_error(
errno.ENOTEMPTY, self.path.basename(name))
else:
@@ -4081,13 +4381,14 @@ class FakeOsModule:
head, tail = self.path.split(head)
while head and tail:
head_dir = self.filesystem.confirmdir(head)
- if head_dir.contents:
+ if head_dir.entries:
break
# only the top-level dir may not be a symlink
self.filesystem.rmdir(head, allow_symlink=True)
head, tail = self.path.split(head)
- def mkdir(self, path, mode=PERM_DEF, *, dir_fd=None):
+ def mkdir(self, path: AnyStr, mode: int = PERM_DEF, *,
+ dir_fd: Optional[int] = None) -> None:
"""Create a leaf Fake directory.
Args:
@@ -4110,7 +4411,8 @@ class FakeOsModule:
self.filesystem.raise_os_error(e.errno, path)
raise
- def makedirs(self, name, mode=PERM_DEF, exist_ok=None):
+ def makedirs(self, name: AnyStr, mode: int = PERM_DEF,
+ exist_ok: bool = None) -> None:
"""Create a leaf Fake directory + create any non-existent parent dirs.
Args:
@@ -4129,9 +4431,14 @@ class FakeOsModule:
exist_ok = False
self.filesystem.makedirs(name, mode, exist_ok)
- def _path_with_dir_fd(self, path, fct, dir_fd):
+ def _path_with_dir_fd(self, path: AnyStr, fct: Callable,
+ dir_fd: Optional[int]) -> AnyStr:
"""Return the path considering dir_fd. Raise on invalid parameters."""
- path = to_string(path)
+ try:
+ path = make_string_path(path)
+ except TypeError:
+ # the error is handled later
+ path = path
if dir_fd is not None:
# check if fd is supported for the built-in real function
real_fct = getattr(os, fct.__name__)
@@ -4140,14 +4447,52 @@ class FakeOsModule:
'dir_fd unavailable on this platform')
if isinstance(path, int):
raise ValueError("%s: Can't specify dir_fd without "
- "matching path" % fct.__name__)
+ "matching path_str" % fct.__name__)
if not self.path.isabs(path):
- return self.path.join(
- self.filesystem.get_open_file(
- dir_fd).get_object().path, path)
+ open_file = self.filesystem.get_open_file(dir_fd)
+ return self.path.join( # type: ignore[type-var, return-value]
+ cast(FakeFile, open_file.get_object()).path, path)
return path
- def access(self, path, mode, *, dir_fd=None, follow_symlinks=True):
+ def truncate(self, path: AnyStr, length: int) -> None:
+ """Truncate the file corresponding to path, so that it is
+ length bytes in size. If length is larger than the current size,
+ the file is filled up with zero bytes.
+
+ Args:
+ path: (str or int) Path to the file, or an integer file
+ descriptor for the file object.
+ length: (int) Length of the file after truncating it.
+
+ Raises:
+ OSError: if the file does not exist or the file descriptor is
+ invalid.
+ """
+ file_object = self.filesystem.resolve(path, allow_fd=True)
+ file_object.size = length
+
+ def ftruncate(self, fd: int, length: int) -> None:
+ """Truncate the file corresponding to fd, so that it is
+ length bytes in size. If length is larger than the current size,
+ the file is filled up with zero bytes.
+
+ Args:
+ fd: (int) File descriptor for the file object.
+ length: (int) Maximum length of the file after truncating it.
+
+ Raises:
+ OSError: if the file descriptor is invalid
+ """
+ file_object = self.filesystem.get_open_file(fd).get_object()
+ if isinstance(file_object, FakeFileWrapper):
+ file_object.size = length
+ else:
+ raise OSError(errno.EBADF, 'Invalid file descriptor')
+
+ def access(self, path: AnyStr, mode: int, *,
+ dir_fd: Optional[int] = None,
+ effective_ids: bool = False,
+ follow_symlinks: bool = True) -> bool:
"""Check if a file exists and has the specified permissions.
Args:
@@ -4156,12 +4501,16 @@ class FakeOsModule:
os.F_OK, os.R_OK, os.W_OK, and os.X_OK.
dir_fd: If not `None`, the file descriptor of a directory, with
`path` being relative to this directory.
+ effective_ids: (bool) Unused. Only here to match the signature.
follow_symlinks: (bool) If `False` and `path` points to a symlink,
the link itself is queried instead of the linked object.
Returns:
bool, `True` if file is accessible, `False` otherwise.
"""
+ if effective_ids and self.filesystem.is_windows_fs:
+ raise NotImplementedError(
+ 'access: effective_ids unavailable on this platform')
path = self._path_with_dir_fd(path, self.access, dir_fd)
try:
stat_result = self.stat(path, follow_symlinks=follow_symlinks)
@@ -4173,7 +4522,9 @@ class FakeOsModule:
mode &= ~os.W_OK
return (mode & ((stat_result.st_mode >> 6) & 7)) == mode
- def chmod(self, path, mode, *, dir_fd=None, follow_symlinks=True):
+ def chmod(self, path: AnyStr, mode: int, *,
+ dir_fd: Optional[int] = None,
+ follow_symlinks: bool = True) -> None:
"""Change the permissions of a file as encoded in integer mode.
Args:
@@ -4184,10 +4535,15 @@ class FakeOsModule:
follow_symlinks: (bool) If `False` and `path` points to a symlink,
the link itself is queried instead of the linked object.
"""
+ if (not follow_symlinks and
+ (os.chmod not in os.supports_follow_symlinks or IS_PYPY)):
+ raise NotImplementedError(
+ "`follow_symlinks` for chmod() is not available "
+ "on this system")
path = self._path_with_dir_fd(path, self.chmod, dir_fd)
self.filesystem.chmod(path, mode, follow_symlinks)
- def lchmod(self, path, mode):
+ def lchmod(self, path: AnyStr, mode: int) -> None:
"""Change the permissions of a file as encoded in integer mode.
If the file is a link, the permissions of the link are changed.
@@ -4196,11 +4552,14 @@ class FakeOsModule:
mode: (int) Permissions.
"""
if self.filesystem.is_windows_fs:
- raise (NameError, "name 'lchmod' is not defined")
+ 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):
+ def utime(self, path: AnyStr,
+ times: Optional[Tuple[Union[int, float], Union[int, float]]] =
+ None, ns: Optional[Tuple[int, int]] = None,
+ dir_fd: Optional[int] = None,
+ follow_symlinks: bool = True) -> None:
"""Change the access and modified times of a file.
Args:
@@ -4226,7 +4585,9 @@ class FakeOsModule:
self.filesystem.utime(
path, times=times, ns=ns, follow_symlinks=follow_symlinks)
- def chown(self, path, uid, gid, *, dir_fd=None, follow_symlinks=True):
+ def chown(self, path: AnyStr, uid: int, gid: int, *,
+ dir_fd: Optional[int] = None,
+ follow_symlinks: bool = True) -> None:
"""Set ownership of a faked file.
Args:
@@ -4256,7 +4617,9 @@ class FakeOsModule:
if gid != -1:
file_object.st_gid = gid
- def mknod(self, path, mode=None, device=0, *, dir_fd=None):
+ def mknod(self, path: AnyStr, mode: Optional[int] = None,
+ device: int = 0, *,
+ dir_fd: Optional[int] = None) -> None:
"""Create a filesystem node named 'filename'.
Does not support device special files or named pipes as the real os
@@ -4277,7 +4640,7 @@ class FakeOsModule:
created.
"""
if self.filesystem.is_windows_fs:
- raise (AttributeError, "module 'os' has no attribute 'mknode'")
+ 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
@@ -4291,7 +4654,7 @@ class FakeOsModule:
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'..'):
+ if tail in (matching_string(tail, '.'), matching_string(tail, '..')):
self.filesystem.raise_os_error(errno.ENOENT, path)
if self.filesystem.exists(path, check_link=True):
self.filesystem.raise_os_error(errno.EEXIST, path)
@@ -4299,7 +4662,8 @@ class FakeOsModule:
tail, mode & ~self.filesystem.umask,
filesystem=self.filesystem))
- def symlink(self, src, dst, *, dir_fd=None):
+ def symlink(self, src: AnyStr, dst: AnyStr, *,
+ dir_fd: Optional[int] = None) -> None:
"""Creates the specified symlink, pointed at the specified link target.
Args:
@@ -4315,7 +4679,9 @@ class FakeOsModule:
self.filesystem.create_symlink(
dst, src, create_missing_dirs=False)
- def link(self, src, dst, *, src_dir_fd=None, dst_dir_fd=None):
+ def link(self, src: AnyStr, dst: AnyStr, *,
+ src_dir_fd: Optional[int] = None,
+ dst_dir_fd: Optional[int] = None) -> None:
"""Create a hard link at new_path, pointing at old_path.
Args:
@@ -4326,9 +4692,6 @@ class FakeOsModule:
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.
@@ -4337,7 +4700,7 @@ class FakeOsModule:
dst = self._path_with_dir_fd(dst, self.link, dst_dir_fd)
self.filesystem.link(src, dst)
- def fsync(self, fd):
+ def fsync(self, fd: int) -> None:
"""Perform fsync for a fake file (in other words, do nothing).
Args:
@@ -4350,14 +4713,14 @@ class FakeOsModule:
# 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)
+ file_object = cast(FakeFileWrapper, self.filesystem.get_open_file(fd))
if self.filesystem.is_windows_fs:
if (not hasattr(file_object, 'allow_update') or
not file_object.allow_update):
self.filesystem.raise_os_error(
errno.EBADF, file_object.file_path)
- def fdatasync(self, fd):
+ def fdatasync(self, fd: int) -> None:
"""Perform fdatasync for a fake file (in other words, do nothing).
Args:
@@ -4374,7 +4737,8 @@ class FakeOsModule:
self.filesystem.raise_os_error(errno.EINVAL)
self.filesystem.get_open_file(fd)
- def sendfile(self, fd_out, fd_in, offset, count):
+ def sendfile(self, fd_out: int, fd_in: int,
+ offset: int, count: int) -> int:
"""Copy count bytes from file descriptor fd_in to file descriptor
fd_out starting at offset.
@@ -4398,8 +4762,8 @@ class FakeOsModule:
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)
+ source = cast(FakeFileWrapper, self.filesystem.get_open_file(fd_in))
+ dest = cast(FakeFileWrapper, self.filesystem.get_open_file(fd_out))
if self.filesystem.is_macos:
if dest.get_object().stat_result.st_mode & 0o777000 != S_IFSOCK:
raise OSError('Socket operation on non-socket')
@@ -4421,7 +4785,7 @@ class FakeOsModule:
return written
return 0
- def __getattr__(self, name):
+ def __getattr__(self, name: str) -> Any:
"""Forwards any unfaked calls to the standard os module."""
return getattr(self._os_module, name)
@@ -4437,34 +4801,115 @@ class FakeIoModule:
"""
@staticmethod
- def dir():
+ def dir() -> List[str]:
"""Return the list of patched function names. Used for patching
functions imported from the module.
"""
- return 'open',
+ _dir = ['open']
+ if sys.version_info >= (3, 8):
+ _dir.append('open_code')
+ return _dir
- def __init__(self, filesystem):
+ def __init__(self, filesystem: FakeFilesystem):
"""
Args:
filesystem: FakeFilesystem used to provide file system information.
"""
self.filesystem = filesystem
+ self.skip_names: List[str] = []
self._io_module = io
- def open(self, file, mode='r', buffering=-1, encoding=None,
- errors=None, newline=None, closefd=True, opener=None):
+ def open(self, file: Union[AnyStr, int],
+ mode: str = 'r', buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ closefd: bool = True,
+ opener: Optional[Callable] = None) -> Union[AnyFileWrapper,
+ IO[Any]]:
"""Redirect the call to FakeFileOpen.
See FakeFileOpen.call() for description.
"""
+ # workaround for built-in open called from skipped modules (see #552)
+ # as open is not imported explicitly, we cannot patch it for
+ # specific modules; instead we check if the caller is a skipped
+ # module (should work in most cases)
+ stack = traceback.extract_stack(limit=2)
+ module_name = os.path.splitext(stack[0].filename)[0]
+ module_name = module_name.replace(os.sep, '.')
+ if any([module_name == sn or module_name.endswith('.' + sn)
+ for sn in self.skip_names]):
+ return io.open(file, mode, buffering, encoding, errors,
+ newline, closefd, opener)
fake_open = FakeFileOpen(self.filesystem)
return fake_open(file, mode, buffering, encoding, errors,
newline, closefd, opener)
+ if sys.version_info >= (3, 8):
+ def open_code(self, path):
+ """Redirect the call to open. Note that the behavior of the real
+ function may be overridden by an earlier call to the
+ PyFile_SetOpenCodeHook(). This behavior is not reproduced here.
+ """
+ if not isinstance(path, str):
+ raise TypeError(
+ "open_code() argument 'path' must be str, not int")
+ patch_mode = self.filesystem.patch_open_code
+ if (patch_mode == PatchMode.AUTO and self.filesystem.exists(path)
+ or patch_mode == PatchMode.ON):
+ return self.open(path, mode='rb')
+ # mostly this is used for compiled code -
+ # don't patch these, as the files are probably in the real fs
+ return self._io_module.open_code(path)
+
def __getattr__(self, name):
"""Forwards any unfaked calls to the standard io module."""
return getattr(self._io_module, name)
+if sys.platform != 'win32':
+ import fcntl
+
+ class FakeFcntlModule:
+ """Replaces the fcntl module. Only valid under Linux/MacOS,
+ currently just mocks the functionality away.
+ """
+
+ @staticmethod
+ def dir() -> List[str]:
+ """Return the list of patched function names. Used for patching
+ functions imported from the module.
+ """
+ return ['fcntl', 'ioctl', 'flock', 'lockf']
+
+ def __init__(self, filesystem: FakeFilesystem):
+ """
+ Args:
+ filesystem: FakeFilesystem used to provide file system
+ information (currently not used).
+ """
+ self.filesystem = filesystem
+ self._fcntl_module = fcntl
+
+ def fcntl(self, fd: int, cmd: int, arg: int = 0) -> Union[int, bytes]:
+ return 0
+
+ def ioctl(self, fd: int, request: int, arg: int = 0,
+ mutate_flag: bool = True) -> Union[int, bytes]:
+ return 0
+
+ def flock(self, fd: int, operation: int) -> None:
+ pass
+
+ def lockf(self, fd: int, cmd: int, len: int = 0,
+ start: int = 0, whence=0) -> Any:
+ pass
+
+ def __getattr__(self, name):
+ """Forwards any unfaked calls to the standard fcntl module."""
+ return getattr(self._fcntl_module, name)
+
+
class FakeFileWrapper:
"""Wrapper for a stream object for use by a FakeFile object.
@@ -4472,12 +4917,15 @@ class FakeFileWrapper:
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):
+ def __init__(self, file_object: FakeFile,
+ file_path: AnyStr,
+ update: bool, read: bool, append: bool, delete_on_close: bool,
+ filesystem: FakeFilesystem,
+ newline: Optional[str], binary: bool, closefd: bool,
+ encoding: Optional[str], errors: Optional[str],
+ buffering: int, raw_io: bool, is_stream: bool = False):
self.file_object = file_object
- self.file_path = file_path
+ self.file_path = file_path # type: ignore[var-annotated]
self._append = append
self._read = read
self.allow_update = update
@@ -4487,15 +4935,22 @@ class FakeFileWrapper:
self._binary = binary
self.is_stream = is_stream
self._changed = False
+ self._buffer_size = buffering
+ if self._buffer_size == 0 and not binary:
+ raise ValueError("can't have unbuffered text I/O")
+ # buffer_size is ignored in text mode
+ elif self._buffer_size == -1 or not binary:
+ self._buffer_size = io.DEFAULT_BUFFER_SIZE
+ self._use_line_buffer = not binary and buffering == 1
+
contents = file_object.byte_contents
self._encoding = encoding or 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._io: Union[BinaryBufferIO, TextBufferIO] = (
+ BinaryBufferIO(contents) if binary
+ else TextBufferIO(contents, encoding=encoding,
+ newline=newline, errors=errors)
+ )
self._read_whence = 0
self._read_seek = 0
self._flush_pos = 0
@@ -4515,31 +4970,36 @@ class FakeFileWrapper:
# 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
+ self.filedes: Optional[int] = None
- def __enter__(self):
+ def __enter__(self) -> 'FakeFileWrapper':
"""To support usage of this fake file with the 'with' statement."""
return self
- def __exit__(self, type, value, traceback):
+ def __exit__(self, exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType]
+ ) -> None:
"""To support usage of this fake file with the 'with' statement."""
self.close()
- def _raise(self, message):
+ def _raise(self, message: str) -> NoReturn:
if self.raw_io:
self._filesystem.raise_os_error(errno.EBADF, self.file_path)
raise io.UnsupportedOperation(message)
- def get_object(self):
+ def get_object(self) -> FakeFile:
"""Return the FakeFile object that is wrapped by the current instance.
"""
return self.file_object
- def fileno(self):
+ def fileno(self) -> int:
"""Return the file descriptor of the file object."""
- return self.filedes
+ if self.filedes is not None:
+ return self.filedes
+ raise OSError(errno.EBADF, 'Invalid file descriptor')
- def close(self):
+ def close(self) -> None:
"""Close the file."""
# ignore closing a closed file
if not self._is_open():
@@ -4549,39 +5009,56 @@ class FakeFileWrapper:
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()
+ self.file_object.st_mtime = now()
+
+ assert self.filedes is not None
if self._closefd:
self._filesystem._close_open_file(self.filedes)
else:
- self._filesystem.open_files[self.filedes].remove(self)
+ open_files = self._filesystem.open_files[self.filedes]
+ assert open_files is not None
+ open_files.remove(self)
if self.delete_on_close:
- self._filesystem.remove_object(self.get_object().path)
+ self._filesystem.remove_object(
+ self.get_object().path) # type: ignore[arg-type]
@property
- def closed(self):
+ def closed(self) -> bool:
"""Simulate the `closed` attribute on file."""
return not self._is_open()
- def flush(self):
+ def _try_flush(self, old_pos: int) -> None:
+ """Try to flush and reset the position if it fails."""
+ flush_pos = self._flush_pos
+ try:
+ self.flush()
+ except OSError:
+ # write failed - reset to previous position
+ self._io.seek(old_pos)
+ self._io.truncate()
+ self._flush_pos = flush_pos
+ raise
+
+ def flush(self) -> None:
"""Flush file contents to 'disk'."""
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)
+ old_contents = self.file_object.byte_contents
+ assert old_contents is not None
contents = old_contents + contents[self._flush_pos:]
self._set_stream_contents(contents)
- self.update_flush_pos()
else:
self._io.flush()
- if self.file_object.set_contents(contents, self._encoding):
+ changed = self.file_object.set_contents(contents, self._encoding)
+ self.update_flush_pos()
+ if changed:
if self._filesystem.is_windows_fs:
self._changed = True
else:
- current_time = time.time()
+ current_time = now()
self.file_object.st_ctime = current_time
self.file_object.st_mtime = current_time
self._file_epoch = self.file_object.epoch
@@ -4589,19 +5066,20 @@ class FakeFileWrapper:
if not self.is_stream:
self._flush_related_files()
- def update_flush_pos(self):
+ def update_flush_pos(self) -> None:
self._flush_pos = self._io.tell()
- def _flush_related_files(self):
+ def _flush_related_files(self) -> None:
for open_files in self._filesystem.open_files[3:]:
if open_files is not None:
for open_file in open_files:
if (open_file is not self and
+ isinstance(open_file, FakeFileWrapper) and
self.file_object == open_file.file_object and
not open_file._append):
open_file._sync_io()
- def seek(self, offset, whence=0):
+ def seek(self, offset: int, whence: int = 0) -> None:
"""Move read/write pointer in 'file'."""
self._check_open_file()
if not self._append:
@@ -4612,7 +5090,7 @@ class FakeFileWrapper:
if not self.is_stream:
self.flush()
- def tell(self):
+ def tell(self) -> int:
"""Return the file's current position.
Returns:
@@ -4632,30 +5110,25 @@ class FakeFileWrapper:
self._io.seek(write_seek)
return self._read_seek
- def _sync_io(self):
+ def _sync_io(self) -> None:
"""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
-
+ contents = self.file_object.byte_contents
+ assert contents is not None
self._set_stream_contents(contents)
self._file_epoch = self.file_object.epoch
- def _set_stream_contents(self, contents):
+ def _set_stream_contents(self, contents: bytes) -> None:
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):
+ def _read_wrappers(self, name: str) -> Callable:
"""Wrap a stream attribute in a read wrapper.
Returns a read_wrapper which tracks our own read pointer since the
@@ -4691,7 +5164,7 @@ class FakeFileWrapper:
return read_wrapper
- def _other_wrapper(self, name, writing):
+ def _other_wrapper(self, name: str) -> Callable:
"""Wrap a stream attribute in an other_wrapper.
Args:
@@ -4721,20 +5194,72 @@ class FakeFileWrapper:
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):
+ def _write_wrapper(self, name: str) -> Callable:
+ """Wrap a stream attribute in a write_wrapper.
+
+ Args:
+ name: the name of the stream attribute to wrap.
+
+ Returns:
+ write_wrapper which is described below.
+ """
+ io_attr = getattr(self._io, name)
+
+ def write_wrapper(*args, **kwargs):
+ """Wrap all other calls to the stream Object.
+
+ We do this to track changes to the write pointer. Anything that
+ moves the write pointer in a file open for appending should move
+ the read pointer as well.
+
+ Args:
+ *args: Pass through args.
+ **kwargs: Pass through kwargs.
+
+ Returns:
+ Wrapped stream object method.
+ """
+ old_pos = self._io.tell()
+ ret_value = io_attr(*args, **kwargs)
+ new_pos = self._io.tell()
+
+ # if the buffer size is exceeded, we flush
+ use_line_buf = self._use_line_buffer and '\n' in args[0]
+ if new_pos - self._flush_pos > self._buffer_size or use_line_buf:
+ flush_all = (new_pos - old_pos > self._buffer_size or
+ use_line_buf)
+ # if the current write does not exceed the buffer size,
+ # we revert to the previous position and flush that,
+ # otherwise we flush all
+ if not flush_all:
+ self._io.seek(old_pos)
+ self._io.truncate()
+ self._try_flush(old_pos)
+ if not flush_all:
+ ret_value = io_attr(*args, **kwargs)
+ if self._append:
+ self._read_seek = self._io.tell()
+ self._read_whence = 0
+ return ret_value
+
+ return write_wrapper
+
+ def _adapt_size_for_related_files(self, size: int) -> None:
for open_files in self._filesystem.open_files[3:]:
if open_files is not None:
for open_file in open_files:
if (open_file is not self and
+ isinstance(open_file, FakeFileWrapper) and
self.file_object == open_file.file_object and
- open_file._append):
+ cast(FakeFileWrapper, open_file)._append):
open_file._read_seek += size
- def _truncate_wrapper(self):
+ def _truncate_wrapper(self) -> Callable:
"""Wrap truncate() to allow flush after truncate.
Returns:
@@ -4753,7 +5278,7 @@ class FakeFileWrapper:
buffer_size = len(self._io.getvalue())
if buffer_size < size:
self._io.seek(buffer_size)
- self._io.write('\0' * (size - buffer_size))
+ self._io.putvalue(b'\0' * (size - buffer_size))
self.file_object.set_contents(
self._io.getvalue(), self._encoding)
self._flush_pos = size
@@ -4764,11 +5289,11 @@ class FakeFileWrapper:
return truncate_wrapper
- def size(self):
+ def size(self) -> int:
"""Return the content size in bytes of the wrapped file."""
return self.file_object.st_size
- def __getattr__(self, name):
+ def __getattr__(self, name: str) -> Any:
if self.file_object.is_large_file():
raise FakeLargeFileIoException(self.file_path)
@@ -4788,18 +5313,20 @@ class FakeFileWrapper:
if not self.is_stream:
self.flush()
if not self._filesystem.is_windows_fs:
- self.file_object.st_atime = time.time()
+ self.file_object.st_atime = now()
if truncate:
return self._truncate_wrapper()
if self._append:
if reading:
return self._read_wrappers(name)
- else:
- return self._other_wrapper(name, writing)
+ elif not writing:
+ return self._other_wrapper(name)
+ if writing:
+ return self._write_wrapper(name)
return getattr(self._io, name)
- def _read_error(self):
+ def _read_error(self) -> Callable:
def read_error(*args, **kwargs):
"""Throw an error unless the argument is zero."""
if args and args[0] == 0:
@@ -4809,7 +5336,7 @@ class FakeFileWrapper:
return read_error
- def _write_error(self):
+ def _write_error(self) -> Callable:
def write_error(*args, **kwargs):
"""Throw an error."""
if self.raw_io:
@@ -4820,16 +5347,19 @@ class FakeFileWrapper:
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 _is_open(self) -> bool:
+ if (self.filedes is not None and
+ self.filedes < len(self._filesystem.open_files)):
+ open_files = self._filesystem.open_files[self.filedes]
+ if open_files is not None and self in open_files:
+ return True
+ return False
- def _check_open_file(self):
+ def _check_open_file(self) -> None:
if not self.is_stream and not self._is_open():
raise ValueError('I/O operation on closed file')
- def __iter__(self):
+ def __iter__(self) -> Union[Iterator[str], Iterator[bytes]]:
if not self._read:
self._raise('File is not open for reading')
return self._io.__iter__()
@@ -4844,22 +5374,27 @@ class StandardStreamWrapper:
"""Wrapper for a system standard stream to be used in open files list.
"""
- def __init__(self, stream_object):
+ def __init__(self, stream_object: TextIO):
self._stream_object = stream_object
- self.filedes = None
+ self.filedes: Optional[int] = None
- def get_object(self):
+ def get_object(self) -> TextIO:
return self._stream_object
- def fileno(self):
+ def fileno(self) -> int:
"""Return the file descriptor of the wrapped standard stream."""
- return self.filedes
+ if self.filedes is not None:
+ return self.filedes
+ raise OSError(errno.EBADF, 'Invalid file descriptor')
+
+ def read(self, n: int = -1) -> bytes:
+ return cast(bytes, self._stream_object.read())
- def close(self):
+ def close(self) -> None:
"""We do not support closing standard streams."""
pass
- def is_stream(self):
+ def is_stream(self) -> bool:
return True
@@ -4867,23 +5402,27 @@ class FakeDirWrapper:
"""Wrapper for a FakeDirectory object to be used in open files list.
"""
- def __init__(self, file_object, file_path, filesystem):
+ def __init__(self, file_object: FakeDirectory,
+ file_path: AnyString, filesystem: FakeFilesystem):
self.file_object = file_object
self.file_path = file_path
self._filesystem = filesystem
- self.filedes = None
+ self.filedes: Optional[int] = None
- def get_object(self):
+ def get_object(self) -> FakeDirectory:
"""Return the FakeFile object that is wrapped by the current instance.
"""
return self.file_object
- def fileno(self):
+ def fileno(self) -> int:
"""Return the file descriptor of the file object."""
- return self.filedes
+ if self.filedes is not None:
+ return self.filedes
+ raise OSError(errno.EBADF, 'Invalid file descriptor')
- def close(self):
+ def close(self) -> None:
"""Close the directory."""
+ assert self.filedes is not None
self._filesystem._close_open_file(self.filedes)
@@ -4892,31 +5431,75 @@ class FakePipeWrapper:
used in open files list.
"""
- def __init__(self, filesystem, fd):
+ def __init__(self, filesystem: FakeFilesystem,
+ fd: int, can_write: bool, mode: str = ''):
self._filesystem = filesystem
self.fd = fd # the real file descriptor
+ self.can_write = can_write
self.file_object = None
- self.filedes = None
+ self.filedes: Optional[int] = None
+ self.real_file = None
+ if mode:
+ self.real_file = open(fd, mode)
- def get_object(self):
+ def __enter__(self) -> 'FakePipeWrapper':
+ """To support usage of this fake pipe with the 'with' statement."""
+ return self
+
+ def __exit__(self, exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType]
+ ) -> None:
+ """To support usage of this fake pipe with the 'with' statement."""
+ self.close()
+
+ def get_object(self) -> None:
return self.file_object
- def fileno(self):
+ def fileno(self) -> int:
"""Return the fake file descriptor of the pipe object."""
- return self.filedes
+ if self.filedes is not None:
+ return self.filedes
+ raise OSError(errno.EBADF, 'Invalid file descriptor')
- def read(self, numBytes):
+ def read(self, numBytes: int = -1) -> bytes:
"""Read from the real pipe."""
+ if self.real_file:
+ return self.real_file.read(numBytes)
return os.read(self.fd, numBytes)
- def write(self, contents):
+ def flush(self) -> None:
+ """Flush the real pipe?"""
+ pass
+
+ def write(self, contents: bytes) -> int:
"""Write to the real pipe."""
+ if self.real_file:
+ return self.real_file.write(contents)
return os.write(self.fd, contents)
- def close(self):
+ def close(self) -> None:
"""Close the pipe descriptor."""
- self._filesystem.open_files[self.filedes].remove(self)
- os.close(self.fd)
+ assert self.filedes is not None
+ open_files = self._filesystem.open_files[self.filedes]
+ assert open_files is not None
+ open_files.remove(self)
+ if self.real_file:
+ self.real_file.close()
+ else:
+ os.close(self.fd)
+
+ def readable(self) -> bool:
+ """The pipe end can either be readable or writable."""
+ return not self.can_write
+
+ def writable(self) -> bool:
+ """The pipe end can either be readable or writable."""
+ return self.can_write
+
+ def seekable(self) -> bool:
+ """A pipe is not seekable."""
+ return False
Deprecator.add(FakeFileWrapper, FakeFileWrapper.get_object, 'GetObject')
@@ -4931,7 +5514,8 @@ class FakeFileOpen:
"""
__name__ = 'FakeFileOpen'
- def __init__(self, filesystem, delete_on_close=False, raw_io=False):
+ def __init__(self, filesystem: FakeFilesystem,
+ delete_on_close: bool = False, raw_io: bool = False):
"""
Args:
filesystem: FakeFilesystem used to provide file system information
@@ -4941,21 +5525,29 @@ class FakeFileOpen:
self._delete_on_close = delete_on_close
self.raw_io = raw_io
- def __call__(self, *args, **kwargs):
+ def __call__(self, *args: Any, **kwargs: Any) -> AnyFileWrapper:
"""Redirects calls to file() or open() to appropriate method."""
return self.call(*args, **kwargs)
- def call(self, file_, mode='r', buffering=-1, encoding=None,
- errors=None, newline=None, closefd=True, opener=None,
- open_modes=None):
+ def call(self, file_: Union[AnyStr, int],
+ mode: str = 'r',
+ buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ closefd: bool = True,
+ opener: Any = None,
+ open_modes: Optional[_OpenModes] = None) -> AnyFileWrapper:
"""Return a file-like object with the contents of the target
file object.
Args:
file_: Path to target file or a file descriptor.
mode: Additional file modes (all modes in `open()` are supported).
- buffering: ignored. (Used for signature compliance with
- __builtin__.open)
+ buffering: the buffer size used for writing. Data will only be
+ flushed if buffer size is exceeded. The default (-1) uses a
+ system specific default buffer size. Text line mode (e.g.
+ buffering=1 in text mode) is not supported.
encoding: The encoding used to encode unicode strings / decode
bytes.
errors: (str) Defines how encoding errors are handled.
@@ -4979,10 +5571,31 @@ class FakeFileOpen:
ValueError: for an invalid mode or mode combination
"""
binary = 'b' in mode
+
+ if binary and encoding:
+ raise ValueError("binary mode doesn't take an encoding argument")
+
newline, open_modes = self._handle_file_mode(mode, newline, open_modes)
file_object, file_path, filedes, real_path = self._handle_file_arg(
file_)
+ if file_object is None and file_path is None:
+ # file must be a fake pipe wrapper, find it...
+ if (filedes is None or
+ len(self.filesystem.open_files) <= filedes or
+ not self.filesystem.open_files[filedes]):
+ raise OSError(errno.EBADF, 'invalid pipe file descriptor')
+ wrappers = self.filesystem.open_files[filedes]
+ assert wrappers is not None
+ existing_wrapper = wrappers[0]
+ assert isinstance(existing_wrapper, FakePipeWrapper)
+ wrapper = FakePipeWrapper(self.filesystem, existing_wrapper.fd,
+ existing_wrapper.can_write, mode)
+ file_des = self.filesystem._add_open_file(wrapper)
+ wrapper.filedes = file_des
+ return wrapper
+
+ assert file_path is not None
if not filedes:
closefd = True
@@ -4991,6 +5604,7 @@ class FakeFileOpen:
not self.filesystem.is_windows_fs)):
self.filesystem.raise_os_error(errno.EEXIST, file_path)
+ assert real_path is not None
file_object = self._init_file_object(file_object,
file_path, open_modes,
real_path)
@@ -5005,7 +5619,7 @@ class FakeFileOpen:
# Not the abspath, not the filename, but the actual argument.
file_object.opened_as = file_path
if open_modes.truncate:
- current_time = time.time()
+ current_time = now()
file_object.st_mtime = current_time
if not self.filesystem.is_windows_fs:
file_object.st_ctime = current_time
@@ -5022,17 +5636,22 @@ class FakeFileOpen:
closefd=closefd,
encoding=encoding,
errors=errors,
+ buffering=buffering,
raw_io=self.raw_io)
if filedes is not None:
fakefile.filedes = filedes
# replace the file wrapper
- self.filesystem.open_files[filedes].append(fakefile)
+ open_files_list = self.filesystem.open_files[filedes]
+ assert open_files_list is not None
+ open_files_list.append(fakefile)
else:
fakefile.filedes = self.filesystem._add_open_file(fakefile)
return fakefile
- def _init_file_object(self, file_object, file_path,
- open_modes, real_path):
+ def _init_file_object(self, file_object: Optional[FakeFile],
+ file_path: AnyStr,
+ open_modes: _OpenModes,
+ real_path: AnyString) -> FakeFile:
if file_object:
if (not is_root() and
((open_modes.can_read and
@@ -5049,45 +5668,57 @@ class FakeFileOpen:
if self.filesystem.islink(file_path):
link_object = self.filesystem.resolve(file_path,
follow_symlinks=False)
- target_path = link_object.contents
+ assert link_object.contents is not None
+ target_path = cast(AnyStr, 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)
+ 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)
+ apply_umask=True)
return file_object
- def _handle_file_arg(self, file_):
+ def _handle_file_arg(self, file_: Union[AnyStr, int]) -> Tuple[
+ Optional[FakeFile], Optional[AnyStr],
+ Optional[int], Optional[AnyStr]]:
file_object = None
if isinstance(file_, int):
# opening a file descriptor
- filedes = file_
+ filedes: int = 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
+ if isinstance(wrapper, FakePipeWrapper):
+ return None, None, filedes, None
+ if isinstance(wrapper, FakeFileWrapper):
+ self._delete_on_close = wrapper.delete_on_close
+ file_object = cast(FakeFile, self.filesystem.get_open_file(
+ filedes).get_object())
+ assert file_object is not None
+ path = file_object.name
+ return file_object, cast(AnyStr, path), filedes, cast(AnyStr, path)
+
+ # open a file file by path
+ file_path = cast(AnyStr, file_)
+ if file_path == self.filesystem.dev_null.name:
+ file_object = self.filesystem.dev_null
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):
+ real_path = self.filesystem.resolve_path(file_path)
+ if self.filesystem.exists(file_path):
+ file_object = self.filesystem.get_object_from_normpath(
+ real_path, check_read_perm=False)
+ return file_object, file_path, None, real_path
+
+ def _handle_file_mode(
+ self, mode: str,
+ newline: Optional[str],
+ open_modes: Optional[_OpenModes]) -> Tuple[Optional[str],
+ _OpenModes]:
orig_modes = mode # Save original modes for error messages.
# Normalize modes. Handle 't' and 'U'.
if 'b' in mode and 't' in mode:
@@ -5098,10 +5729,11 @@ class FakeFileOpen:
if mode not in _OPEN_MODE_MAP:
raise ValueError('Invalid mode: %r' % orig_modes)
open_modes = _OpenModes(*_OPEN_MODE_MAP[mode])
+ assert open_modes is not None
return newline, open_modes
-def _run_doctest():
+def _run_doctest() -> TestResults:
import doctest
import pyfakefs
return doctest.testmod(pyfakefs.fake_filesystem)
diff --git a/pyfakefs/fake_filesystem_unittest.py b/pyfakefs/fake_filesystem_unittest.py
index dbb2e34..6633cb5 100644
--- a/pyfakefs/fake_filesystem_unittest.py
+++ b/pyfakefs/fake_filesystem_unittest.py
@@ -38,98 +38,114 @@ to `:py:class`pyfakefs.fake_filesystem_unittest.TestCase`.
import doctest
import functools
import inspect
+import linecache
import shutil
import sys
import tempfile
+import tokenize
+from importlib.abc import Loader, MetaPathFinder
+from types import ModuleType, TracebackType, FunctionType
+from typing import (
+ Any, Callable, Dict, List, Set, Tuple, Optional, Union,
+ AnyStr, Type, Iterator, cast, ItemsView, Sequence
+)
import unittest
import warnings
+from unittest import TestSuite
from pyfakefs.deprecator import Deprecator
-from pyfakefs.fake_filesystem import set_uid, set_gid, reset_ids
+from pyfakefs.fake_filesystem import (
+ set_uid, set_gid, reset_ids, PatchMode, FakeFile, FakeFilesystem
+)
from pyfakefs.helpers import IS_PYPY
+from pyfakefs.mox3_stubout import StubOutForTesting
try:
from importlib.machinery import ModuleSpec
except ImportError:
- ModuleSpec = object
+ ModuleSpec = object # type: ignore[assignment, misc]
from importlib import reload
from pyfakefs import fake_filesystem
from pyfakefs import fake_filesystem_shutil
+from pyfakefs import fake_pathlib
from pyfakefs import mox3_stubout
-from pyfakefs.extra_packages import pathlib, pathlib2, use_scandir
-
-if pathlib:
- from pyfakefs import fake_pathlib
+from pyfakefs.extra_packages import pathlib2, use_scandir
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):
+def patchfs(_func: Callable = None, *,
+ additional_skip_names: Optional[
+ List[Union[str, ModuleType]]] = None,
+ modules_to_reload: Optional[List[ModuleType]] = None,
+ modules_to_patch: Optional[Dict[str, ModuleType]] = None,
+ allow_root_user: bool = True,
+ use_known_patches: bool = True,
+ patch_open_code: PatchMode = PatchMode.OFF,
+ patch_default_args: bool = False,
+ use_cache: bool = True) -> Callable:
"""Convenience decorator to use patcher with additional parameters in a
test function.
Usage::
@patchfs
- test_my_function(fs):
- fs.create_file('foo')
+ def test_my_function(fake_fs):
+ fake_fs.create_file('foo')
@patchfs(allow_root_user=False)
- test_with_patcher_args(fs):
+ def test_with_patcher_args(fs):
os.makedirs('foo/bar')
"""
- def wrap_patchfs(f):
+ def wrap_patchfs(f: Callable) -> Callable:
@functools.wraps(f)
def wrapped(*args, **kwargs):
with Patcher(
additional_skip_names=additional_skip_names,
modules_to_reload=modules_to_reload,
modules_to_patch=modules_to_patch,
- allow_root_user=allow_root_user) as p:
- kwargs['fs'] = p.fs
+ allow_root_user=allow_root_user,
+ use_known_patches=use_known_patches,
+ patch_open_code=patch_open_code,
+ patch_default_args=patch_default_args,
+ use_cache=use_cache) as p:
+ args = list(args)
+ args.append(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)
+ if _func:
+ if not callable(_func):
+ raise TypeError(
+ "Decorator argument is not a function.\n"
+ "Did you mean `@patchfs(additional_skip_names=...)`?"
+ )
+ if hasattr(_func, 'patchings'):
+ _func.nr_patches = len(_func.patchings) # type: ignore
+ return wrap_patchfs(_func)
return wrap_patchfs
-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
+def load_doctests(
+ loader: Any, tests: TestSuite, ignore: Any, module: ModuleType,
+ additional_skip_names: Optional[
+ List[Union[str, ModuleType]]] = None,
+ modules_to_reload: Optional[List[ModuleType]] = None,
+ modules_to_patch: Optional[Dict[str, ModuleType]] = None,
+ allow_root_user: bool = True,
+ use_known_patches: bool = True,
+ patch_open_code: PatchMode = PatchMode.OFF,
+ patch_default_args: bool = False
+) -> TestSuite: # pylint:disable=unused-argument
"""Load the doctest tests for the specified module into unittest.
Args:
loader, tests, ignore : arguments passed in from `load_tests()`
@@ -141,7 +157,10 @@ def load_doctests(loader, tests, ignore, module,
_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)
+ allow_root_user=allow_root_user,
+ use_known_patches=use_known_patches,
+ patch_open_code=patch_open_code,
+ patch_default_args=patch_default_args)
globs = _patcher.replace_globs(vars(module))
tests.addTests(doctest.DocTestSuite(module,
globs=globs,
@@ -190,19 +209,24 @@ class TestCaseMixin:
methodName=methodName, modules_to_reload=[sut])
"""
- additional_skip_names = None
- modules_to_reload = None
- modules_to_patch = None
+ additional_skip_names: Optional[List[Union[str, ModuleType]]] = None
+ modules_to_reload: Optional[List[ModuleType]] = None
+ modules_to_patch: Optional[Dict[str, ModuleType]] = None
@property
- def fs(self):
- return self._stubber.fs
+ def fs(self) -> FakeFilesystem:
+ return cast(FakeFilesystem, self._stubber.fs)
def setUpPyfakefs(self,
- additional_skip_names=None,
- modules_to_reload=None,
- modules_to_patch=None,
- allow_root_user=True):
+ additional_skip_names: Optional[
+ List[Union[str, ModuleType]]] = None,
+ modules_to_reload: Optional[List[ModuleType]] = None,
+ modules_to_patch: Optional[Dict[str, ModuleType]] = None,
+ allow_root_user: bool = True,
+ use_known_patches: bool = True,
+ patch_open_code: PatchMode = PatchMode.OFF,
+ patch_default_args: bool = False,
+ use_cache: bool = True) -> None:
"""Bind the file-related modules to the :py:class:`pyfakefs` fake file
system instead of the real file system. Also bind the fake `open()`
function.
@@ -224,13 +248,17 @@ class TestCaseMixin:
additional_skip_names=additional_skip_names,
modules_to_reload=modules_to_reload,
modules_to_patch=modules_to_patch,
- allow_root_user=allow_root_user
+ allow_root_user=allow_root_user,
+ use_known_patches=use_known_patches,
+ patch_open_code=patch_open_code,
+ patch_default_args=patch_default_args,
+ use_cache=use_cache
)
self._stubber.setUp()
- self.addCleanup(self._stubber.tearDown)
+ cast(TestCase, self).addCleanup(self._stubber.tearDown)
- def pause(self):
+ def pause(self) -> None:
"""Pause the patching of the file system modules until `resume` is
called. After that call, all file system calls are executed in the
real file system.
@@ -239,7 +267,7 @@ class TestCaseMixin:
"""
self._stubber.pause()
- def resume(self):
+ def resume(self) -> None:
"""Resume the patching of the file system modules if `pause` has
been called before. After that call, all file system calls are
executed in the fake file system.
@@ -255,11 +283,11 @@ class TestCase(unittest.TestCase, 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):
+ def __init__(self, methodName: str = 'runTest',
+ additional_skip_names: Optional[
+ List[Union[str, ModuleType]]] = None,
+ modules_to_reload: Optional[List[ModuleType]] = None,
+ modules_to_patch: Optional[Dict[str, ModuleType]] = None):
"""Creates the test class instance and the patcher used to stub out
file system related modules.
@@ -267,16 +295,16 @@ class TestCase(unittest.TestCase, TestCaseMixin):
methodName: The name of the test method (same as in
unittest.TestCase)
"""
- super(TestCase, self).__init__(methodName)
+ super().__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):
+ def copyRealFile(self, real_file_path: AnyStr,
+ fake_file_path: Optional[AnyStr] = None,
+ create_missing_dirs: bool = True) -> FakeFile:
"""Add the file `real_file_path` in the real file system to the same
path in the fake file system.
@@ -312,10 +340,10 @@ class TestCase(unittest.TestCase, TestCaseMixin):
if not create_missing_dirs:
raise ValueError("CopyRealFile() is deprecated and no longer "
"supports NOT creating missing directories")
+ assert self._stubber.fs is not None
return self._stubber.fs.add_real_file(real_file_path, read_only=False)
- @DeprecationWarning
- def tearDownPyfakefs(self):
+ def tearDownPyfakefs(self) -> None:
"""This method is deprecated and exists only for backward
compatibility. It does nothing.
"""
@@ -338,67 +366,165 @@ class Patcher:
'''Stub nothing that is imported within these modules.
`sys` is included to prevent `sys.path` from being stubbed with the fake
`os.path`.
+ The `pytest` and `py` modules are used by pytest and have to access the
+ real file system.
+ The `linecache` module is used to read the test file in case of test
+ failure to get traceback information before test tear down.
+ In order to make sure that reading the test file is not faked,
+ we skip faking the module.
+ We also have to set back the cached open function in tokenize.
'''
- SKIPMODULES = {None, fake_filesystem, fake_filesystem_shutil, sys}
+ SKIPMODULES = {
+ None, fake_filesystem, fake_filesystem_shutil,
+ sys, linecache, tokenize
+ }
+ # caches all modules that do not have file system modules or function
+ # to speed up _find_modules
+ CACHED_MODULES: Set[ModuleType] = set()
+ FS_MODULES: Dict[str, Set[Tuple[ModuleType, str]]] = {}
+ FS_FUNCTIONS: Dict[Tuple[str, str, str], Set[ModuleType]] = {}
+ FS_DEFARGS: List[Tuple[FunctionType, int, Callable[..., Any]]] = []
+ SKIPPED_FS_MODULES: Dict[str, Set[Tuple[ModuleType, str]]] = {}
+
assert None in SKIPMODULES, ("sys.modules contains 'None' values;"
" must skip them.")
IS_WINDOWS = sys.platform in ('win32', 'cygwin')
- SKIPNAMES = {'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__"""
+ SKIPNAMES = {'os', 'path', 'io', 'genericpath', 'fcntl',
+ OS_MODULE, PATH_MODULE}
+
+ # hold values from last call - if changed, the cache has to be invalidated
+ PATCHED_MODULE_NAMES: Set[str] = set()
+ ADDITIONAL_SKIP_NAMES: Set[str] = set()
+ PATCH_DEFAULT_ARGS = False
+
+ def __init__(self, additional_skip_names: Optional[
+ List[Union[str, ModuleType]]] = None,
+ modules_to_reload: Optional[List[ModuleType]] = None,
+ modules_to_patch: Optional[Dict[str, ModuleType]] = None,
+ allow_root_user: bool = True,
+ use_known_patches: bool = True,
+ patch_open_code: PatchMode = PatchMode.OFF,
+ patch_default_args: bool = False,
+ use_cache: bool = True) -> None:
+ """
+ Args:
+ 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`.
+ allow_root_user: If True (default), if the test is run as root
+ user, the user in the fake file system is also considered a
+ root user, otherwise it is always considered a regular user.
+ use_known_patches: If True (the default), some patches for commonly
+ used packages are applied which make them usable with pyfakefs.
+ patch_open_code: If True, `io.open_code` is patched. The default
+ is not to patch it, as it mostly is used to load compiled
+ modules that are not in the fake file system.
+ patch_default_args: If True, default arguments are checked for
+ file system functions, which are patched. This check is
+ expansive, so it is off by default.
+ use_cache: If True (default), patched and non-patched modules are
+ cached between tests for performance reasons. As this is a new
+ feature, this argument allows to turn it off in case it
+ causes any problems.
+ """
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()
+ self._skip_names = self.SKIPNAMES.copy()
# save the original open function for use in pytest plugin
self.original_open = open
- self.fake_open = None
+ self.patch_open_code = patch_open_code
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 = {}
+ skip_names = [
+ cast(ModuleType, m).__name__ if inspect.ismodule(m)
+ else cast(str, m) for m in additional_skip_names
+ ]
+ self._skip_names.update(skip_names)
+
+ self._fake_module_classes: Dict[str, Any] = {}
+ self._unfaked_module_classes: Dict[str, Any] = {}
+ self._class_modules: Dict[str, List[str]] = {}
self._init_fake_module_classes()
- self.modules_to_reload = modules_to_reload or []
+ # reload tempfile under posix to patch default argument
+ self.modules_to_reload: List[ModuleType] = (
+ [] if sys.platform == 'win32' else [tempfile]
+ )
+ if modules_to_reload is not None:
+ self.modules_to_reload.extend(modules_to_reload)
+ self.patch_default_args = patch_default_args
+ self.use_cache = use_cache
+
+ if use_known_patches:
+ from pyfakefs.patched_packages import (
+ get_modules_to_patch, get_classes_to_patch,
+ get_fake_module_classes
+ )
+
+ modules_to_patch = modules_to_patch or {}
+ modules_to_patch.update(get_modules_to_patch())
+ self._class_modules.update(get_classes_to_patch())
+ self._fake_module_classes.update(get_fake_module_classes())
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 = {}
+ patched_module_names = set(modules_to_patch)
+ else:
+ patched_module_names = set()
+ clear_cache = not use_cache
+ if use_cache:
+ if patched_module_names != self.PATCHED_MODULE_NAMES:
+ self.__class__.PATCHED_MODULE_NAMES = patched_module_names
+ clear_cache = True
+ if self._skip_names != self.ADDITIONAL_SKIP_NAMES:
+ self.__class__.ADDITIONAL_SKIP_NAMES = self._skip_names
+ clear_cache = True
+ if patch_default_args != self.PATCH_DEFAULT_ARGS:
+ self.__class__.PATCH_DEFAULT_ARGS = patch_default_args
+ clear_cache = True
+
+ if clear_cache:
+ self.clear_cache()
+ self._fake_module_functions: Dict[str, Dict] = {}
self._init_fake_module_functions()
# Attributes set by _refresh()
- self._modules = {}
- self._fct_modules = {}
- self._def_functions = []
- self._open_functions = {}
- self._stubs = None
- self.fs = None
- self.fake_modules = {}
- self._dyn_patcher = None
+ self._stubs: Optional[StubOutForTesting] = None
+ self.fs: Optional[FakeFilesystem] = None
+ self.fake_modules: Dict[str, Any] = {}
+ self.unfaked_modules: Dict[str, Any] = {}
# _isStale is set by tearDown(), reset by _refresh()
self._isStale = True
+ self._dyn_patcher: Optional[DynamicPatcher] = None
self._patching = False
- def _init_fake_module_classes(self):
+ def clear_cache(self) -> None:
+ """Clear the module cache."""
+ self.__class__.CACHED_MODULES = set()
+ self.__class__.FS_MODULES = {}
+ self.__class__.FS_FUNCTIONS = {}
+ self.__class__.FS_DEFARGS = []
+ self.__class__.SKIPPED_FS_MODULES = {}
+
+ def _init_fake_module_classes(self) -> None:
# IMPORTANT TESTING NOTE: Whenever you add a new module below, test
# it by adding an attribute in fixtures/module_with_attributes.py
# and a test in fake_filesystem_unittest_test.py, class
@@ -407,31 +533,36 @@ class Patcher:
'os': fake_filesystem.FakeOsModule,
'shutil': fake_filesystem_shutil.FakeShutilModule,
'io': fake_filesystem.FakeIoModule,
+ 'pathlib': fake_pathlib.FakePathlibModule
}
if IS_PYPY:
# in PyPy io.open, the module is referenced as _io
self._fake_module_classes['_io'] = fake_filesystem.FakeIoModule
+ if sys.platform != 'win32':
+ self._fake_module_classes[
+ 'fcntl'] = fake_filesystem.FakeFcntlModule
# class modules maps class names against a list of modules they can
# be contained in - this allows for alternative modules like
# `pathlib` and `pathlib2`
- 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._class_modules['Path'] = ['pathlib']
+ self._unfaked_module_classes[
+ 'pathlib'] = fake_pathlib.RealPathlibModule
+ if pathlib2:
self._fake_module_classes[
- 'Path'] = fake_pathlib.FakePathlibPathModule
+ 'pathlib2'] = fake_pathlib.FakePathlibModule
+ self._class_modules['Path'].append('pathlib2')
+ self._unfaked_module_classes[
+ 'pathlib2'] = fake_pathlib.RealPathlibModule
+ self._fake_module_classes[
+ 'Path'] = fake_pathlib.FakePathlibPathModule
+ self._unfaked_module_classes[
+ 'Path'] = fake_pathlib.RealPathlibPathModule
if use_scandir:
self._fake_module_classes[
'scandir'] = fake_scandir.FakeScanDirModule
- def _init_fake_module_functions(self):
+ def _init_fake_module_functions(self) -> None:
# handle patching function imported separately like
# `from os import stat`
# each patched function name has to be looked up separately
@@ -455,7 +586,7 @@ class Patcher:
self._fake_module_functions.setdefault(
fct_name, {})[PATH_MODULE] = module_attr
- def __enter__(self):
+ def __enter__(self) -> 'Patcher':
"""Context manager for usage outside of
fake_filesystem_unittest.TestCase.
Ensure that all patched modules are removed in case of an
@@ -464,120 +595,153 @@ class Patcher:
self.setUp()
return self
- def __exit__(self, exc_type, exc_val, exc_tb):
+ def __exit__(self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType]) -> None:
self.tearDown()
- def _is_fs_module(self, mod, name, module_names):
+ def _is_fs_module(self, mod: ModuleType,
+ name: str,
+ module_names: List[str]) -> bool:
try:
- return (inspect.ismodule(mod) and
- mod.__name__ in module_names
- or inspect.isclass(mod) and
- mod.__module__ in self._class_modules.get(name, []))
- except AttributeError:
- # handle cases where the module has no __name__ or __module__
- # attribute - see #460
- return False
-
- def _is_fs_function(self, fct):
+ # check for __name__ first and ignore the AttributeException
+ # if it does not exist - avoids calling expansive ismodule
+ if mod.__name__ in module_names and inspect.ismodule(mod):
+ return True
+ except Exception:
+ pass
try:
- return ((inspect.isfunction(fct) or
- inspect.isbuiltin(fct)) and
- fct.__name__ in self._fake_module_functions and
+ if (name in self._class_modules and
+ mod.__module__ in self._class_modules[name]):
+ return inspect.isclass(mod)
+ except Exception:
+ # handle AttributeError and any other exception possibly triggered
+ # by side effects of inspect methods
+ pass
+ return False
+
+ def _is_fs_function(self, fct: FunctionType) -> bool:
+ try:
+ # check for __name__ first and ignore the AttributeException
+ # if it does not exist - avoids calling expansive inspect
+ # methods in most cases
+ return (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
+ fct.__name__] and
+ (inspect.isfunction(fct) or inspect.isbuiltin(fct)))
+ except Exception:
+ # handle AttributeError and any other exception possibly triggered
+ # by side effects of inspect methods
return False
- def _def_values(self, item):
+ def _def_values(
+ self,
+ item: FunctionType) -> Iterator[Tuple[FunctionType, int, Any]]:
"""Find default arguments that are file-system functions to be
patched in top-level functions and members of top-level classes."""
# check for module-level functions
- if inspect.isfunction(item):
- if item.__defaults__:
+ try:
+ if item.__defaults__ and inspect.isfunction(item):
for i, d in enumerate(item.__defaults__):
if self._is_fs_function(d):
yield item, i, d
- elif inspect.isclass(item):
- # check for methods in class (nested classes are ignored for now)
- try:
+ except Exception:
+ pass
+ try:
+ if inspect.isclass(item):
+ # check for methods in class
+ # (nested classes are ignored for now)
+ # inspect.getmembers is very expansive!
for m in inspect.getmembers(item,
predicate=inspect.isfunction):
- m = m[1]
- if m.__defaults__:
- for i, d in enumerate(m.__defaults__):
+ f = cast(FunctionType, m[1])
+ if f.__defaults__:
+ for i, d in enumerate(f.__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):
+ yield f, i, d
+ except Exception:
+ # Ignore any exception, examples:
+ # ImportError: No module named '_gdbm'
+ # _DontDoThat() (see #523)
+ pass
+
+ def _find_def_values(
+ self, module_items: ItemsView[str, FunctionType]) -> None:
+ for _, fct in module_items:
+ for f, i, d in self._def_values(fct):
+ self.__class__.FS_DEFARGS.append((f, i, d))
+
+ def _find_modules(self) -> None:
"""Find and cache all modules that import file system modules.
Later, `setUp()` will stub these with the fake file system
modules.
"""
-
module_names = list(self._fake_module_classes.keys()) + [PATH_MODULE]
for name, module in list(sys.modules.items()):
try:
- if (module in self.SKIPMODULES or
- not inspect.ismodule(module) or
- module.__name__.split('.')[0] in self._skipNames):
+ if (self.use_cache and module in self.CACHED_MODULES or
+ not inspect.ismodule(module)):
continue
- except AttributeError:
+ except Exception:
# workaround for some py (part of pytest) versions
# where py.error has no __name__ attribute
# see https://github.com/pytest-dev/py/issues/73
+ # and any other exception triggered by inspect.ismodule
+ if self.use_cache:
+ self.__class__.CACHED_MODULES.add(module)
continue
-
+ skipped = (module in self.SKIPMODULES or
+ any([sn.startswith(module.__name__)
+ for sn in self._skip_names]))
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):
+ modules = {name: mod for name, mod in module_items
+ if self._is_fs_module(mod, name, module_names)}
+
+ if skipped:
+ for name, mod in modules.items():
+ self.__class__.SKIPPED_FS_MODULES.setdefault(
+ name, set()).add((module, mod.__name__))
+ else:
+ for name, mod in modules.items():
+ self.__class__.FS_MODULES.setdefault(name, set()).add(
+ (module, mod.__name__))
+ functions = {name: fct for name, fct in
+ module_items
+ if self._is_fs_function(fct)}
+
+ for name, fct in functions.items():
+ self.__class__.FS_FUNCTIONS.setdefault(
+ (name, fct.__name__, fct.__module__),
+ set()).add(module)
+
+ # find default arguments that are file system functions
+ if self.patch_default_args:
+ self._find_def_values(module_items)
+
+ if self.use_cache:
+ self.__class__.CACHED_MODULES.add(module)
+
+ def _refresh(self) -> None:
"""Renew the fake file system and set the _isStale flag to `False`."""
if self._stubs is not None:
self._stubs.smart_unset_all()
self._stubs = mox3_stubout.StubOutForTesting()
self.fs = fake_filesystem.FakeFilesystem(patcher=self)
+ self.fs.patch_open_code = self.patch_open_code
for name in self._fake_module_classes:
self.fake_modules[name] = self._fake_module_classes[name](self.fs)
+ if hasattr(self.fake_modules[name], 'skip_names'):
+ self.fake_modules[name].skip_names = self._skip_names
self.fake_modules[PATH_MODULE] = self.fake_modules['os'].path
- self.fake_open = fake_filesystem.FakeFileOpen(self.fs)
+ for name in self._unfaked_module_classes:
+ self.unfaked_modules[name] = self._unfaked_module_classes[name]()
self._isStale = False
- def setUp(self, doctester=None):
+ def setUp(self, doctester: Any = None) -> None:
"""Bind the file-related modules to the :py:mod:`pyfakefs` fake
modules real ones. Also bind the fake `file()` and `open()` functions.
"""
@@ -585,56 +749,81 @@ class Patcher:
hasattr(shutil, '_HAS_FCOPYFILE') and
shutil._HAS_FCOPYFILE)
if self.has_fcopy_file:
- shutil._HAS_FCOPYFILE = False
+ shutil._HAS_FCOPYFILE = False # type: ignore[attr-defined]
temp_dir = tempfile.gettempdir()
- self._find_modules()
+ with warnings.catch_warnings():
+ # ignore warnings, see #542 and #614
+ warnings.filterwarnings(
+ 'ignore'
+ )
+ self._find_modules()
+
self._refresh()
if doctester is not None:
doctester.globs = self.replace_globs(doctester.globs)
self.start_patching()
+ linecache.open = self.original_open # type: ignore[attr-defined]
+ tokenize._builtin_open = self.original_open # type: ignore
# the temp directory is assumed to exist at least in `tempfile1`,
# so we create it here for convenience
+ assert self.fs is not None
self.fs.create_dir(temp_dir)
- def start_patching(self):
+ def start_patching(self) -> None:
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.patch_modules()
+ self.patch_functions()
+ self.patch_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:
+ if sys.modules.get(module.__name__) is module:
reload(module)
- def replace_globs(self, globs_):
+ def patch_functions(self) -> None:
+ assert self._stubs is not None
+ for (name, ft_name, ft_mod), modules in self.FS_FUNCTIONS.items():
+ method, mod_name = self._fake_module_functions[ft_name][ft_mod]
+ fake_module = self.fake_modules[mod_name]
+ attr = method.__get__(fake_module, fake_module.__class__)
+ for module in modules:
+ self._stubs.smart_set(module, name, attr)
+
+ def patch_modules(self) -> None:
+ assert self._stubs is not None
+ for name, modules in self.FS_MODULES.items():
+ for module, attr in modules:
+ self._stubs.smart_set(
+ module, name, self.fake_modules[attr])
+ for name, modules in self.SKIPPED_FS_MODULES.items():
+ for module, attr in modules:
+ if attr in self.unfaked_modules:
+ self._stubs.smart_set(
+ module, name, self.unfaked_modules[attr])
+
+ def patch_defaults(self) -> None:
+ for (fct, idx, ft) in self.FS_DEFARGS:
+ method, mod_name = self._fake_module_functions[
+ ft.__name__][ft.__module__]
+ fake_module = self.fake_modules[mod_name]
+ attr = method.__get__(fake_module, fake_module.__class__)
+ new_defaults = []
+ assert fct.__defaults__ is not None
+ for i, d in enumerate(fct.__defaults__):
+ if i == idx:
+ new_defaults.append(attr)
+ else:
+ new_defaults.append(d)
+ fct.__defaults__ = tuple(new_defaults)
+
+ def replace_globs(self, globs_: Dict[str, Any]) -> Dict[str, Any]:
globs = globs_.copy()
if self._isStale:
self._refresh()
@@ -643,35 +832,36 @@ class Patcher:
globs[name] = self._fake_module_classes[name](self.fs)
return globs
- def tearDown(self, doctester=None):
+ def tearDown(self, doctester: Any = None):
"""Clear the fake filesystem bindings created by `setUp()`."""
self.stop_patching()
if self.has_fcopy_file:
- shutil._HAS_FCOPYFILE = True
+ shutil._HAS_FCOPYFILE = True # type: ignore[attr-defined]
reset_ids()
- def stop_patching(self):
+ def stop_patching(self) -> None:
if self._patching:
self._isStale = True
self._patching = False
- self._stubs.smart_unset_all()
+ if self._stubs:
+ self._stubs.smart_unset_all()
self.unset_defaults()
- self._dyn_patcher.cleanup()
- sys.meta_path.pop(0)
+ if self._dyn_patcher:
+ self._dyn_patcher.cleanup()
+ sys.meta_path.pop(0)
- def unset_defaults(self):
- for (fct, idx, ft) in self._def_functions:
+ def unset_defaults(self) -> None:
+ for (fct, idx, ft) in self.FS_DEFARGS:
new_defaults = []
- for i, d in enumerate(fct.__defaults__):
+ for i, d in enumerate(cast(Tuple, fct.__defaults__)):
if i == idx:
new_defaults.append(ft)
else:
new_defaults.append(d)
fct.__defaults__ = tuple(new_defaults)
- self._def_functions = []
- def pause(self):
+ def pause(self) -> None:
"""Pause the patching of the file system modules until `resume` is
called. After that call, all file system calls are executed in the
real file system.
@@ -680,7 +870,7 @@ class Patcher:
"""
self.stop_patching()
- def resume(self):
+ def resume(self) -> None:
"""Resume the patching of the file system modules if `pause` has
been called before. After that call, all file system calls are
executed in the fake file system.
@@ -695,7 +885,7 @@ class Pause:
going out of it's scope.
"""
- def __init__(self, caller):
+ def __init__(self, caller: Union[Patcher, TestCaseMixin, FakeFilesystem]):
"""Initializes the context manager with the fake filesystem.
Args:
@@ -703,8 +893,9 @@ class Pause:
or the pyfakefs test case.
"""
if isinstance(caller, (Patcher, TestCaseMixin)):
- self._fs = caller.fs
- elif isinstance(caller, fake_filesystem.FakeFilesystem):
+ assert caller.fs is not None
+ self._fs: FakeFilesystem = caller.fs
+ elif isinstance(caller, FakeFilesystem):
self._fs = caller
else:
raise ValueError('Invalid argument - should be of type '
@@ -712,25 +903,25 @@ class Pause:
'"fake_filesystem_unittest.TestCase" '
'or "fake_filesystem.FakeFilesystem"')
- def __enter__(self):
+ def __enter__(self) -> FakeFilesystem:
self._fs.pause()
return self._fs
- def __exit__(self, *args):
- return self._fs.resume()
+ def __exit__(self, *args: Any) -> None:
+ self._fs.resume()
-class DynamicPatcher:
+class DynamicPatcher(MetaPathFinder, Loader):
"""A file loader that replaces file system related modules by their
fake implementation if they are loaded after calling `setUpPyfakefs()`.
Implements the protocol needed for import hooks.
"""
- def __init__(self, patcher):
+ def __init__(self, patcher: Patcher) -> None:
self._patcher = patcher
self.sysmodules = {}
self.modules = self._patcher.fake_modules
- self._loaded_module_names = set()
+ self._loaded_module_names: Set[str] = set()
# remove all modules that have to be patched from `sys.modules`,
# otherwise the find_... methods will not be called
@@ -742,9 +933,9 @@ class DynamicPatcher:
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]
+ def cleanup(self) -> None:
+ for module_name in self.sysmodules:
+ sys.modules[module_name] = self.sysmodules[module_name]
for module in self._patcher.modules_to_reload:
if module.__name__ in sys.modules:
reload(module)
@@ -757,7 +948,7 @@ class DynamicPatcher:
if name in sys.modules and name not in reloaded_module_names:
del sys.modules[name]
- def needs_patch(self, name):
+ def needs_patch(self, name: str) -> bool:
"""Check if the module with the given name shall be replaced."""
if name not in self.modules:
self._loaded_module_names.add(name)
@@ -767,12 +958,15 @@ class DynamicPatcher:
return False
return True
- def find_spec(self, fullname, path, target=None):
- """Module finder for Python 3."""
+ def find_spec(self, fullname: str,
+ path: Optional[Sequence[Union[bytes, str]]],
+ target: Optional[ModuleType] = None) -> Optional[ModuleSpec]:
+ """Module finder."""
if self.needs_patch(fullname):
return ModuleSpec(fullname, self)
+ return None
- def load_module(self, fullname):
+ def load_module(self, fullname: str) -> ModuleType:
"""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
index 0c06708..8868558 100644
--- a/pyfakefs/fake_pathlib.py
+++ b/pyfakefs/fake_pathlib.py
@@ -28,23 +28,18 @@ Note: as the implementation is based on FakeFilesystem, all faked classes
(including PurePosixPath, PosixPath, PureWindowsPath and WindowsPath)
get the properties of the underlying fake filesystem.
"""
+import errno
import fnmatch
+import functools
import os
+import pathlib
+from pathlib import PurePath
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 urllib.parse import quote_from_bytes as urlquote_from_bytes
from pyfakefs import fake_scandir
-from pyfakefs.extra_packages import use_scandir, pathlib, pathlib2
+from pyfakefs.extra_packages import use_scandir
from pyfakefs.fake_filesystem import FakeFileOpen, FakeFilesystem
@@ -59,8 +54,8 @@ def init_module(filesystem):
def _wrap_strfunc(strfunc):
@functools.wraps(strfunc)
- def _wrapped(pathobj, *args):
- return strfunc(pathobj.filesystem, str(pathobj), *args)
+ def _wrapped(pathobj, *args, **kwargs):
+ return strfunc(pathobj.filesystem, str(pathobj), *args, **kwargs)
return staticmethod(_wrapped)
@@ -84,12 +79,12 @@ def _wrap_binary_strfunc_reverse(strfunc):
try:
- accessor = pathlib._Accessor
+ accessor = pathlib._Accessor # type: ignore [attr-defined]
except AttributeError:
accessor = object
-class _FakeAccessor(accessor):
+class _FakeAccessor(accessor): # type: ignore [valid-type, misc]
"""Accessor which forwards some of the functions to FakeFilesystem methods.
"""
@@ -100,19 +95,31 @@ class _FakeAccessor(accessor):
listdir = _wrap_strfunc(FakeFilesystem.listdir)
- chmod = _wrap_strfunc(FakeFilesystem.chmod)
-
if use_scandir:
scandir = _wrap_strfunc(fake_scandir.scandir)
+ chmod = _wrap_strfunc(FakeFilesystem.chmod)
+
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):
+ def lchmod(self, pathobj, *args, **kwargs):
"""Raises not implemented for Windows systems."""
raise NotImplementedError("lchmod() not available on this system")
+ def chmod(self, pathobj, *args, **kwargs):
+ if "follow_symlinks" in kwargs:
+ if sys.version_info < (3, 10):
+ raise TypeError("chmod() got an unexpected keyword "
+ "argument 'follow_synlinks'")
+ if (not kwargs["follow_symlinks"] and
+ os.chmod not in os.supports_follow_symlinks):
+ raise NotImplementedError(
+ "`follow_symlinks` for chmod() is not available "
+ "on this system")
+ return pathobj.filesystem.chmod(str(pathobj), *args, **kwargs)
+
mkdir = _wrap_strfunc(FakeFilesystem.makedir)
unlink = _wrap_strfunc(FakeFilesystem.remove)
@@ -130,15 +137,31 @@ class _FakeAccessor(accessor):
FakeFilesystem.create_symlink(fs, file_path, link_target,
create_missing_dirs=False))
+ if (3, 8) <= sys.version_info:
+ link_to = _wrap_binary_strfunc(
+ lambda fs, file_path, link_target:
+ FakeFilesystem.link(fs, file_path, link_target))
+
+ if sys.version_info >= (3, 10):
+ link = _wrap_binary_strfunc(
+ lambda fs, file_path, link_target:
+ FakeFilesystem.link(fs, file_path, link_target))
+
+ # this will use the fake filesystem because os is patched
+ def getcwd(self):
+ return os.getcwd()
+
+ readlink = _wrap_strfunc(FakeFilesystem.readlink)
+
utime = _wrap_strfunc(FakeFilesystem.utime)
_fake_accessor = _FakeAccessor()
-flavour = pathlib._Flavour if pathlib else object
+flavour = pathlib._Flavour # type: ignore [attr-defined]
-class _FakeFlavour(flavour):
+class _FakeFlavour(flavour): # type: ignore [valid-type, misc]
"""Fake Flavour implementation used by PurePath and _Flavour"""
filesystem = None
@@ -443,10 +466,7 @@ class _FakePosixFlavour(_FakeFlavour):
return re.compile(fnmatch.translate(pattern)).fullmatch
-path_module = pathlib.Path if pathlib else object
-
-
-class FakePath(path_module):
+class FakePath(pathlib.Path):
"""Replacement for pathlib.Path. Reimplement some methods to use
fake filesystem. The rest of the methods work as they are, as they will
use the fake accessor.
@@ -459,21 +479,45 @@ class FakePath(path_module):
def __new__(cls, *args, **kwargs):
"""Creates the correct subclass based on OS."""
if cls is FakePathlibModule.Path:
- cls = (FakePathlibModule.WindowsPath if os.name == 'nt'
+ cls = (FakePathlibModule.WindowsPath
+ if cls.filesystem.is_windows_fs
else FakePathlibModule.PosixPath)
- self = cls._from_parts(args, init=True)
+ self = cls._from_parts(args)
return self
- def _path(self):
- """Returns the underlying path string as used by the fake filesystem.
- """
- return str(self)
+ @classmethod
+ def _from_parts(cls, args, init=False): # pylint: disable=unused-argument
+ # Overwritten to call _init to set the fake accessor,
+ # which is not done since Python 3.10
+ self = object.__new__(cls)
+ self._init()
+ drv, root, parts = self._parse_args(args)
+ self._drv = drv
+ self._root = root
+ self._parts = parts
+ return self
+
+ @classmethod
+ def _from_parsed_parts(cls, drv, root, parts):
+ # Overwritten to call _init to set the fake accessor,
+ # which is not done since Python 3.10
+ self = object.__new__(cls)
+ self._init()
+ self._drv = drv
+ self._root = root
+ self._parts = parts
+ return self
def _init(self, template=None):
"""Initializer called from base class."""
self._accessor = _fake_accessor
self._closed = False
+ def _path(self):
+ """Returns the underlying path string as used by the fake filesystem.
+ """
+ return str(self)
+
@classmethod
def cwd(cls):
"""Return a new path pointing to the current working directory
@@ -494,7 +538,7 @@ class FakePath(path_module):
Raises:
OSError: if the path doesn't exist (strict=True or Python < 3.6)
"""
- if sys.version_info >= (3, 6) or pathlib2:
+ if sys.version_info >= (3, 6):
if strict is None:
strict = False
else:
@@ -556,7 +600,7 @@ class FakePath(path_module):
with FakeFileOpen(self.filesystem)(self._path(), mode='wb') as f:
return f.write(view)
- def write_text(self, data, encoding=None, errors=None):
+ def write_text(self, data, encoding=None, errors=None, newline=None):
"""Open the fake file in text mode, write to it, and close
the file.
@@ -564,7 +608,9 @@ class FakePath(path_module):
data: the string to be written
encoding: the encoding used for the string; if not given, the
default locale encoding is used
- errors: ignored
+ errors: (str) Defines how encoding errors are handled.
+ newline: Controls universal newlines, passed to stream object.
+ New in Python 3.10.
Raises:
TypeError: if data is not of type 'str'.
OSError: if the target object is a directory, the path is
@@ -573,10 +619,14 @@ class FakePath(path_module):
if not isinstance(data, str):
raise TypeError('data must be str, not %s' %
data.__class__.__name__)
+ if newline is not None and sys.version_info < (3, 10):
+ raise TypeError("write_text() got an unexpected "
+ "keyword argument 'newline'")
with FakeFileOpen(self.filesystem)(self._path(),
mode='w',
encoding=encoding,
- errors=errors) as f:
+ errors=errors,
+ newline=newline) as f:
return f.write(data)
@classmethod
@@ -584,8 +634,15 @@ class FakePath(path_module):
"""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))
+ home = os.path.expanduser("~")
+ if cls.filesystem.is_windows_fs != (os.name == 'nt'):
+ username = os.path.split(home)[1]
+ if cls.filesystem.is_windows_fs:
+ home = os.path.join('C:', 'Users', username)
+ else:
+ home = os.path.join('home', username)
+ cls.filesystem.create_dir(home)
+ return cls(home.replace(os.sep, cls.filesystem.path_separator))
def samefile(self, other_path):
"""Return whether other_path is the same or not as this file
@@ -649,8 +706,6 @@ class FakePathlibModule:
`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.
@@ -670,18 +725,45 @@ class FakePathlibModule:
"""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.
+ class WindowsPath(FakePath, PureWindowsPath):
+ """A subclass of Path and PureWindowsPath that represents
+ concrete Windows filesystem paths.
+ """
+ __slots__ = ()
+
+ def owner(self):
+ raise NotImplementedError(
+ "Path.owner() is unsupported on this system")
+
+ def group(self):
+ raise NotImplementedError(
+ "Path.group() is unsupported on this system")
+
+ def is_mount(self):
+ raise NotImplementedError(
+ "Path.is_mount() is unsupported on this system")
+
+ class PosixPath(FakePath, PurePosixPath):
+ """A subclass of Path and PurePosixPath that represents
+ concrete non-Windows filesystem paths.
+ """
+ __slots__ = ()
+
+ def owner(self):
+ """Return the current user name. It is assumed that the fake
+ file system was created by the current user.
"""
- __slots__ = ()
- else:
- class PosixPath(FakePath, PurePosixPath):
- """A subclass of Path and PurePosixPath that represents
- concrete non-Windows filesystem paths.
+ import pwd
+
+ return pwd.getpwuid(os.getuid()).pw_name
+
+ def group(self):
+ """Return the current group name. It is assumed that the fake
+ file system was created by the current user.
"""
- __slots__ = ()
+ import grp
+
+ return grp.getgrgid(os.getgid()).gr_name
Path = FakePath
@@ -694,7 +776,7 @@ class FakePathlibPathModule:
"""Patches `pathlib.Path` by passing all calls to FakePathlibModule."""
fake_pathlib = None
- def __init__(self, filesystem):
+ def __init__(self, filesystem=None):
if self.fake_pathlib is None:
self.__class__.fake_pathlib = FakePathlibModule(filesystem)
@@ -703,3 +785,78 @@ class FakePathlibPathModule:
def __getattr__(self, name):
return getattr(self.fake_pathlib.Path, name)
+
+
+class RealPath(pathlib.Path):
+ """Replacement for `pathlib.Path` if it shall not be faked.
+ Needed because `Path` in `pathlib` is always faked, even if `pathlib`
+ itself is not.
+ """
+
+ def __new__(cls, *args, **kwargs):
+ """Creates the correct subclass based on OS."""
+ if cls is RealPathlibModule.Path:
+ cls = (RealPathlibModule.WindowsPath if os.name == 'nt'
+ else RealPathlibModule.PosixPath)
+ self = cls._from_parts(args)
+ return self
+
+
+class RealPathlibModule:
+ """Used to replace `pathlib` for skipped modules.
+ As the original `pathlib` is always patched to use the fake path,
+ we need to provide a version which does not do this.
+ """
+
+ def __init__(self):
+ RealPathlibModule.PureWindowsPath._flavour = pathlib._WindowsFlavour()
+ RealPathlibModule.PurePosixPath._flavour = pathlib._PosixFlavour()
+ self._pathlib_module = pathlib
+
+ class PurePosixPath(PurePath):
+ """A subclass of PurePath, that represents Posix filesystem paths"""
+ __slots__ = ()
+
+ class PureWindowsPath(PurePath):
+ """A subclass of PurePath, that represents Windows filesystem paths"""
+ __slots__ = ()
+
+ if sys.platform == 'win32':
+ class WindowsPath(RealPath, PureWindowsPath):
+ """A subclass of Path and PureWindowsPath that represents
+ concrete Windows filesystem paths.
+ """
+ __slots__ = ()
+ else:
+ class PosixPath(RealPath, PurePosixPath):
+ """A subclass of Path and PurePosixPath that represents
+ concrete non-Windows filesystem paths.
+ """
+ __slots__ = ()
+
+ Path = RealPath
+
+ def __getattr__(self, name):
+ """Forwards any unfaked calls to the standard pathlib module."""
+ return getattr(self._pathlib_module, name)
+
+
+class RealPathlibPathModule:
+ """Patches `pathlib.Path` by passing all calls to RealPathlibModule."""
+ real_pathlib = None
+
+ @classmethod
+ def __instancecheck__(cls, instance):
+ # as we cannot derive from pathlib.Path, we fake
+ # the inheritance to pass isinstance checks - see #666
+ return isinstance(instance, PurePath)
+
+ def __init__(self):
+ if self.real_pathlib is None:
+ self.__class__.real_pathlib = RealPathlibModule()
+
+ def __call__(self, *args, **kwargs):
+ return self.real_pathlib.Path(*args, **kwargs)
+
+ def __getattr__(self, name):
+ return getattr(self.real_pathlib.Path, name)
diff --git a/pyfakefs/fake_scandir.py b/pyfakefs/fake_scandir.py
index 5573b40..8941973 100644
--- a/pyfakefs/fake_scandir.py
+++ b/pyfakefs/fake_scandir.py
@@ -133,14 +133,14 @@ class ScanDirIter:
else:
self.abspath = self.filesystem.absnormpath(path)
self.path = to_string(path)
- contents = self.filesystem.confirmdir(self.abspath).contents
- self.contents_iter = iter(contents)
+ entries = self.filesystem.confirmdir(self.abspath).entries
+ self.entry_iter = iter(entries)
def __iter__(self):
return self
def __next__(self):
- entry = self.contents_iter.__next__()
+ entry = self.entry_iter.__next__()
dir_entry = DirEntry(self.filesystem)
dir_entry.name = entry
dir_entry.path = self.filesystem.joinpaths(self.path,
@@ -241,12 +241,11 @@ def walk(filesystem, top, topdown=True, onerror=None, followlinks=False):
yield top_contents
for directory in top_contents[1]:
- if not followlinks and filesystem.islink(directory):
+ path = filesystem.joinpaths(top_dir, directory)
+ if not followlinks and filesystem.islink(path):
continue
- for contents in do_walk(filesystem.joinpaths(top_dir,
- directory)):
+ for contents in do_walk(path):
yield contents
-
if not topdown:
yield top_contents
diff --git a/pyfakefs/helpers.py b/pyfakefs/helpers.py
index 58de021..ba75d2a 100644
--- a/pyfakefs/helpers.py
+++ b/pyfakefs/helpers.py
@@ -13,43 +13,52 @@
"""Helper classes use for fake file system implementation."""
import io
import locale
+import os
import platform
import stat
import sys
+import time
from copy import copy
from stat import S_IFLNK
-
-import os
+from typing import Union, Optional, Any, AnyStr, overload, cast
IS_PYPY = platform.python_implementation() == 'PyPy'
IS_WIN = sys.platform == 'win32'
IN_DOCKER = os.path.exists('/.dockerenv')
+AnyPath = Union[AnyStr, os.PathLike]
-def is_int_type(val):
+
+def is_int_type(val: Any) -> bool:
"""Return True if `val` is of integer type."""
return isinstance(val, int)
-def is_byte_string(val):
+def is_byte_string(val: Any) -> bool:
"""Return True if `val` is a bytes-like object, False for a unicode
string."""
return not hasattr(val, 'encode')
-def is_unicode_string(val):
+def is_unicode_string(val: Any) -> bool:
"""Return True if `val` is a unicode string, False for a bytes-like
object."""
return hasattr(val, 'encode')
-def make_string_path(dir_name):
- if sys.version_info >= (3, 6):
- dir_name = os.fspath(dir_name)
- return dir_name
+@overload
+def make_string_path(dir_name: AnyStr) -> AnyStr: ...
+
+@overload
+def make_string_path(dir_name: os.PathLike) -> str: ...
-def to_string(path):
+
+def make_string_path(dir_name: AnyPath) -> AnyStr:
+ return cast(AnyStr, os.fspath(dir_name))
+
+
+def to_string(path: Union[AnyStr, Union[str, bytes]]) -> str:
"""Return the string representation of a byte string using the preferred
encoding, or the string itself if path is a str."""
if isinstance(path, bytes):
@@ -57,41 +66,89 @@ def to_string(path):
return path
+def to_bytes(path: Union[AnyStr, Union[str, bytes]]) -> bytes:
+ """Return the bytes representation of a string using the preferred
+ encoding, or the byte string itself if path is a byte string."""
+ if isinstance(path, str):
+ return bytes(path, locale.getpreferredencoding(False))
+ return path
+
+
+def join_strings(s1: AnyStr, s2: AnyStr) -> AnyStr:
+ """This is a bit of a hack to satisfy mypy - may be refactored."""
+ return s1 + s2
+
+
+def real_encoding(encoding: Optional[str]) -> Optional[str]:
+ """Since Python 3.10, the new function ``io.text_encoding`` returns
+ "locale" as the encoding if None is defined. This will be handled
+ as no encoding in pyfakefs."""
+ if sys.version_info >= (3, 10):
+ return encoding if encoding != "locale" else None
+ return encoding
+
+
+def now():
+ return time.time()
+
+
+@overload
+def matching_string(matched: bytes, string: AnyStr) -> bytes: ...
+
+
+@overload
+def matching_string(matched: str, string: AnyStr) -> str: ...
+
+
+@overload
+def matching_string(matched: AnyStr, string: None) -> None: ...
+
+
+def matching_string( # type: ignore[misc]
+ matched: AnyStr, string: Optional[AnyStr]) -> Optional[AnyStr]:
+ """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
+
+
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
+ _stat_float_times: bool = True
+
+ def __init__(self, is_windows: bool, user_id: int, group_id: int,
+ initial_time: Optional[float] = None):
+ self._use_float: Optional[bool] = None
+ self.st_mode: int = 0
+ self.st_ino: Optional[int] = None
+ self.st_dev: int = 0
+ self.st_nlink: int = 0
+ self.st_uid: int = user_id
+ self.st_gid: int = group_id
+ self._st_size: int = 0
+ self.is_windows: bool = is_windows
+ self._st_atime_ns: int = int((initial_time or 0) * 1e9)
+ self._st_mtime_ns: int = self._st_atime_ns
+ self._st_ctime_ns: int = self._st_atime_ns
@property
- def use_float(self):
+ def use_float(self) -> bool:
if self._use_float is None:
return self.stat_float_times()
return self._use_float
@use_float.setter
- def use_float(self, val):
+ def use_float(self, val: bool) -> None:
self._use_float = val
- def __eq__(self, other):
+ def __eq__(self, other: Any) -> bool:
return (
isinstance(other, FakeStatResult) and
self._st_atime_ns == other._st_atime_ns and
@@ -106,10 +163,10 @@ class FakeStatResult:
self.st_mode == other.st_mode
)
- def __ne__(self, other):
+ def __ne__(self, other: Any) -> bool:
return not self == other
- def copy(self):
+ def copy(self) -> "FakeStatResult":
"""Return a copy where the float usage is hard-coded to mimic the
behavior of the real os.stat_result.
"""
@@ -117,7 +174,7 @@ class FakeStatResult:
stat_result.use_float = self.use_float
return stat_result
- def set_from_stat_result(self, stat_result):
+ def set_from_stat_result(self, stat_result: os.stat_result) -> None:
"""Set values from a real os.stat_result.
Note: values that are controlled by the fake filesystem are not set.
This includes st_ino, st_dev and st_nlink.
@@ -131,7 +188,7 @@ class FakeStatResult:
self._st_ctime_ns = stat_result.st_ctime_ns
@classmethod
- def stat_float_times(cls, newvalue=None):
+ def stat_float_times(cls, newvalue: Optional[bool] = None) -> bool:
"""Determine whether a file's time stamps are reported as floats
or ints.
@@ -147,50 +204,50 @@ class FakeStatResult:
return cls._stat_float_times
@property
- def st_ctime(self):
+ def st_ctime(self) -> Union[int, float]:
"""Return the creation time in seconds."""
ctime = self._st_ctime_ns / 1e9
return ctime if self.use_float else int(ctime)
+ @st_ctime.setter
+ def st_ctime(self, val: Union[int, float]) -> None:
+ """Set the creation time in seconds."""
+ self._st_ctime_ns = int(val * 1e9)
+
@property
- def st_atime(self):
+ def st_atime(self) -> Union[int, float]:
"""Return the access time in seconds."""
atime = self._st_atime_ns / 1e9
return atime if self.use_float else int(atime)
+ @st_atime.setter
+ def st_atime(self, val: Union[int, float]) -> None:
+ """Set the access time in seconds."""
+ self._st_atime_ns = int(val * 1e9)
+
@property
- def st_mtime(self):
+ def st_mtime(self) -> Union[int, float]:
"""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):
+ def st_mtime(self, val: Union[int, float]) -> None:
"""Set the modification time in seconds."""
self._st_mtime_ns = int(val * 1e9)
@property
- def st_size(self):
+ def st_size(self) -> int:
if self.st_mode & S_IFLNK == S_IFLNK and self.is_windows:
return 0
return self._st_size
@st_size.setter
- def st_size(self, val):
+ def st_size(self, val: int) -> None:
self._st_size = val
@property
- def st_file_attributes(self):
+ def st_file_attributes(self) -> int:
if not self.is_windows:
raise AttributeError("module 'os.stat_result' "
"has no attribute 'st_file_attributes'")
@@ -207,15 +264,15 @@ class FakeStatResult:
return mode
@property
- def st_reparse_tag(self):
+ def st_reparse_tag(self) -> int:
if not self.is_windows or sys.version_info < (3, 8):
raise AttributeError("module 'os.stat_result' "
"has no attribute 'st_reparse_tag'")
if self.st_mode & stat.S_IFLNK:
- return stat.IO_REPARSE_TAG_SYMLINK
+ return stat.IO_REPARSE_TAG_SYMLINK # type: ignore[attr-defined]
return 0
- def __getitem__(self, item):
+ def __getitem__(self, item: int) -> Optional[int]:
"""Implement item access to mimic `os.stat_result` behavior."""
import stat
@@ -243,190 +300,59 @@ class FakeStatResult:
raise ValueError('Invalid item')
@property
- def st_atime_ns(self):
+ def st_atime_ns(self) -> int:
"""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):
+ def st_atime_ns(self, val: int) -> None:
"""Set the access time in nanoseconds."""
self._st_atime_ns = val
+ @property
+ def st_mtime_ns(self) -> int:
+ """Return the modification time in nanoseconds."""
+ return self._st_mtime_ns
+
@st_mtime_ns.setter
- def st_mtime_ns(self, val):
+ def st_mtime_ns(self, val: int) -> None:
"""Set the modification time of the fake file in nanoseconds."""
self._st_mtime_ns = val
+ @property
+ def st_ctime_ns(self) -> int:
+ """Return the creation time in nanoseconds."""
+ return self._st_ctime_ns
+
@st_ctime_ns.setter
- def st_ctime_ns(self, val):
+ def st_ctime_ns(self, val: int) -> None:
"""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.
+class BinaryBufferIO(io.BytesIO):
+ """Stream class that handles byte contents for files."""
+
+ def __init__(self, contents: Optional[bytes]):
+ super().__init__(contents or b'')
+
+ def putvalue(self, value: bytes) -> None:
+ self.write(value)
+
+
+class TextBufferIO(io.TextIOWrapper):
+ """Stream class that handles Python string contents for files.
"""
- def __init__(self, contents=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
+ def __init__(self, contents: Optional[bytes] = None,
+ newline: Optional[str] = None,
+ encoding: Optional[str] = None,
+ errors: str = 'strict'):
+ self._bytestream = io.BytesIO(contents or b'')
+ super().__init__(self._bytestream, encoding, errors, newline)
+
+ def getvalue(self) -> bytes:
+ return self._bytestream.getvalue()
+
+ def putvalue(self, value: bytes) -> None:
+ self._bytestream.write(value)
diff --git a/pyfakefs/patched_packages.py b/pyfakefs/patched_packages.py
new file mode 100644
index 0000000..749b795
--- /dev/null
+++ b/pyfakefs/patched_packages.py
@@ -0,0 +1,135 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Provides patches for some commonly used modules that enable them to work
+with pyfakefs.
+"""
+import sys
+
+try:
+ import pandas.io.parsers as parsers
+except ImportError:
+ parsers = None
+
+try:
+ import xlrd
+except ImportError:
+ xlrd = None
+
+try:
+ from django.core.files import locks
+except ImportError:
+ locks = None
+
+
+def get_modules_to_patch():
+ modules_to_patch = {}
+ if xlrd is not None:
+ modules_to_patch['xlrd'] = XLRDModule
+ if locks is not None:
+ modules_to_patch['django.core.files.locks'] = FakeLocks
+ return modules_to_patch
+
+
+def get_classes_to_patch():
+ classes_to_patch = {}
+ if parsers is not None:
+ classes_to_patch[
+ 'TextFileReader'
+ ] = 'pandas.io.parsers'
+ return classes_to_patch
+
+
+def get_fake_module_classes():
+ fake_module_classes = {}
+ if parsers is not None:
+ fake_module_classes[
+ 'TextFileReader'
+ ] = FakeTextFileReader
+ return fake_module_classes
+
+
+if xlrd is not None:
+ class XLRDModule:
+ """Patches the xlrd module, which is used as the default Excel file
+ reader by pandas. Disables using memory mapped files, which are
+ implemented platform-specific on OS level."""
+
+ def __init__(self, _):
+ self._xlrd_module = xlrd
+
+ def open_workbook(self, filename=None,
+ logfile=sys.stdout,
+ verbosity=0,
+ use_mmap=False,
+ file_contents=None,
+ encoding_override=None,
+ formatting_info=False,
+ on_demand=False,
+ ragged_rows=False):
+ return self._xlrd_module.open_workbook(
+ filename, logfile, verbosity, False, file_contents,
+ encoding_override, formatting_info, on_demand, ragged_rows)
+
+ def __getattr__(self, name):
+ """Forwards any unfaked calls to the standard xlrd module."""
+ return getattr(self._xlrd_module, name)
+
+if parsers is not None:
+ # we currently need to add fake modules for both the parser module and
+ # the contained text reader - maybe this can be simplified
+
+ class FakeTextFileReader:
+ fake_parsers = None
+
+ def __init__(self, filesystem):
+ if self.fake_parsers is None:
+ self.__class__.fake_parsers = ParsersModule(filesystem)
+
+ def __call__(self, *args, **kwargs):
+ return self.fake_parsers.TextFileReader(*args, **kwargs)
+
+ def __getattr__(self, name):
+ return getattr(self.fake_parsers.TextFileReader, name)
+
+ class ParsersModule:
+ def __init__(self, _):
+ self._parsers_module = parsers
+
+ class TextFileReader(parsers.TextFileReader):
+ def __init__(self, *args, **kwargs):
+ kwargs['engine'] = 'python'
+ super().__init__(*args, **kwargs)
+
+ def __getattr__(self, name):
+ """Forwards any unfaked calls to the standard xlrd module."""
+ return getattr(self._parsers_module, name)
+
+if locks is not None:
+ class FakeLocks:
+ """django.core.files.locks uses low level OS functions, fake it."""
+ _locks_module = locks
+
+ def __init__(self, _):
+ pass
+
+ @staticmethod
+ def lock(f, flags):
+ return True
+
+ @staticmethod
+ def unlock(f):
+ return True
+
+ def __getattr__(self, name):
+ return getattr(self._locks_module, name)
diff --git a/pyfakefs/pytest_plugin.py b/pyfakefs/pytest_plugin.py
index 93702f2..3d3306c 100644
--- a/pyfakefs/pytest_plugin.py
+++ b/pyfakefs/pytest_plugin.py
@@ -8,25 +8,16 @@ 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 _pytest
import py
import pytest
from pyfakefs.fake_filesystem_unittest import Patcher
+Patcher.SKIPMODULES.add(py)
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)
+if hasattr(_pytest, "pathlib"):
+ Patcher.SKIPMODULES.add(_pytest.pathlib)
@pytest.fixture
@@ -38,6 +29,5 @@ def fs(request):
else:
patcher = Patcher()
patcher.setUp()
- tokenize._builtin_open = patcher.original_open
yield patcher.fs
patcher.tearDown()
diff --git a/pyfakefs/pytest_tests/conftest.py b/pyfakefs/pytest_tests/conftest.py
index 92b49ff..b6918c7 100644
--- a/pyfakefs/pytest_tests/conftest.py
+++ b/pyfakefs/pytest_tests/conftest.py
@@ -15,20 +15,13 @@
# 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)
+# import the fs fixture to be visible if pyfakefs is not installed
+from pyfakefs.pytest_plugin import fs # noqa: F401
-from pyfakefs.fake_filesystem_unittest import Patcher # noqa: E402
from pyfakefs.pytest_tests import example # noqa: E402
@@ -37,7 +30,11 @@ 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()
+
+
+@pytest.fixture
+def fake_filesystem(fs): # noqa: F811
+ """Shows how to use an alias for the fs fixture."""
+ yield fs
diff --git a/pyfakefs/pytest_tests/example.py b/pyfakefs/pytest_tests/example.py
index 62d619a..c5b530b 100644
--- a/pyfakefs/pytest_tests/example.py
+++ b/pyfakefs/pytest_tests/example.py
@@ -12,14 +12,6 @@
# Used as SUT for pytest_fixture_test.py
-try:
- from pathlib2 import Path
+from pathlib import Path
- EXAMPLE_FILE = Path('/test') / 'file'
-except ImportError:
- try:
- from pathlib import Path
-
- EXAMPLE_FILE = Path('/test') / 'file'
- except ImportError:
- EXAMPLE_FILE = None
+EXAMPLE_FILE = Path('/test') / 'file'
diff --git a/pyfakefs/pytest_tests/pytest_check_failed_plugin_test.py b/pyfakefs/pytest_tests/pytest_check_failed_plugin_test.py
index aebf948..42ff87e 100644
--- a/pyfakefs/pytest_tests/pytest_check_failed_plugin_test.py
+++ b/pyfakefs/pytest_tests/pytest_check_failed_plugin_test.py
@@ -1,9 +1,14 @@
"""Tests that a failed pytest properly displays the call stack.
-Uses the output from running pytest with pytest_plugin_failing_test.py.
+Uses the output from running pytest with pytest_plugin_failing_helper.py.
Regression test for #381.
"""
+import os
+import pytest
+
+@pytest.mark.skipif(not os.path.exists('testresult.txt'),
+ reason='Only run in CI tests')
def test_failed_testresult_stacktrace():
with open('testresult.txt') as f:
contents = f.read()
diff --git a/pyfakefs/pytest_tests/pytest_doctest_test.py b/pyfakefs/pytest_tests/pytest_doctest_test.py
index 1649eb5..4a36788 100644
--- a/pyfakefs/pytest_tests/pytest_doctest_test.py
+++ b/pyfakefs/pytest_tests/pytest_doctest_test.py
@@ -4,7 +4,7 @@ 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
+ $ pytest --doctest-modules pytest_doctest_test.py
Add `-s` option to enable print statements.
"""
diff --git a/pyfakefs/pytest_tests/pytest_fixture_param_test.py b/pyfakefs/pytest_tests/pytest_fixture_param_test.py
index 4ff6b46..aa6a44d 100644
--- a/pyfakefs/pytest_tests/pytest_fixture_param_test.py
+++ b/pyfakefs/pytest_tests/pytest_fixture_param_test.py
@@ -12,6 +12,7 @@
# Example for a test using a custom pytest fixture with an argument to Patcher
# Needs Python >= 3.6
+import os
import pytest
@@ -41,3 +42,11 @@ def check_that_example_file_is_in_fake_fs():
assert file.read() == 'stuff here'
assert example.EXAMPLE_FILE.read_text() == 'stuff here'
assert example.EXAMPLE_FILE.is_file()
+
+
+def test_twice_chdir(fs):
+ # regression test for #530 - make sure that
+ # alternative path separators are correctly handled under Windows
+ fs.create_dir("/absolute/path/to/directory")
+ os.chdir("/absolute/path/to/directory")
+ os.chdir("/absolute/path/to/directory")
diff --git a/pyfakefs/pytest_tests/pytest_plugin_failing_test.py b/pyfakefs/pytest_tests/pytest_plugin_failing_helper.py
index dd4bab8..dd4bab8 100644
--- a/pyfakefs/pytest_tests/pytest_plugin_failing_test.py
+++ b/pyfakefs/pytest_tests/pytest_plugin_failing_helper.py
diff --git a/pyfakefs/pytest_tests/pytest_plugin_test.py b/pyfakefs/pytest_tests/pytest_plugin_test.py
index 61f8cdc..5632ff6 100644
--- a/pyfakefs/pytest_tests/pytest_plugin_test.py
+++ b/pyfakefs/pytest_tests/pytest_plugin_test.py
@@ -10,6 +10,19 @@ def test_fs_fixture(fs):
assert os.path.exists('/var/data/xx1.txt')
+def test_fs_fixture_alias(fake_filesystem):
+ fake_filesystem.create_file('/var/data/xx1.txt')
+ assert os.path.exists('/var/data/xx1.txt')
+
+
+def test_both_fixtures(fs, fake_filesystem):
+ fake_filesystem.create_file('/var/data/xx1.txt')
+ fs.create_file('/var/data/xx2.txt')
+ assert os.path.exists('/var/data/xx1.txt')
+ assert os.path.exists('/var/data/xx2.txt')
+ assert fs == fake_filesystem
+
+
def test_pause_resume(fs):
fake_temp_file = tempfile.NamedTemporaryFile()
assert fs.exists(fake_temp_file.name)
diff --git a/pyfakefs/tests/all_tests.py b/pyfakefs/tests/all_tests.py
index f91ca24..331a3d1 100644
--- a/pyfakefs/tests/all_tests.py
+++ b/pyfakefs/tests/all_tests.py
@@ -18,21 +18,22 @@ 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
+from pyfakefs.tests import (
+ dynamic_patch_test,
+ fake_stat_time_test,
+ example_test,
+ fake_filesystem_glob_test,
+ fake_filesystem_shutil_test,
+ fake_filesystem_test,
+ fake_filesystem_unittest_test,
+ fake_filesystem_vs_real_test,
+ fake_open_test,
+ fake_os_test,
+ fake_pathlib_test,
+ fake_tempfile_test,
+ patched_packages_test,
+ mox3_stubout_test
+)
class AllTests(unittest.TestSuite):
@@ -53,11 +54,9 @@ class AllTests(unittest.TestSuite):
loader.loadTestsFromModule(example_test),
loader.loadTestsFromModule(mox3_stubout_test),
loader.loadTestsFromModule(dynamic_patch_test),
+ loader.loadTestsFromModule(fake_pathlib_test),
+ loader.loadTestsFromModule(patched_packages_test)
])
- if pathlib:
- self.addTests([
- loader.loadTestsFromModule(fake_pathlib_test)
- ])
return self
diff --git a/pyfakefs/tests/all_tests_without_extra_packages.py b/pyfakefs/tests/all_tests_without_extra_packages.py
index 28ecfad..3d65e9d 100644
--- a/pyfakefs/tests/all_tests_without_extra_packages.py
+++ b/pyfakefs/tests/all_tests_without_extra_packages.py
@@ -11,21 +11,13 @@
# limitations under the License.
"""A test suite that runs all tests for pyfakefs at once.
-Excludes tests using external pathlib2 and scandir packages."""
+Excludes tests using external scandir package."""
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:
diff --git a/pyfakefs/tests/dynamic_patch_test.py b/pyfakefs/tests/dynamic_patch_test.py
index f81c843..132dd0a 100644
--- a/pyfakefs/tests/dynamic_patch_test.py
+++ b/pyfakefs/tests/dynamic_patch_test.py
@@ -13,10 +13,10 @@
"""
Tests for patching modules loaded after `setUpPyfakefs()`.
"""
+import pathlib
import unittest
from pyfakefs import fake_filesystem_unittest
-from pyfakefs.extra_packages import pathlib
class TestPyfakefsUnittestBase(fake_filesystem_unittest.TestCase):
@@ -56,7 +56,6 @@ class DynamicImportPatchTest(TestPyfakefsUnittestBase):
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)
diff --git a/pyfakefs/tests/example.py b/pyfakefs/tests/example.py
index 5793cdd..09d7a3d 100644
--- a/pyfakefs/tests/example.py
+++ b/pyfakefs/tests/example.py
@@ -121,7 +121,7 @@ def get_glob(glob_path):
>>> import sys
>>> if sys.platform.startswith('win'):
... # Windows style path
- ... file_names == [r'\test\file1.txt', r'\test\file2.txt']
+ ... file_names == [r'/test\file1.txt', r'/test\file2.txt']
... else:
... # UNIX style path
... file_names == ['/test/file1.txt', '/test/file2.txt']
diff --git a/pyfakefs/tests/example_test.py b/pyfakefs/tests/example_test.py
index b44a695..91225d0 100644
--- a/pyfakefs/tests/example_test.py
+++ b/pyfakefs/tests/example_test.py
@@ -113,7 +113,7 @@ class TestExample(fake_filesystem_unittest.TestCase): # pylint: disable=R0904
matching_paths = sorted(example.get_glob('/test/dir1/dir*'))
if is_windows:
self.assertEqual(matching_paths,
- [r'\test\dir1\dir2a', r'\test\dir1\dir2b'])
+ [r'/test/dir1\dir2a', r'/test/dir1\dir2b'])
else:
self.assertEqual(matching_paths,
['/test/dir1/dir2a', '/test/dir1/dir2b'])
diff --git a/pyfakefs/tests/fake_filesystem_glob_test.py b/pyfakefs/tests/fake_filesystem_glob_test.py
index 87ccbe1..7432fa8 100644
--- a/pyfakefs/tests/fake_filesystem_glob_test.py
+++ b/pyfakefs/tests/fake_filesystem_glob_test.py
@@ -35,7 +35,7 @@ class FakeGlobUnitTest(fake_filesystem_unittest.TestCase):
self.assertEqual(glob.glob(''), [])
def test_glob_star(self):
- basedir = os.sep + 'xyzzy'
+ basedir = '/xyzzy'
self.assertEqual([os.path.join(basedir, 'subdir'),
os.path.join(basedir, 'subdir2'),
os.path.join(basedir, 'subfile')],
@@ -46,7 +46,7 @@ class FakeGlobUnitTest(fake_filesystem_unittest.TestCase):
self.assertEqual(['/xyzzy/subfile'], glob.glob('/xyzzy/subfile'))
def test_glob_question(self):
- basedir = os.sep + 'xyzzy'
+ basedir = '/xyzzy'
self.assertEqual([os.path.join(basedir, 'subdir'),
os.path.join(basedir, 'subdir2'),
os.path.join(basedir, 'subfile')],
@@ -60,7 +60,7 @@ class FakeGlobUnitTest(fake_filesystem_unittest.TestCase):
self.assertEqual([], glob.glob('nonexistent'))
def test_magic_dir(self):
- self.assertEqual([os.sep + '[Temp]'], glob.glob('/*emp*'))
+ self.assertEqual(['/[Temp]'], glob.glob('/*emp*'))
def test_glob1(self):
self.assertEqual(['[Temp]'], glob.glob1('/', '*Tem*'))
diff --git a/pyfakefs/tests/fake_filesystem_shutil_test.py b/pyfakefs/tests/fake_filesystem_shutil_test.py
index bfa167d..5861e63 100644
--- a/pyfakefs/tests/fake_filesystem_shutil_test.py
+++ b/pyfakefs/tests/fake_filesystem_shutil_test.py
@@ -24,7 +24,7 @@ import tempfile
import unittest
from pyfakefs import fake_filesystem_unittest
-from pyfakefs.fake_filesystem import is_root
+from pyfakefs.fake_filesystem import is_root, set_uid, USER_ID
from pyfakefs.tests.test_utils import RealFsTestMixin
is_windows = sys.platform == 'win32'
@@ -36,7 +36,10 @@ class RealFsTestCase(fake_filesystem_unittest.TestCase, RealFsTestMixin):
RealFsTestMixin.__init__(self)
def setUp(self):
+ RealFsTestMixin.setUp(self)
self.cwd = os.getcwd()
+ self.uid = USER_ID
+ set_uid(1000)
if not self.use_real_fs():
self.setUpPyfakefs()
self.filesystem = self.fs
@@ -46,10 +49,8 @@ class RealFsTestCase(fake_filesystem_unittest.TestCase, RealFsTestMixin):
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)
+ set_uid(self.uid)
+ RealFsTestMixin.tearDown(self)
@property
def is_windows_fs(self):
@@ -59,6 +60,22 @@ class RealFsTestCase(fake_filesystem_unittest.TestCase, RealFsTestMixin):
class FakeShutilModuleTest(RealFsTestCase):
+ @unittest.skipIf(is_windows, 'Posix specific behavior')
+ def test_catch_permission_error(self):
+ root_path = self.make_path('rootpath')
+ self.create_dir(root_path)
+ dir1_path = self.os.path.join(root_path, 'dir1')
+ dir2_path = self.os.path.join(root_path, 'dir2')
+ self.create_dir(dir1_path)
+ self.os.chmod(dir1_path, 0o555) # remove write permissions
+ self.create_dir(dir2_path)
+ old_file_path = self.os.path.join(dir2_path, 'f1.txt')
+ new_file_path = self.os.path.join(dir1_path, 'f1.txt')
+ self.create_file(old_file_path)
+
+ with self.assertRaises(PermissionError):
+ shutil.move(old_file_path, new_file_path)
+
def test_rmtree(self):
directory = self.make_path('xyzzy')
dir_path = os.path.join(directory, 'subdir')
@@ -90,7 +107,8 @@ class FakeShutilModuleTest(RealFsTestCase):
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)
+ with self.assertRaises(OSError):
+ shutil.rmtree(dir_path)
self.assertTrue(os.path.exists(file_path))
self.os.chmod(file_path, 0o666)
@@ -103,7 +121,8 @@ class FakeShutilModuleTest(RealFsTestCase):
self.create_file(file_path)
self.os.chmod(dir_path, 0o555)
if not is_root():
- self.assertRaises(OSError, shutil.rmtree, dir_path)
+ with self.assertRaises(OSError):
+ shutil.rmtree(dir_path)
self.assertTrue(os.path.exists(file_path))
self.os.chmod(dir_path, 0o777)
else:
@@ -129,12 +148,14 @@ class FakeShutilModuleTest(RealFsTestCase):
file_path = os.path.join(dir_path, 'baz')
self.create_file(file_path)
with open(file_path):
- self.assertRaises(OSError, shutil.rmtree, dir_path)
+ with 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)
+ with self.assertRaises(OSError):
+ shutil.rmtree(directory)
try:
shutil.rmtree(directory, ignore_errors=True)
except OSError:
@@ -261,10 +282,8 @@ class FakeShutilModuleTest(RealFsTestCase):
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)
+ with self.assertRaises(OSError):
+ shutil.copytree(src_file, dst_directory)
def test_move_file_in_same_filesystem(self):
self.skip_real_fs()
@@ -378,8 +397,8 @@ class FakeCopyFileTest(RealFsTestCase):
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)
+ with 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()
@@ -389,8 +408,8 @@ class FakeCopyFileTest(RealFsTestCase):
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)
+ with self.assertRaises(shutil.Error):
+ shutil.copyfile(src_file, dst_file)
def test_succeeds_if_dest_exists_and_is_writable(self):
src_file = self.make_path('xyzzy')
@@ -422,7 +441,8 @@ class FakeCopyFileTest(RealFsTestCase):
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)
+ with self.assertRaises(OSError):
+ shutil.copyfile(src_file, dst_file)
os.chmod(dst_file, 0o666)
@@ -439,7 +459,8 @@ class FakeCopyFileTest(RealFsTestCase):
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)
+ with self.assertRaises(OSError):
+ shutil.copyfile(src_file, dst_file)
else:
shutil.copyfile(src_file, dst_file)
self.assertTrue(os.path.exists(dst_file))
@@ -449,7 +470,8 @@ class FakeCopyFileTest(RealFsTestCase):
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)
+ with self.assertRaises(OSError):
+ shutil.copyfile(src_file, dst_file)
@unittest.skipIf(is_windows, 'Posix specific behavior')
def test_raises_if_src_not_readable(self):
@@ -461,7 +483,8 @@ class FakeCopyFileTest(RealFsTestCase):
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)
+ with self.assertRaises(OSError):
+ shutil.copyfile(src_file, dst_file)
else:
shutil.copyfile(src_file, dst_file)
self.assertTrue(os.path.exists(dst_file))
@@ -472,10 +495,8 @@ class FakeCopyFileTest(RealFsTestCase):
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)
+ with self.assertRaises(OSError):
+ shutil.copyfile(src_file, dst_file)
def test_raises_if_dest_is_a_directory(self):
src_file = self.make_path('xyzzy')
@@ -485,10 +506,8 @@ class FakeCopyFileTest(RealFsTestCase):
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)
+ with self.assertRaises(OSError):
+ shutil.copyfile(src_file, dst_dir)
def test_moving_dir_into_dir(self):
# regression test for #515
diff --git a/pyfakefs/tests/fake_filesystem_test.py b/pyfakefs/tests/fake_filesystem_test.py
index 2923366..f131d2f 100644
--- a/pyfakefs/tests/fake_filesystem_test.py
+++ b/pyfakefs/tests/fake_filesystem_test.py
@@ -20,19 +20,18 @@ 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
+from pyfakefs.tests.test_utils import TestCase, RealFsTestCase, time_mock
class FakeDirectoryUnitTest(TestCase):
def setUp(self):
- self.orig_time = time.time
- time.time = DummyTime(10, 1)
+ self.time = time_mock(10, 1)
+ self.time.start()
self.filesystem = fake_filesystem.FakeFilesystem(path_separator='/')
self.os = fake_filesystem.FakeOsModule(self.filesystem)
self.fake_file = fake_filesystem.FakeFile(
@@ -41,17 +40,18 @@ class FakeDirectoryUnitTest(TestCase):
'somedir', filesystem=self.filesystem)
def tearDown(self):
- time.time = self.orig_time
+ self.time.stop()
def test_new_file_and_directory(self):
self.assertTrue(stat.S_IFREG & self.fake_file.st_mode)
self.assertTrue(stat.S_IFDIR & self.fake_dir.st_mode)
- self.assertEqual({}, self.fake_dir.contents)
- self.assertEqual(10, self.fake_file.st_ctime)
+ self.assertEqual({}, self.fake_dir.entries)
+ self.assertEqual(12, 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)
+ self.assertEqual({'foobar': self.fake_file},
+ self.fake_dir.entries)
def test_get_entry(self):
self.fake_dir.add_entry(self.fake_file)
@@ -89,20 +89,17 @@ class FakeDirectoryUnitTest(TestCase):
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')
+ with self.assertRaises(KeyError):
+ self.fake_dir.get_entry('foobar')
def test_should_throw_if_set_size_is_not_integer(self):
- def set_size():
+ with self.raises_os_error(errno.ENOSPC):
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():
+ with self.raises_os_error(errno.ENOSPC):
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)
@@ -122,18 +119,18 @@ class FakeDirectoryUnitTest(TestCase):
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')
+ with self.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')
+ with self.raises_os_error(errno.EISDIR):
+ self.fake_dir.set_contents('a')
def test_pads_with_nullbytes_if_size_is_greater_than_current_size(self):
self.fake_file.size = 13
self.assertEqual('dummy_file\0\0\0', self.fake_file.contents)
def test_set_m_time(self):
- self.assertEqual(10, self.fake_file.st_mtime)
+ self.assertEqual(12, 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
@@ -179,12 +176,12 @@ class SetLargeFileSizeTest(TestCase):
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)
+ with self.raises_os_error(errno.ENOSPC):
+ self.fake_file.set_large_file_size(0.1)
def test_should_throw_if_size_is_negative(self):
- self.assert_raises_os_error(errno.ENOSPC,
- self.fake_file.set_large_file_size, -1)
+ with self.raises_os_error(errno.ENOSPC):
+ self.fake_file.set_large_file_size(-1)
def test_sets_content_none_if_size_is_non_negative_integer(self):
self.fake_file.set_large_file_size(1000000000)
@@ -265,10 +262,11 @@ class FakeFilesystemUnitTest(TestCase):
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)
+ self.assertEqual({}, self.filesystem.root.entries)
def test_none_raises_type_error(self):
- self.assertRaises(TypeError, self.filesystem.exists, None)
+ with self.assertRaises(TypeError):
+ self.filesystem.exists(None)
def test_empty_string_does_not_exist(self):
self.assertFalse(self.filesystem.exists(''))
@@ -292,7 +290,7 @@ class FakeFilesystemUnitTest(TestCase):
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)
+ self.filesystem.root.entries)
def test_exists_added_file(self):
self.filesystem.add_object(self.root_name, self.fake_file)
@@ -338,18 +336,18 @@ class FakeFilesystemUnitTest(TestCase):
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')
+ with self.raises_os_error(errno.ENOENT):
+ self.filesystem.get_object('some_bogus_filename')
def test_remove_object_from_root(self):
self.filesystem.add_object(self.root_name, self.fake_file)
self.filesystem.remove_object(self.fake_file.name)
- self.assert_raises_os_error(
- errno.ENOENT, self.filesystem.get_object, self.fake_file.name)
+ with self.raises_os_error(errno.ENOENT):
+ self.filesystem.get_object(self.fake_file.name)
def test_remove_nonexisten_object_from_root_error(self):
- self.assert_raises_os_error(
- errno.ENOENT, self.filesystem.remove_object, 'some_bogus_filename')
+ with self.raises_os_error(errno.ENOENT):
+ self.filesystem.remove_object('some_bogus_filename')
def test_exists_removed_file(self):
self.filesystem.add_object(self.root_name, self.fake_file)
@@ -361,21 +359,19 @@ class FakeFilesystemUnitTest(TestCase):
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)
+ self.filesystem.root.get_entry(self.fake_child.name).entries)
def test_add_object_to_regular_file_error_posix(self):
self.filesystem.is_windows_fs = False
self.filesystem.add_object(self.root_name, self.fake_file)
- self.assert_raises_os_error(errno.ENOTDIR,
- self.filesystem.add_object,
- self.fake_file.name, self.fake_file)
+ with self.raises_os_error(errno.ENOTDIR):
+ self.filesystem.add_object(self.fake_file.name, self.fake_file)
def test_add_object_to_regular_file_error_windows(self):
self.filesystem.is_windows_fs = True
self.filesystem.add_object(self.root_name, self.fake_file)
- self.assert_raises_os_error(errno.ENOENT,
- self.filesystem.add_object,
- self.fake_file.name, self.fake_file)
+ with self.raises_os_error(errno.ENOENT):
+ self.filesystem.add_object(self.fake_file.name, self.fake_file)
def test_exists_file_added_to_child(self):
self.filesystem.add_object(self.root_name, self.fake_child)
@@ -395,10 +391,9 @@ class FakeFilesystemUnitTest(TestCase):
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'))
+ with self.raises_os_error(errno.ENOENT):
+ self.filesystem.get_object(self.filesystem.joinpaths(
+ self.fake_child.name, 'some_bogus_filename'))
def test_remove_object_from_child(self):
self.filesystem.add_object(self.root_name, self.fake_child)
@@ -406,23 +401,23 @@ class FakeFilesystemUnitTest(TestCase):
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)
+ with self.raises_os_error(errno.ENOENT):
+ self.filesystem.get_object(target_path)
def test_remove_object_from_child_error(self):
self.filesystem.add_object(self.root_name, self.fake_child)
- self.assert_raises_os_error(
- errno.ENOENT, self.filesystem.remove_object,
- self.filesystem.joinpaths(self.fake_child.name,
- 'some_bogus_filename'))
+ with self.raises_os_error(errno.ENOENT):
+ self.filesystem.remove_object(
+ self.filesystem.joinpaths(self.fake_child.name,
+ 'some_bogus_filename'))
def test_remove_object_from_non_directory_error(self):
self.filesystem.add_object(self.root_name, self.fake_file)
- 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'))
+ with self.raises_os_error(errno.ENOTDIR):
+ self.filesystem.remove_object(
+ self.filesystem.joinpaths(
+ '%s' % self.fake_file.name,
+ 'file_does_not_matter_since_parent_not_a_directory'))
def test_exists_file_removed_from_child(self):
self.filesystem.add_object(self.root_name, self.fake_child)
@@ -439,13 +434,15 @@ class FakeFilesystemUnitTest(TestCase):
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)
+ with self.assertRaises(OSError):
+ self.filesystem.get_object(grandchild_file)
self.filesystem.add_object(grandchild_directory, self.fake_file)
self.assertEqual(self.fake_file,
self.filesystem.get_object(grandchild_file))
self.assertTrue(self.filesystem.exists(grandchild_file))
self.filesystem.remove_object(grandchild_file)
- self.assertRaises(OSError, self.filesystem.get_object, grandchild_file)
+ with self.assertRaises(OSError):
+ self.filesystem.get_object(grandchild_file)
self.assertFalse(self.filesystem.exists(grandchild_file))
def test_create_directory_in_root_directory(self):
@@ -458,8 +455,8 @@ class FakeFilesystemUnitTest(TestCase):
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)
+ with self.raises_os_error(errno.EEXIST):
+ self.filesystem.create_dir(path)
def test_create_directory(self):
path = 'foo/bar/baz'
@@ -478,8 +475,8 @@ class FakeFilesystemUnitTest(TestCase):
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)
+ with self.raises_os_error(errno.EEXIST):
+ self.filesystem.create_dir(path)
def test_create_file_in_read_only_directory_raises_in_posix(self):
self.filesystem.is_windows_fs = False
@@ -488,9 +485,8 @@ class FakeFilesystemUnitTest(TestCase):
file_path = dir_path + '/baz'
if not is_root():
- self.assert_raises_os_error(errno.EACCES,
- self.filesystem.create_file,
- file_path)
+ with self.raises_os_error(errno.EACCES):
+ self.filesystem.create_file(file_path)
else:
self.filesystem.create_file(file_path)
self.assertTrue(self.filesystem.exists(file_path))
@@ -533,8 +529,8 @@ class FakeFilesystemUnitTest(TestCase):
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)
+ with self.raises_os_error(errno.EEXIST):
+ self.filesystem.create_file(path)
def test_create_file(self):
path = 'foo/bar/baz'
@@ -570,13 +566,14 @@ class FakeFilesystemUnitTest(TestCase):
self.assertEqual('', f.read())
def test_create_file_with_incorrect_mode_type(self):
- self.assertRaises(TypeError, self.filesystem.create_file, 'foo', 'bar')
+ with self.assertRaises(TypeError):
+ self.filesystem.create_file('foo', 'bar')
def test_create_file_already_exists_error(self):
path = 'foo/bar/baz'
self.filesystem.create_file(path, contents='dummy_data')
- self.assert_raises_os_error(
- errno.EEXIST, self.filesystem.create_file, path)
+ with self.raises_os_error(errno.EEXIST):
+ self.filesystem.create_file(path)
def test_create_link(self):
path = 'foo/bar/baz'
@@ -626,10 +623,10 @@ class FakeFilesystemUnitTest(TestCase):
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')
+ with self.raises_os_error(error_subtype):
+ self.filesystem.resolve('not_a_dir/foo')
+ with self.raises_os_error(error_subtype):
+ self.filesystem.lresolve('not_a_dir/foo/bar')
def test_directory_access_on_file_windows(self):
self.filesystem.is_windows_fs = True
@@ -715,8 +712,8 @@ class CaseInsensitiveFakeFilesystemTest(TestCase):
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)
+ with self.raises_os_error(errno.ELOOP):
+ self.os.path.getsize(link_path)
def test_get_mtime(self):
test_file = self.filesystem.create_file('foo/bar1.txt')
@@ -738,13 +735,14 @@ class CaseSensitiveFakeFilesystemTest(TestCase):
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')
+ with self.assertRaises(OSError):
+ self.filesystem.get_object('/Foo/Bar/Baz')
def test_remove_object(self):
self.filesystem.create_dir('/foo/bar')
self.filesystem.create_file('/foo/bar/baz')
- self.assertRaises(
- OSError, self.filesystem.remove_object, '/Foo/Bar/Baz')
+ with self.assertRaises(OSError):
+ self.filesystem.remove_object('/Foo/Bar/Baz')
self.assertTrue(self.filesystem.exists('/foo/bar/baz'))
def test_exists(self):
@@ -780,13 +778,14 @@ class CaseSensitiveFakeFilesystemTest(TestCase):
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')
+ with self.assertRaises(os.error):
+ self.path.getsize('FOO/BAR/BAZ')
def test_get_mtime(self):
test_file = self.filesystem.create_file('foo/bar1.txt')
test_file.st_mtime = 24
- self.assert_raises_os_error(
- errno.ENOENT, self.path.getmtime, 'Foo/Bar1.TXT')
+ with self.raises_os_error(errno.ENOENT):
+ self.path.getmtime('Foo/Bar1.TXT')
class OsPathInjectionRegressionTest(TestCase):
@@ -838,24 +837,19 @@ class OsPathInjectionRegressionTest(TestCase):
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
+ filename = 'foo'
+ abspath = '!%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))
+ self.assertEqual(abspath, self.path.abspath('..!%s' % filename))
def test_abspath_windows(self):
self.check_abspath(is_windows=True)
@@ -907,17 +901,23 @@ class FakePathModuleTest(TestCase):
def test_isabs_with_drive_component(self):
self.filesystem.is_windows_fs = False
self.assertFalse(self.path.isabs('C:!foo'))
+ self.assertFalse(self.path.isabs(b'C:!foo'))
self.assertTrue(self.path.isabs('!'))
+ self.assertTrue(self.path.isabs(b'!'))
self.filesystem.is_windows_fs = True
self.assertTrue(self.path.isabs('C:!foo'))
+ self.assertTrue(self.path.isabs(b'C:!foo'))
self.assertTrue(self.path.isabs('!'))
+ self.assertTrue(self.path.isabs(b'!'))
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, '')
+ with self.assertRaises(ValueError):
+ self.path.relpath(None)
+ with self.assertRaises(ValueError):
+ self.path.relpath('')
self.assertEqual('path!to!foo', self.path.relpath(path_foo))
self.assertEqual('..!foo',
self.path.relpath(path_foo, path_bar))
@@ -939,6 +939,17 @@ class FakePathModuleTest(TestCase):
self.assertEqual('!george!washington!bridge',
self.os.path.realpath('bridge'))
+ @unittest.skipIf(sys.version_info < (3, 10), "'strict' new in Python 3.10")
+ def test_realpath_strict(self):
+ self.filesystem.create_file('!foo!bar')
+ self.filesystem.cwd = '!foo'
+ self.assertEqual('!foo!baz',
+ self.os.path.realpath('baz', strict=False))
+ with self.raises_os_error(errno.ENOENT):
+ self.os.path.realpath('baz', strict=True)
+ self.assertEqual('!foo!bar',
+ self.os.path.realpath('bar', strict=True))
+
def test_samefile(self):
file_path1 = '!foo!bar!baz'
file_path2 = '!foo!bar!boo'
@@ -948,46 +959,53 @@ class FakePathModuleTest(TestCase):
self.assertFalse(self.path.samefile(file_path1, file_path2))
self.assertTrue(
self.path.samefile(file_path1, '!foo!..!foo!bar!..!bar!baz'))
+ self.assertTrue(
+ self.path.samefile(file_path1, b'!foo!..!foo!bar!..!bar!baz'))
def test_exists(self):
file_path = 'foo!bar!baz'
+ file_path_bytes = b'foo!bar!baz'
self.filesystem.create_file(file_path)
self.assertTrue(self.path.exists(file_path))
+ self.assertTrue(self.path.exists(file_path_bytes))
self.assertFalse(self.path.exists('!some!other!bogus!path'))
def test_lexists(self):
file_path = 'foo!bar!baz'
+ file_path_bytes = b'foo!bar!baz'
self.filesystem.create_dir('foo!bar')
self.filesystem.create_symlink(file_path, 'bogus')
self.assertTrue(self.path.lexists(file_path))
+ self.assertTrue(self.path.lexists(file_path_bytes))
self.assertFalse(self.path.exists(file_path))
+ self.assertFalse(self.path.exists(file_path_bytes))
self.filesystem.create_file('foo!bar!bogus')
self.assertTrue(self.path.exists(file_path))
def test_dirname_with_drive(self):
self.filesystem.is_windows_fs = True
- self.assertEqual(u'c:!foo',
- self.path.dirname(u'c:!foo!bar'))
+ self.assertEqual('c:!foo',
+ self.path.dirname('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('!foo',
+ self.path.dirname('!foo!bar'))
self.assertEqual(b'!',
self.path.dirname(b'!foo'))
- self.assertEqual(u'c:foo',
- self.path.dirname(u'c:foo!bar'))
+ self.assertEqual('c:foo',
+ self.path.dirname('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('foo',
+ self.path.dirname('foo!bar'))
def test_dirname(self):
dirname = 'foo!bar'
self.assertEqual(dirname, self.path.dirname('%s!baz' % dirname))
def test_join_strings(self):
- components = [u'foo', u'bar', u'baz']
- self.assertEqual(u'foo!bar!baz', self.path.join(*components))
+ components = ['foo', 'bar', 'baz']
+ self.assertEqual('foo!bar!baz', self.path.join(*components))
def test_join_bytes(self):
components = [b'foo', b'bar', b'baz']
@@ -1012,7 +1030,8 @@ class FakePathModuleTest(TestCase):
def test_getsize_path_nonexistent(self):
file_path = 'foo!bar!baz'
- self.assertRaises(os.error, self.path.getsize, file_path)
+ with self.assertRaises(os.error):
+ self.path.getsize(file_path)
def test_getsize_file_empty(self):
file_path = 'foo!bar!baz'
@@ -1021,8 +1040,10 @@ class FakePathModuleTest(TestCase):
def test_getsize_file_non_zero_size(self):
file_path = 'foo!bar!baz'
+ file_path_bytes = b'foo!bar!baz'
self.filesystem.create_file(file_path, contents='1234567')
self.assertEqual(7, self.path.getsize(file_path))
+ self.assertEqual(7, self.path.getsize(file_path_bytes))
def test_getsize_dir_empty(self):
# For directories, only require that the size is non-negative.
@@ -1043,6 +1064,7 @@ class FakePathModuleTest(TestCase):
def test_isdir(self):
self.filesystem.create_file('foo!bar')
self.assertTrue(self.path.isdir('foo'))
+ self.assertTrue(self.path.isdir(b'foo'))
self.assertFalse(self.path.isdir('foo!bar'))
self.assertFalse(self.path.isdir('it_dont_exist'))
@@ -1061,19 +1083,20 @@ class FakePathModuleTest(TestCase):
self.filesystem.create_file('foo!bar')
self.assertFalse(self.path.isfile('foo'))
self.assertTrue(self.path.isfile('foo!bar'))
+ self.assertTrue(self.path.isfile(b'foo!bar'))
self.assertFalse(self.path.isfile('it_dont_exist'))
def test_get_mtime(self):
test_file = self.filesystem.create_file('foo!bar1.txt')
- time.time.start()
- self.assertEqual(10, test_file.st_mtime)
+ self.assertNotEqual(24, self.path.getmtime('foo!bar1.txt'))
test_file.st_mtime = 24
self.assertEqual(24, self.path.getmtime('foo!bar1.txt'))
+ self.assertEqual(24, self.path.getmtime(b'foo!bar1.txt'))
def test_get_mtime_raises_os_error(self):
- self.assertFalse(self.path.exists('it_dont_exist'))
- self.assert_raises_os_error(errno.ENOENT, self.path.getmtime,
- 'it_dont_exist')
+ self.assertFalse(self.path.exists('does_not_exist'))
+ with self.raises_os_error(errno.ENOENT):
+ self.path.getmtime('does_not_exist')
def test_islink(self):
self.filesystem.create_dir('foo')
@@ -1085,6 +1108,8 @@ class FakePathModuleTest(TestCase):
# comments in Python/Lib/posixpath.py.
self.assertTrue(self.path.islink('foo!link_to_file'))
self.assertTrue(self.path.isfile('foo!link_to_file'))
+ self.assertTrue(self.path.islink(b'foo!link_to_file'))
+ self.assertTrue(self.path.isfile(b'foo!link_to_file'))
self.assertTrue(self.path.isfile('foo!regular_file'))
self.assertFalse(self.path.islink('foo!regular_file'))
@@ -1101,9 +1126,11 @@ class FakePathModuleTest(TestCase):
def test_ismount(self):
self.assertFalse(self.path.ismount(''))
self.assertTrue(self.path.ismount('!'))
+ self.assertTrue(self.path.ismount(b'!'))
self.assertFalse(self.path.ismount('!mount!'))
self.filesystem.add_mount_point('!mount')
self.assertTrue(self.path.ismount('!mount'))
+ self.assertTrue(self.path.ismount(b'!mount'))
self.assertTrue(self.path.ismount('!mount!'))
def test_ismount_with_drive_letters(self):
@@ -1142,7 +1169,7 @@ class FakePathModuleTest(TestCase):
if self.is_windows:
private_path_function = '_get_bothseps'
else:
- private_path_function = '_joinrealpath'
+ private_path_function = '_join_real_path'
if private_path_function:
self.assertTrue(hasattr(self.path, private_path_function),
'Get a real os.path function '
@@ -1232,9 +1259,9 @@ class SplitPathTest(PathManipulationTestBase):
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(('|||', ''), self.filesystem.splitpath('|||'))
self.assertEqual(('|', 'a'), self.filesystem.splitpath('|a'))
- 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|'))
@@ -1365,6 +1392,7 @@ class AlternativePathSeparatorTest(TestCase):
class DriveLetterSupportTest(TestCase):
def setUp(self):
self.filesystem = fake_filesystem.FakeFilesystem(path_separator='!')
+ self.filesystem.alternative_path_separator = '^'
self.filesystem.is_windows_fs = True
def test_initial_value(self):
@@ -1383,11 +1411,11 @@ class DriveLetterSupportTest(TestCase):
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'))
+ self.filesystem.cwd = ''
+ self.assertEqual('c:!foo!bar',
+ self.filesystem.absnormpath('c:!foo!!bar'))
+ self.filesystem.cwd = 'c:!foo'
+ self.assertEqual('c:!foo!bar', self.filesystem.absnormpath('bar'))
def test_normalize_path_bytes(self):
self.filesystem.cwd = b''
@@ -1397,20 +1425,30 @@ class DriveLetterSupportTest(TestCase):
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'))
+ self.assertEqual(('c:!foo', 'bar'),
+ self.filesystem.splitpath('c:!foo!bar'))
+ self.assertEqual(('c:!', 'foo'),
+ self.filesystem.splitpath('c:!foo'))
+ self.assertEqual(('!foo', 'bar'),
+ self.filesystem.splitpath('!foo!bar'))
+ self.assertEqual(('!', 'foo'),
+ self.filesystem.splitpath('!foo'))
+ self.assertEqual(('c:foo', 'bar'),
+ self.filesystem.splitpath('c:foo!bar'))
+ self.assertEqual(('c:', 'foo'),
+ self.filesystem.splitpath('c:foo'))
+ self.assertEqual(('foo', 'bar'),
+ self.filesystem.splitpath('foo!bar'))
+
+ def test_split_with_alt_separator(self):
+ self.assertEqual(('a^b', 'c'), self.filesystem.splitpath('a^b^c'))
+ self.assertEqual(('a^b!c', 'd'), self.filesystem.splitpath('a^b!c^d'))
+ self.assertEqual(('a^b!c', 'd'), self.filesystem.splitpath('a^b!c!d'))
+ self.assertEqual((b'a^b', b'c'), self.filesystem.splitpath(b'a^b^c'))
+ self.assertEqual((b'a^b!c', b'd'),
+ self.filesystem.splitpath(b'a^b!c^d'))
+ self.assertEqual((b'a^b!c', b'd'),
+ self.filesystem.splitpath(b'a^b!c!d'))
def test_split_path_bytes(self):
self.assertEqual((b'c:!foo', b'bar'),
@@ -1441,14 +1479,14 @@ class DriveLetterSupportTest(TestCase):
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'))
+ self.assertEqual(('c:', '!foo!bar'),
+ self.filesystem.splitdrive('c:!foo!bar'))
+ self.assertEqual(('', '!foo!bar'),
+ self.filesystem.splitdrive('!foo!bar'))
+ self.assertEqual(('c:', 'foo!bar'),
+ self.filesystem.splitdrive('c:foo!bar'))
+ self.assertEqual(('', 'foo!bar'),
+ self.filesystem.splitdrive('foo!bar'))
def test_split_drive_bytes(self):
self.assertEqual((b'c:', b'!foo!bar'),
@@ -1456,6 +1494,20 @@ class DriveLetterSupportTest(TestCase):
self.assertEqual((b'', b'!foo!bar'),
self.filesystem.splitdrive(b'!foo!bar'))
+ def test_split_drive_alt_sep(self):
+ self.assertEqual(('c:', '^foo^bar'),
+ self.filesystem.splitdrive('c:^foo^bar'))
+ self.assertEqual(('', 'foo^bar'),
+ self.filesystem.splitdrive('foo^bar'))
+ self.assertEqual(('', 'foo^bar!baz'),
+ self.filesystem.splitdrive('foo^bar!baz'))
+ self.assertEqual((b'c:', b'^foo^bar'),
+ self.filesystem.splitdrive(b'c:^foo^bar'))
+ self.assertEqual((b'', b'^foo^bar'),
+ self.filesystem.splitdrive(b'^foo^bar'))
+ self.assertEqual((b'', b'^foo^bar!baz'),
+ self.filesystem.splitdrive(b'^foo^bar!baz'))
+
def test_split_drive_with_unc_path(self):
self.assertEqual(('!!foo!bar', '!baz'),
self.filesystem.splitdrive('!!foo!bar!baz'))
@@ -1465,28 +1517,77 @@ class DriveLetterSupportTest(TestCase):
self.assertEqual(('!!foo!bar', '!!'),
self.filesystem.splitdrive('!!foo!bar!!'))
+ def test_split_drive_with_unc_path_alt_sep(self):
+ self.assertEqual(('^^foo^bar', '!baz'),
+ self.filesystem.splitdrive('^^foo^bar!baz'))
+ self.assertEqual(('', '^^foo'), self.filesystem.splitdrive('^^foo'))
+ self.assertEqual(('', '^^foo^^bar'),
+ self.filesystem.splitdrive('^^foo^^bar'))
+ self.assertEqual(('^^foo^bar', '^^'),
+ self.filesystem.splitdrive('^^foo^bar^^'))
+
+ def test_split_path_with_drive(self):
+ self.assertEqual(('d:!foo', 'baz'),
+ self.filesystem.splitpath('d:!foo!baz'))
+ self.assertEqual(('d:!foo!baz', ''),
+ self.filesystem.splitpath('d:!foo!baz!'))
+ self.assertEqual(('c:', ''),
+ self.filesystem.splitpath('c:'))
+ self.assertEqual(('c:!', ''),
+ self.filesystem.splitpath('c:!'))
+ self.assertEqual(('c:!!', ''),
+ self.filesystem.splitpath('c:!!'))
+
+ def test_split_path_with_drive_alt_sep(self):
+ self.assertEqual(('d:^foo', 'baz'),
+ self.filesystem.splitpath('d:^foo^baz'))
+ self.assertEqual(('d:^foo^baz', ''),
+ self.filesystem.splitpath('d:^foo^baz^'))
+ self.assertEqual(('c:', ''),
+ self.filesystem.splitpath('c:'))
+ self.assertEqual(('c:^', ''),
+ self.filesystem.splitpath('c:^'))
+ self.assertEqual(('c:^^', ''),
+ self.filesystem.splitpath('c:^^'))
+
+ def test_split_path_with_unc_path(self):
+ self.assertEqual(('!!foo!bar!', 'baz'),
+ self.filesystem.splitpath('!!foo!bar!baz'))
+ self.assertEqual(('!!foo!bar', ''),
+ self.filesystem.splitpath('!!foo!bar'))
+ self.assertEqual(('!!foo!bar!!', ''),
+ self.filesystem.splitpath('!!foo!bar!!'))
+
+ def test_split_path_with_unc_path_alt_sep(self):
+ self.assertEqual(('^^foo^bar^', 'baz'),
+ self.filesystem.splitpath('^^foo^bar^baz'))
+ self.assertEqual(('^^foo^bar', ''),
+ self.filesystem.splitpath('^^foo^bar'))
+ self.assertEqual(('^^foo^bar^^', ''),
+ self.filesystem.splitpath('^^foo^bar^^'))
+
class DiskSpaceTest(TestCase):
def setUp(self):
self.filesystem = fake_filesystem.FakeFilesystem(path_separator='!',
total_size=100)
self.os = fake_filesystem.FakeOsModule(self.filesystem)
+ self.open = fake_filesystem.FakeFileOpen(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:
+ with self.open('!mount!file', 'w') as dest:
dest.write('a' * (total_size + 1))
- self.assertRaises(OSError, create_too_large_file)
+ with 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:
+ with self.open('!mount!file', 'w') as dest:
dest.write('a' * total_size)
self.assertEqual(total_size,
@@ -1506,16 +1607,16 @@ class DiskSpaceTest(TestCase):
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.filesystem.create_file('!foo!bar', contents='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'сложно',
+ self.filesystem.create_file('!foo!bar', contents='сложно',
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'複雑',
+ self.filesystem.create_file('!foo!bar', contents='複雑',
encoding='utf-8')
self.assertEqual((100, 6, 94), self.filesystem.get_disk_usage())
@@ -1550,7 +1651,8 @@ class DiskSpaceTest(TestCase):
initial_usage = self.filesystem.get_disk_usage()
- self.assertRaises(OSError, create_large_file)
+ with self.assertRaises(OSError):
+ create_large_file()
self.assertEqual(initial_usage, self.filesystem.get_disk_usage())
@@ -1572,7 +1674,8 @@ class DiskSpaceTest(TestCase):
def create_large_file():
self.filesystem.create_file('!foo!bar', st_size=101)
- self.assertRaises(OSError, create_large_file)
+ with self.assertRaises(OSError):
+ create_large_file()
self.assertEqual(initial_usage, self.filesystem.get_disk_usage())
@@ -1587,10 +1690,10 @@ class DiskSpaceTest(TestCase):
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)
+ with self.raises_os_error(errno.ENOSPC):
+ file_object.set_large_file_size(200)
+ with self.raises_os_error(errno.ENOSPC):
+ file_object.set_contents('a' * 150)
def test_file_system_size_after_directory_rename(self):
self.filesystem.create_file('!foo!bar', st_size=20)
@@ -1621,11 +1724,10 @@ class DiskSpaceTest(TestCase):
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)
+ with self.raises_os_error(errno.ENOSPC):
+ self.filesystem.create_file('!mount_limited!foo', st_size=60)
+ with self.raises_os_error(errno.ENOSPC):
+ self.filesystem.create_file('!bar', st_size=110)
try:
self.filesystem.create_file('!foo', st_size=60)
@@ -1652,9 +1754,8 @@ class DiskSpaceTest(TestCase):
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)
+ with self.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,
@@ -1663,9 +1764,8 @@ class DiskSpaceTest(TestCase):
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')
+ with self.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)
@@ -1690,7 +1790,7 @@ class DiskSpaceTest(TestCase):
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)
+ self.assertEqual(800, self.filesystem.get_disk_usage('d:!foo').free)
def test_copying_preserves_byte_contents(self):
source_file = self.filesystem.create_file('foo', contents=b'somebytes')
@@ -1698,6 +1798,53 @@ class DiskSpaceTest(TestCase):
dest_file.set_contents(source_file.contents)
self.assertEqual(dest_file.contents, source_file.contents)
+ def test_diskusage_after_open_write(self):
+ with self.open('bar.txt', 'w') as f:
+ f.write('a' * 60)
+ f.flush()
+ self.assertEqual(60, self.filesystem.get_disk_usage()[1])
+
+ def test_disk_full_after_reopened(self):
+ with self.open('bar.txt', 'w') as f:
+ f.write('a' * 60)
+ with self.open('bar.txt') as f:
+ self.assertEqual('a' * 60, f.read())
+ with self.raises_os_error(errno.ENOSPC):
+ with self.open('bar.txt', 'w') as f:
+ f.write('b' * 110)
+ with self.raises_os_error(errno.ENOSPC):
+ f.flush()
+ with self.open('bar.txt') as f:
+ self.assertEqual('', f.read())
+
+ def test_disk_full_append(self):
+ file_path = 'bar.txt'
+ with self.open(file_path, 'w') as f:
+ f.write('a' * 60)
+ with self.open(file_path) as f:
+ self.assertEqual('a' * 60, f.read())
+ with self.raises_os_error(errno.ENOSPC):
+ with self.open(file_path, 'a') as f:
+ f.write('b' * 41)
+ with self.raises_os_error(errno.ENOSPC):
+ f.flush()
+ with self.open('bar.txt') as f:
+ self.assertEqual(f.read(), 'a' * 60)
+
+ def test_disk_full_after_reopened_rplus_seek(self):
+ with self.open('bar.txt', 'w') as f:
+ f.write('a' * 60)
+ with self.open('bar.txt') as f:
+ self.assertEqual(f.read(), 'a' * 60)
+ with self.raises_os_error(errno.ENOSPC):
+ with self.open('bar.txt', 'r+') as f:
+ f.seek(50)
+ f.write('b' * 60)
+ with self.raises_os_error(errno.ENOSPC):
+ f.flush()
+ with self.open('bar.txt') as f:
+ self.assertEqual(f.read(), 'a' * 60)
+
class MountPointTest(TestCase):
def setUp(self):
@@ -1726,10 +1873,10 @@ class MountPointTest(TestCase):
'!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!')
+ with self.raises_os_error(errno.EEXIST):
+ self.filesystem.add_mount_point('!foo')
+ with self.raises_os_error(errno.EEXIST):
+ self.filesystem.add_mount_point('!foo!')
def test_that_drives_are_auto_mounted(self):
self.filesystem.is_windows_fs = True
@@ -1760,7 +1907,31 @@ class MountPointTest(TestCase):
'!!foo!bar!bip!bop').st_dev)
-class RealFileSystemAccessTest(TestCase):
+class ConvenienceMethodTest(RealFsTestCase):
+
+ def test_create_link_with_non_existent_parent(self):
+ self.skip_if_symlink_not_supported()
+ file1_path = self.make_path('test_file1')
+ link_path = self.make_path('nonexistent', 'test_file2')
+
+ self.filesystem.create_file(file1_path, contents='link test')
+ self.assertEqual(self.os.stat(file1_path).st_nlink, 1)
+ self.filesystem.create_link(file1_path, link_path)
+ self.assertEqual(self.os.stat(file1_path).st_nlink, 2)
+ self.assertTrue(self.filesystem.exists(link_path))
+
+ def test_create_symlink_with_non_existent_parent(self):
+ self.skip_if_symlink_not_supported()
+ file1_path = self.make_path('test_file1')
+ link_path = self.make_path('nonexistent', 'test_file2')
+
+ self.filesystem.create_file(file1_path, contents='symlink test')
+ self.filesystem.create_symlink(link_path, file1_path)
+ self.assertTrue(self.filesystem.exists(link_path))
+ self.assertTrue(self.filesystem.islink(link_path))
+
+
+class RealFileSystemAccessTest(RealFsTestCase):
def setUp(self):
# use the real path separator to work with the real file system
self.filesystem = fake_filesystem.FakeFilesystem()
@@ -1771,29 +1942,26 @@ class RealFileSystemAccessTest(TestCase):
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)
+ with self.assertRaises(OSError):
+ self.filesystem.add_real_file(nonexisting_path)
self.assertFalse(self.filesystem.exists(nonexisting_path))
def test_add_non_existing_real_directory_raises(self):
nonexisting_path = '/nonexisting'
- self.assert_raises_os_error(errno.ENOENT,
- self.filesystem.add_real_directory,
- nonexisting_path)
+ with self.raises_os_error(errno.ENOENT):
+ self.filesystem.add_real_directory(nonexisting_path)
self.assertFalse(self.filesystem.exists(nonexisting_path))
def test_existing_fake_file_raises(self):
real_file_path = __file__
self.filesystem.create_file(real_file_path)
- self.assert_raises_os_error(errno.EEXIST,
- self.filesystem.add_real_file,
- real_file_path)
+ with self.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)
+ with self.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):
@@ -1820,8 +1988,8 @@ class RealFileSystemAccessTest(TestCase):
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')
+ with self.raises_os_error(errno.EACCES):
+ self.fake_open(real_file_path, 'w')
else:
with self.fake_open(real_file_path, 'w'):
pass
@@ -1858,9 +2026,9 @@ class RealFileSystemAccessTest(TestCase):
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')
+ with self.raises_os_error(errno.EEXIST):
+ self.filesystem.add_real_file(real_file_path,
+ target_path='/foo/bar')
def test_add_real_file_to_non_existing_path(self):
real_file_path = os.path.abspath(__file__)
@@ -1992,6 +2160,7 @@ class RealFileSystemAccessTest(TestCase):
)
def test_add_existing_real_directory_symlink_target_path(self):
+ self.skip_if_symlink_not_supported(force_real_fs=True)
real_directory = os.path.join(self.root_path, 'pyfakefs', 'tests')
symlinks = [
('..', os.path.join(
@@ -2000,15 +2169,9 @@ class RealFileSystemAccessTest(TestCase):
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
+ with self.create_symlinks(symlinks):
+ self.filesystem.add_real_directory(
+ real_directory, target_path='/path', lazy_read=False)
self.assertTrue(self.filesystem.exists(
'/path/fixtures/symlink_dir_relative'))
@@ -2018,6 +2181,7 @@ class RealFileSystemAccessTest(TestCase):
'/path/fixtures/symlink_file_relative'))
def test_add_existing_real_directory_symlink_lazy_read(self):
+ self.skip_if_symlink_not_supported(force_real_fs=True)
real_directory = os.path.join(self.root_path, 'pyfakefs', 'tests')
symlinks = [
('..', os.path.join(
@@ -2026,29 +2190,22 @@ class RealFileSystemAccessTest(TestCase):
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
+ with self.create_symlinks(symlinks):
+ self.filesystem.add_real_directory(
+ real_directory, target_path='/path', lazy_read=True)
+
+ self.assertTrue(self.filesystem.exists(
+ '/path/fixtures/symlink_dir_relative'))
+ self.assertTrue(self.filesystem.exists(
+ '/path/fixtures/symlink_dir_relative/all_tests.py'))
+ self.assertTrue(self.filesystem.exists(
+ '/path/fixtures/symlink_file_relative'))
def test_add_existing_real_directory_tree_to_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')
+ with self.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,
@@ -2179,7 +2336,7 @@ class FileSideEffectTests(TestCase):
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')
+ self.assertEqual(self.side_effect_file_object_content, 'foo')
if __name__ == '__main__':
diff --git a/pyfakefs/tests/fake_filesystem_unittest_test.py b/pyfakefs/tests/fake_filesystem_unittest_test.py
index b444211..f4da663 100644
--- a/pyfakefs/tests/fake_filesystem_unittest_test.py
+++ b/pyfakefs/tests/fake_filesystem_unittest_test.py
@@ -21,17 +21,24 @@ import glob
import io
import multiprocessing
import os
+import pathlib
+import runpy
import shutil
import sys
import tempfile
import unittest
+import warnings
from distutils.dir_util import copy_tree, remove_tree
-from unittest import TestCase
+from pathlib import Path
+from unittest import TestCase, mock
import pyfakefs.tests.import_as_example
+import pyfakefs.tests.logsio
from pyfakefs import fake_filesystem_unittest, fake_filesystem
-from pyfakefs.extra_packages import pathlib
-from pyfakefs.fake_filesystem_unittest import Patcher, Pause, patchfs
+from pyfakefs.fake_filesystem import OSType
+from pyfakefs.fake_filesystem_unittest import (
+ Patcher, Pause, patchfs, PatchMode
+)
from pyfakefs.tests.fixtures import module_with_attributes
@@ -44,13 +51,35 @@ class TestPatcher(TestCase):
self.assertEqual('test', contents)
@patchfs
- def test_context_decorator(self, fs):
- fs.create_file('/foo/bar', contents='test')
+ def test_context_decorator(self, fake_fs):
+ fake_fs.create_file('/foo/bar', contents='test')
with open('/foo/bar') as f:
contents = f.read()
self.assertEqual('test', contents)
+class TestPatchfsArgumentOrder(TestCase):
+ @patchfs
+ @mock.patch('os.system')
+ def test_argument_order1(self, fake_fs, patched_system):
+ fake_fs.create_file('/foo/bar', contents='test')
+ with open('/foo/bar') as f:
+ contents = f.read()
+ self.assertEqual('test', contents)
+ os.system("foo")
+ patched_system.assert_called_with("foo")
+
+ @mock.patch('os.system')
+ @patchfs
+ def test_argument_order2(self, patched_system, fake_fs):
+ fake_fs.create_file('/foo/bar', contents='test')
+ with open('/foo/bar') as f:
+ contents = f.read()
+ self.assertEqual('test', contents)
+ os.system("foo")
+ patched_system.assert_called_with("foo")
+
+
class TestPyfakefsUnittestBase(fake_filesystem_unittest.TestCase):
def setUp(self):
"""Set up the fake file system"""
@@ -68,8 +97,8 @@ class TestPyfakefsUnittest(TestPyfakefsUnittestBase): # pylint: disable=R0904
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')
+ self.assertEqual('This test file was created using the '
+ 'open() function.\n', content)
def test_io_open(self):
"""Fake io module is bound"""
@@ -80,8 +109,8 @@ class TestPyfakefsUnittest(TestPyfakefsUnittestBase): # pylint: disable=R0904
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')
+ self.assertEqual('This test file was created using the '
+ 'io.open() function.\n', content)
def test_os(self):
"""Fake os module is bound"""
@@ -92,22 +121,21 @@ class TestPyfakefsUnittest(TestPyfakefsUnittestBase): # pylint: disable=R0904
def test_glob(self):
"""Fake glob module is bound"""
is_windows = sys.platform.startswith('win')
- self.assertEqual(glob.glob('/test/dir1/dir*'),
- [])
+ 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'])
+ self.assertEqual([r'/test/dir1\dir2a'], matching_paths)
else:
- self.assertEqual(matching_paths, ['/test/dir1/dir2a'])
+ self.assertEqual(['/test/dir1/dir2a'], matching_paths)
self.fs.create_dir('/test/dir1/dir2b')
matching_paths = sorted(glob.glob('/test/dir1/dir*'))
if is_windows:
- self.assertEqual(matching_paths,
- [r'\test\dir1\dir2a', r'\test\dir1\dir2b'])
+ self.assertEqual([r'/test/dir1\dir2a', r'/test/dir1\dir2b'],
+ matching_paths)
else:
- self.assertEqual(matching_paths,
- ['/test/dir1/dir2a', '/test/dir1/dir2b'])
+ self.assertEqual(['/test/dir1/dir2a', '/test/dir1/dir2b'],
+ matching_paths)
def test_shutil(self):
"""Fake shutil module is bound"""
@@ -119,7 +147,6 @@ class TestPyfakefsUnittest(TestPyfakefsUnittestBase): # pylint: disable=R0904
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:
@@ -147,12 +174,11 @@ class TestPatchingImports(TestPyfakefsUnittestBase):
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_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'
@@ -166,6 +192,12 @@ class TestPatchingImports(TestPyfakefsUnittestBase):
self.assertTrue(
pyfakefs.tests.import_as_example.check_if_exists6(file_path))
+ def test_import_pathlib_path(self):
+ file_path = '/foo/bar'
+ self.fs.create_dir(file_path)
+ self.assertTrue(
+ pyfakefs.tests.import_as_example.check_if_exists7(file_path))
+
def test_import_function_from_os(self):
file_path = '/foo/bar'
self.fs.create_file(file_path, contents=b'abc')
@@ -191,7 +223,10 @@ class TestPatchingImports(TestPyfakefsUnittestBase):
self.assertEqual('abc', contents)
-class TestPatchingDefaultArgs(TestPyfakefsUnittestBase):
+class TestPatchingDefaultArgs(fake_filesystem_unittest.TestCase):
+ def setUp(self):
+ self.setUpPyfakefs(patch_default_args=True)
+
def test_path_exists_as_default_arg_in_function(self):
file_path = '/foo/bar'
self.fs.create_dir(file_path)
@@ -204,6 +239,11 @@ class TestPatchingDefaultArgs(TestPyfakefsUnittestBase):
sut = pyfakefs.tests.import_as_example.TestDefaultArg()
self.assertTrue(sut.check_if_exists(file_path))
+ def test_fake_path_exists4(self):
+ self.fs.create_file('foo')
+ self.assertTrue(
+ pyfakefs.tests.import_as_example.check_if_exists4('foo'))
+
class TestAttributesWithFakeModuleNames(TestPyfakefsUnittestBase):
"""Test that module attributes with names like `path` or `io` are not
@@ -262,10 +302,54 @@ class NoSkipNamesTest(fake_filesystem_unittest.TestCase):
"""Reference test for additional_skip_names tests:
make sure that the module is patched by default."""
+ def setUp(self):
+ self.setUpPyfakefs()
+
def test_path_exists(self):
- self.assertTrue(
+ self.assertFalse(
pyfakefs.tests.import_as_example.exists_this_file())
+ def test_fake_path_exists1(self):
+ self.fs.create_file('foo')
+ self.assertTrue(
+ pyfakefs.tests.import_as_example.check_if_exists1('foo'))
+
+ def test_fake_path_exists2(self):
+ self.fs.create_file('foo')
+ self.assertTrue(
+ pyfakefs.tests.import_as_example.check_if_exists2('foo'))
+
+ def test_fake_path_exists3(self):
+ self.fs.create_file('foo')
+ self.assertTrue(
+ pyfakefs.tests.import_as_example.check_if_exists3('foo'))
+
+ def test_fake_path_exists5(self):
+ self.fs.create_file('foo')
+ self.assertTrue(
+ pyfakefs.tests.import_as_example.check_if_exists5('foo'))
+
+ def test_fake_path_exists6(self):
+ self.fs.create_file('foo')
+ self.assertTrue(
+ pyfakefs.tests.import_as_example.check_if_exists6('foo'))
+
+ def test_fake_path_exists7(self):
+ self.fs.create_file('foo')
+ self.assertTrue(
+ pyfakefs.tests.import_as_example.check_if_exists7('foo'))
+
+ def test_open_fails(self):
+ with self.assertRaises(OSError):
+ pyfakefs.tests.import_as_example.open_this_file()
+
+ def test_open_patched_in_module_ending_with_io(self):
+ # regression test for #569
+ file_path = '/foo/bar'
+ self.fs.create_file(file_path, contents=b'abc')
+ contents = pyfakefs.tests.logsio.file_contents(file_path)
+ self.assertEqual(b'abc', contents)
+
class AdditionalSkipNamesTest(fake_filesystem_unittest.TestCase):
"""Make sure that modules in additional_skip_names are not patched.
@@ -276,9 +360,50 @@ class AdditionalSkipNamesTest(fake_filesystem_unittest.TestCase):
additional_skip_names=['pyfakefs.tests.import_as_example'])
def test_path_exists(self):
- self.assertFalse(
+ self.assertTrue(
pyfakefs.tests.import_as_example.exists_this_file())
+ def test_fake_path_does_not_exist1(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists1('foo'))
+
+ def test_fake_path_does_not_exist2(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists2('foo'))
+
+ def test_fake_path_does_not_exist3(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists3('foo'))
+
+ def test_fake_path_does_not_exist4(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists4('foo'))
+
+ def test_fake_path_does_not_exist5(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists5('foo'))
+
+ def test_fake_path_does_not_exist6(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists6('foo'))
+
+ def test_fake_path_does_not_exist7(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists7('foo'))
+
+ def test_open_succeeds(self):
+ pyfakefs.tests.import_as_example.open_this_file()
+
+ def test_path_succeeds(self):
+ pyfakefs.tests.import_as_example.return_this_file_path()
+
class AdditionalSkipNamesModuleTest(fake_filesystem_unittest.TestCase):
"""Make sure that modules in additional_skip_names are not patched.
@@ -289,9 +414,50 @@ class AdditionalSkipNamesModuleTest(fake_filesystem_unittest.TestCase):
additional_skip_names=[pyfakefs.tests.import_as_example])
def test_path_exists(self):
- self.assertFalse(
+ self.assertTrue(
pyfakefs.tests.import_as_example.exists_this_file())
+ def test_fake_path_does_not_exist1(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists1('foo'))
+
+ def test_fake_path_does_not_exist2(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists2('foo'))
+
+ def test_fake_path_does_not_exist3(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists3('foo'))
+
+ def test_fake_path_does_not_exist4(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists4('foo'))
+
+ def test_fake_path_does_not_exist5(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists5('foo'))
+
+ def test_fake_path_does_not_exist6(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists6('foo'))
+
+ def test_fake_path_does_not_exist7(self):
+ self.fs.create_file('foo')
+ self.assertFalse(
+ pyfakefs.tests.import_as_example.check_if_exists7('foo'))
+
+ def test_open_succeeds(self):
+ pyfakefs.tests.import_as_example.open_this_file()
+
+ def test_path_succeeds(self):
+ pyfakefs.tests.import_as_example.return_this_file_path()
+
class FakeExampleModule:
"""Used to patch a function that uses system-specific functions that
@@ -334,17 +500,17 @@ class PatchModuleTestUsingDecorator(unittest.TestCase):
@patchfs
@unittest.expectedFailure
- def test_system_stat_failing(self, fs):
+ def test_system_stat_failing(self, fake_fs):
file_path = '/foo/bar'
- fs.create_file(file_path, contents=b'test')
+ fake_fs.create_file(file_path, contents=b'test')
self.assertEqual(
4, pyfakefs.tests.import_as_example.system_stat(file_path).st_size)
@patchfs(modules_to_patch={
'pyfakefs.tests.import_as_example': FakeExampleModule})
- def test_system_stat(self, fs):
+ def test_system_stat(self, fake_fs):
file_path = '/foo/bar'
- fs.create_file(file_path, contents=b'test')
+ fake_fs.create_file(file_path, contents=b'test')
self.assertEqual(
4, pyfakefs.tests.import_as_example.system_stat(file_path).st_size)
@@ -363,15 +529,25 @@ class NoRootUserTest(fake_filesystem_unittest.TestCase):
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)
+ with 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')
+ with self.assertRaises(OSError):
+ open(file_path, 'w')
+
+class PauseResumeTest(fake_filesystem_unittest.TestCase):
+ def setUp(self):
+ self.real_temp_file = None
+ self.setUpPyfakefs()
+
+ def tearDown(self):
+ if self.real_temp_file is not None:
+ self.real_temp_file.close()
-class PauseResumeTest(TestPyfakefsUnittestBase):
def test_pause_resume(self):
fake_temp_file = tempfile.NamedTemporaryFile()
self.assertTrue(self.fs.exists(fake_temp_file.name))
@@ -379,11 +555,11 @@ class PauseResumeTest(TestPyfakefsUnittestBase):
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.real_temp_file = tempfile.NamedTemporaryFile()
+ self.assertFalse(self.fs.exists(self.real_temp_file.name))
+ self.assertTrue(os.path.exists(self.real_temp_file.name))
self.resume()
- self.assertFalse(os.path.exists(real_temp_file.name))
+ self.assertFalse(os.path.exists(self.real_temp_file.name))
self.assertTrue(os.path.exists(fake_temp_file.name))
def test_pause_resume_fs(self):
@@ -396,15 +572,15 @@ class PauseResumeTest(TestPyfakefsUnittestBase):
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))
+ self.real_temp_file = tempfile.NamedTemporaryFile()
+ self.assertFalse(self.fs.exists(self.real_temp_file.name))
+ self.assertTrue(os.path.exists(self.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.assertFalse(self.fs.exists(self.real_temp_file.name))
+ self.assertTrue(os.path.exists(self.real_temp_file.name))
self.fs.resume()
- self.assertFalse(os.path.exists(real_temp_file.name))
+ self.assertFalse(os.path.exists(self.real_temp_file.name))
self.assertTrue(os.path.exists(fake_temp_file.name))
def test_pause_resume_contextmanager(self):
@@ -414,10 +590,10 @@ class PauseResumeTest(TestPyfakefsUnittestBase):
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.real_temp_file = tempfile.NamedTemporaryFile()
+ self.assertFalse(self.fs.exists(self.real_temp_file.name))
+ self.assertTrue(os.path.exists(self.real_temp_file.name))
+ self.assertFalse(os.path.exists(self.real_temp_file.name))
self.assertTrue(os.path.exists(fake_temp_file.name))
def test_pause_resume_fs_contextmanager(self):
@@ -427,15 +603,16 @@ class PauseResumeTest(TestPyfakefsUnittestBase):
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.real_temp_file = tempfile.NamedTemporaryFile()
+ self.assertFalse(self.fs.exists(self.real_temp_file.name))
+ self.assertTrue(os.path.exists(self.real_temp_file.name))
+ self.assertFalse(os.path.exists(self.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)
+ with self.assertRaises(RuntimeError):
+ fs.resume()
class PauseResumePatcherTest(fake_filesystem_unittest.TestCase):
@@ -453,6 +630,7 @@ class PauseResumePatcherTest(fake_filesystem_unittest.TestCase):
p.resume()
self.assertFalse(os.path.exists(real_temp_file.name))
self.assertTrue(os.path.exists(fake_temp_file.name))
+ real_temp_file.close()
def test_pause_resume_contextmanager(self):
with Patcher() as p:
@@ -467,104 +645,7 @@ class PauseResumePatcherTest(fake_filesystem_unittest.TestCase):
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')))
+ real_temp_file.close()
class TestPyfakefsTestCase(unittest.TestCase):
@@ -646,5 +727,147 @@ class TestDistutilsCopyTree(fake_filesystem_unittest.TestCase):
self.assertFalse(os.path.isdir('./test2/'))
+class PathlibTest(TestCase):
+ """Regression test for #527"""
+
+ @patchfs
+ def test_cwd(self, fs):
+ """Make sure fake file system is used for os in pathlib"""
+ self.assertEqual(os.path.sep, str(pathlib.Path.cwd()))
+ dot_abs = pathlib.Path(".").absolute()
+ self.assertEqual(os.path.sep, str(dot_abs))
+ self.assertTrue(dot_abs.exists())
+
+
+class TestDeprecationSuppression(fake_filesystem_unittest.TestCase):
+ @unittest.skipIf(sys.version_info[1] == 6,
+ 'Test fails for Python 3.6 for unknown reason')
+ def test_no_deprecation_warning(self):
+ """Ensures that deprecation warnings are suppressed during module
+ lookup, see #542.
+ """
+
+ from pyfakefs.tests.fixtures.deprecated_property import \
+ DeprecationTest # noqa: F401
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("error", DeprecationWarning)
+ self.setUpPyfakefs()
+ self.assertEqual(0, len(w))
+
+
+def load_configs(configs):
+ """ Helper code for patching open_code in auto mode, see issue #554. """
+ retval = []
+ for config in configs:
+ if len(config) > 3 and config[-3:] == ".py":
+ retval += runpy.run_path(config)
+ else:
+ retval += runpy.run_module(config)
+ return retval
+
+
+class AutoPatchOpenCodeTestCase(fake_filesystem_unittest.TestCase):
+ """ Test patching open_code in auto mode, see issue #554."""
+
+ def setUp(self):
+ self.setUpPyfakefs(patch_open_code=PatchMode.AUTO)
+
+ self.configpy = 'configpy.py'
+ self.fs.create_file(
+ self.configpy,
+ contents="configurable_value='yup'")
+ self.config_module = 'pyfakefs.tests.fixtures.config_module'
+
+ def test_both(self):
+ load_configs([self.configpy, self.config_module])
+
+ def test_run_path(self):
+ load_configs([self.configpy])
+
+ def test_run_module(self):
+ load_configs([self.config_module])
+
+
+class TestOtherFS(fake_filesystem_unittest.TestCase):
+ def setUp(self):
+ self.setUpPyfakefs()
+
+ def test_real_file_with_home(self):
+ """Regression test for #558"""
+ self.fs.is_windows_fs = os.name != 'nt'
+ self.fs.add_real_file(__file__)
+ with open(__file__) as f:
+ self.assertTrue(f.read())
+ home = Path.home()
+ if sys.version_info < (3, 6):
+ # fspath support since Python 3.6
+ home = str(home)
+ os.chdir(home)
+ with open(__file__) as f:
+ self.assertTrue(f.read())
+
+ def test_windows(self):
+ self.fs.os = OSType.WINDOWS
+ path = r'C:\foo\bar'
+ self.assertEqual(path, os.path.join('C:\\', 'foo', 'bar'))
+ self.assertEqual(('C:', r'\foo\bar'), os.path.splitdrive(path))
+ self.fs.create_file(path)
+ self.assertTrue(os.path.exists(path))
+ self.assertTrue(os.path.exists(path.upper()))
+ self.assertTrue(os.path.ismount(r'\\share\foo'))
+ self.assertTrue(os.path.ismount(r'C:'))
+ self.assertEqual('\\', os.sep)
+ self.assertEqual('\\', os.path.sep)
+ self.assertEqual('/', os.altsep)
+ self.assertEqual(';', os.pathsep)
+ self.assertEqual('\r\n', os.linesep)
+ self.assertEqual('nul', os.devnull)
+
+ def test_linux(self):
+ self.fs.os = OSType.LINUX
+ path = '/foo/bar'
+ self.assertEqual(path, os.path.join('/', 'foo', 'bar'))
+ self.assertEqual(('', 'C:/foo/bar'), os.path.splitdrive('C:/foo/bar'))
+ self.fs.create_file(path)
+ self.assertTrue(os.path.exists(path))
+ self.assertFalse(os.path.exists(path.upper()))
+ self.assertTrue(os.path.ismount('/'))
+ self.assertFalse(os.path.ismount('//share/foo'))
+ self.assertEqual('/', os.sep)
+ self.assertEqual('/', os.path.sep)
+ self.assertEqual(None, os.altsep)
+ self.assertEqual(':', os.pathsep)
+ self.assertEqual('\n', os.linesep)
+ self.assertEqual('/dev/null', os.devnull)
+
+ def test_macos(self):
+ self.fs.os = OSType.MACOS
+ path = '/foo/bar'
+ self.assertEqual(path, os.path.join('/', 'foo', 'bar'))
+ self.assertEqual(('', 'C:/foo/bar'), os.path.splitdrive('C:/foo/bar'))
+ self.fs.create_file(path)
+ self.assertTrue(os.path.exists(path))
+ self.assertTrue(os.path.exists(path.upper()))
+ self.assertTrue(os.path.ismount('/'))
+ self.assertFalse(os.path.ismount('//share/foo'))
+ self.assertEqual('/', os.sep)
+ self.assertEqual('/', os.path.sep)
+ self.assertEqual(None, os.altsep)
+ self.assertEqual(':', os.pathsep)
+ self.assertEqual('\n', os.linesep)
+ self.assertEqual('/dev/null', os.devnull)
+
+ def test_drivelike_path(self):
+ self.fs.os = OSType.LINUX
+ folder = Path('/test')
+ file_path = folder / 'C:/testfile'
+ file_path.parent.mkdir(parents=True)
+ file_path.touch()
+ # use str() to be Python 3.5 compatible
+ os.chdir(str(folder))
+ self.assertTrue(os.path.exists(str(file_path.relative_to(folder))))
+
+
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
index 756d7cd..3be1076 100644
--- a/pyfakefs/tests/fake_filesystem_vs_real_test.py
+++ b/pyfakefs/tests/fake_filesystem_vs_real_test.py
@@ -23,6 +23,7 @@ import time
import unittest
from pyfakefs import fake_filesystem
+from pyfakefs.helpers import IS_PYPY
def sep(path):
@@ -181,8 +182,8 @@ class FakeFilesystemVsRealTest(TestCase):
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
+ method_call = f'{method_name}'
+ method_call += '()' if path == () else '({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):
@@ -222,7 +223,11 @@ class FakeFilesystemVsRealTest(TestCase):
if not callable(fake):
fake_method = getattr(fake, method_name)
args = [] if path == () else [path]
- fake_value = str(fake_method(*args))
+ result = fake_method(*args)
+ if isinstance(result, bytes):
+ fake_value = result.decode()
+ else:
+ fake_value = str(result)
except Exception as e: # pylint: disable-msg=W0703
fake_err = e
return fake_err, fake_value
@@ -237,7 +242,11 @@ class FakeFilesystemVsRealTest(TestCase):
real_method = real
if not callable(real):
real_method = getattr(real, method_name)
- real_value = str(real_method(*args))
+ result = real_method(*args)
+ if isinstance(result, bytes):
+ real_value = result.decode()
+ else:
+ real_value = str(result)
except Exception as e: # pylint: disable-msg=W0703
real_err = e
return real_err, real_value
@@ -337,14 +346,12 @@ class FakeFilesystemVsRealTest(TestCase):
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'
- ]
+ os_path_method_names = ['isabs', 'isdir']
if not self.is_windows:
- os_path_method_names.append('islink')
- os_path_method_names.append('lexists')
+ os_path_method_names += ['islink', 'lexists']
+ if not self.is_windows or not IS_PYPY:
+ os_path_method_names += ['isfile', 'exists']
+
wrapped_methods = [
['access', self._access_real, self._access_fake],
['stat.size', self._stat_size_real, self._stat_size_fake],
@@ -392,6 +399,50 @@ class FakeFilesystemVsRealTest(TestCase):
self.fail('Behaviors do not match for %s:\n %s' %
(path, '\n '.join(differences)))
+ def assertFileHandleOpenBehaviorsMatch(self, *args, **kwargs):
+ """Compare open() function invocation between real and fake.
+
+ Runs open(*args, **kwargs) on both real and fake.
+
+ Args:
+ *args: args to pass through to open()
+ **kwargs: kwargs to pass through to open().
+
+ Returns:
+ None.
+
+ Raises:
+ AssertionError if underlying open() behavior differs from fake.
+ """
+ real_err = None
+ fake_err = None
+ try:
+ with open(*args, **kwargs):
+ pass
+ except Exception as e: # pylint: disable-msg=W0703
+ real_err = e
+
+ try:
+ with self.fake_open(*args, **kwargs):
+ pass
+ except Exception as e: # pylint: disable-msg=W0703
+ fake_err = e
+
+ # default equal in case one is None and other is not.
+ is_exception_equal = (real_err == fake_err)
+ if real_err and fake_err:
+ # exception __eq__ doesn't evaluate equal ever, thus manual check.
+ is_exception_equal = (type(real_err) is type(fake_err) and
+ real_err.args == fake_err.args)
+
+ if not is_exception_equal:
+ msg = (
+ "Behaviors don't match on open with args %s & kwargs %s.\n" %
+ (args, kwargs))
+ real_err_msg = 'Real open results in: %s\n' % repr(real_err)
+ fake_err_msg = 'Fake open results in: %s\n' % repr(fake_err)
+ self.fail(msg + real_err_msg + fake_err_msg)
+
# Helpers for checks which are not straight method calls.
@staticmethod
def _access_real(path):
@@ -628,6 +679,18 @@ class FakeFilesystemVsRealTest(TestCase):
self.assertFileHandleBehaviorsMatch('write', 'wb', 'other contents')
self.assertFileHandleBehaviorsMatch('append', 'ab', 'other contents')
+ # binary cannot have encoding
+ self.assertFileHandleOpenBehaviorsMatch('read', 'rb', encoding='enc')
+ self.assertFileHandleOpenBehaviorsMatch(
+ 'write', mode='wb', encoding='enc')
+ self.assertFileHandleOpenBehaviorsMatch('append', 'ab', encoding='enc')
+
+ # text can have encoding
+ self.assertFileHandleOpenBehaviorsMatch('read', 'r', encoding='utf-8')
+ self.assertFileHandleOpenBehaviorsMatch('write', 'w', encoding='utf-8')
+ self.assertFileHandleOpenBehaviorsMatch(
+ 'append', 'a', encoding='utf-8')
+
def main(_):
unittest.main()
diff --git a/pyfakefs/tests/fake_open_test.py b/pyfakefs/tests/fake_open_test.py
index 5786af2..9e942f9 100644
--- a/pyfakefs/tests/fake_open_test.py
+++ b/pyfakefs/tests/fake_open_test.py
@@ -21,15 +21,25 @@ import io
import locale
import os
import stat
+import sys
import time
import unittest
from pyfakefs import fake_filesystem
-from pyfakefs.fake_filesystem import is_root, PERM_READ
+from pyfakefs.fake_filesystem import is_root, PERM_READ, FakeIoModule
+from pyfakefs.fake_filesystem_unittest import PatchMode
from pyfakefs.tests.test_utils import RealFsTestCase
class FakeFileOpenTestBase(RealFsTestCase):
+ def setUp(self):
+ super(FakeFileOpenTestBase, self).setUp()
+ if self.use_real_fs():
+ self.open = io.open
+ else:
+ self.fake_io_module = FakeIoModule(self.filesystem)
+ self.open = self.fake_io_module.open
+
def path_separator(self):
return '!'
@@ -83,13 +93,18 @@ class FakeFileOpenTest(FakeFileOpenTestBase):
# 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)
+ try:
+ with self.open(file_path, 'w') as f:
+ f.write(text_fractions)
+ except UnicodeEncodeError:
+ # see https://github.com/jmcgeheeiv/pyfakefs/issues/623
+ self.skipTest("This test does not work with an ASCII locale")
+
with self.open(file_path) as f:
contents = f.read()
self.assertEqual(contents, text_fractions)
- def test_byte_contents_py3(self):
+ 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:
@@ -103,22 +118,17 @@ class FakeFileOpenTest(FakeFileOpenTestBase):
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)
+ try:
+ with self.open(file_path, 'w') as f:
+ f.write(str_contents)
+ except UnicodeEncodeError:
+ # see https://github.com/jmcgeheeiv/pyfakefs/issues/623
+ self.skipTest("This test does not work with an ASCII locale")
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',
@@ -157,7 +167,8 @@ class FakeFileOpenTest(FakeFileOpenTestBase):
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())
+ with self.open(file_path) as f:
+ self.assertEqual(contents, f.readlines())
def test_iterate_over_file(self):
contents = [
@@ -320,8 +331,10 @@ class FakeFileOpenTest(FakeFileOpenTestBase):
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)
+ with self.assertRaises(io.UnsupportedOperation):
+ fake_file.read(0)
+ with self.assertRaises(io.UnsupportedOperation):
+ fake_file.readline()
expected_len = len(''.join(contents))
expected_len += len(contents) * (len(self.os.linesep) - 1)
self.assertEqual(expected_len, fake_file.tell())
@@ -411,7 +424,8 @@ class FakeFileOpenTest(FakeFileOpenTestBase):
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')
+ with self.assertRaises(ValueError):
+ self.open(file_path, 'INV')
def test_open_flags400(self):
# set up
@@ -437,8 +451,10 @@ class FakeFileOpenTest(FakeFileOpenTestBase):
# 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+')
+ with self.assertRaises(OSError):
+ self.open(file_path, 'r')
+ with self.assertRaises(OSError):
+ self.open(file_path, 'w+')
else:
self.open(file_path, 'r').close()
self.open(file_path, 'w+').close()
@@ -450,9 +466,12 @@ class FakeFileOpenTest(FakeFileOpenTestBase):
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+')
+ with self.assertRaises(OSError):
+ self.open(file_path, 'r')
+ with self.assertRaises(OSError):
+ self.open(file_path, 'w')
+ with self.assertRaises(OSError):
+ self.open(file_path, 'w+')
else:
self.open(file_path, 'r').close()
self.open(file_path, 'w').close()
@@ -622,22 +641,32 @@ class FakeFileOpenTest(FakeFileOpenTestBase):
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.assertRaises(OSError):
+ fh.read()
+ with 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.assertRaises(OSError):
+ fh.read()
+ with 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'])
+ with self.assertRaises(OSError):
+ fh.truncate()
+ with self.assertRaises(OSError):
+ fh.write('contents')
+ with self.assertRaises(OSError):
+ fh.writelines(['con', 'tents'])
def _iterator_open(mode):
- for _ in self.open(file_path, mode):
- pass
+ with self.open(file_path, mode) as f:
+ for _ in f:
+ pass
- self.assertRaises(OSError, _iterator_open, 'w')
- self.assertRaises(OSError, _iterator_open, 'a')
+ with self.assertRaises(OSError):
+ _iterator_open('w')
+ with self.assertRaises(OSError):
+ _iterator_open('a')
def test_open_raises_io_error_if_parent_is_file_posix(self):
self.check_posix_only()
@@ -690,12 +719,12 @@ class FakeFileOpenTest(FakeFileOpenTestBase):
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))
+ with self.open(file_path, 'w') as f0:
+ with self.open(file_path, 'w') as f1:
+ f0.write('test')
+ f0.truncate()
+ f1.flush()
+ self.assertEqual(4, self.os.path.getsize(file_path))
def test_getsize_after_truncate(self):
# Regression test for #412
@@ -736,13 +765,20 @@ class FakeFileOpenTest(FakeFileOpenTestBase):
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())
+ with self.assertRaises(ValueError):
+ fake_file.read(1)
+ with self.assertRaises(ValueError):
+ fake_file.write('a')
+ with self.assertRaises(ValueError):
+ fake_file.readline()
+ with self.assertRaises(ValueError):
+ fake_file.truncate()
+ with self.assertRaises(ValueError):
+ fake_file.tell()
+ with self.assertRaises(ValueError):
+ fake_file.seek(1)
+ with self.assertRaises(ValueError):
+ fake_file.flush()
def test_accessing_open_file_with_another_handle_raises(self):
# Regression test for #282
@@ -752,8 +788,10 @@ class FakeFileOpenTest(FakeFileOpenTestBase):
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'))
+ with self.assertRaises(ValueError):
+ fake_file.read(1)
+ with self.assertRaises(ValueError):
+ fake_file.write('a')
self.os.close(f0)
def test_tell_flushes_under_mac_os(self):
@@ -879,7 +917,7 @@ class FakeFileOpenTest(FakeFileOpenTestBase):
self.assertEqual(b'test', f.read())
def test_unicode_filename(self):
- file_path = self.make_path(u'тест')
+ file_path = self.make_path('тест')
with self.open(file_path, 'wb') as f:
f.write(b'test')
with self.open(file_path, 'rb') as f:
@@ -892,22 +930,344 @@ class FakeFileOpenTest(FakeFileOpenTestBase):
with self.open(self.os.devnull) as f:
self.assertEqual('', f.read())
+ def test_utf16_text(self):
+ # regression test for #574
+ file_path = self.make_path('foo')
+ with self.open(file_path, "w", encoding='utf-16') as f:
+ assert f.write("1") == 1
+
+ with self.open(file_path, "a", encoding='utf-16') as f:
+ assert f.write("2") == 1
+
+ with self.open(file_path, "r", encoding='utf-16') as f:
+ text = f.read()
+ assert text == "12"
+
class RealFileOpenTest(FakeFileOpenTest):
def use_real_fs(self):
return True
+@unittest.skipIf(sys.version_info < (3, 8),
+ 'open_code only present since Python 3.8')
+class FakeFilePatchedOpenCodeTest(FakeFileOpenTestBase):
+
+ def setUp(self):
+ super(FakeFilePatchedOpenCodeTest, self).setUp()
+ if self.use_real_fs():
+ self.open_code = io.open_code
+ else:
+ self.filesystem.patch_open_code = PatchMode.ON
+ self.open_code = self.fake_io_module.open_code
+
+ def tearDown(self):
+ if not self.use_real_fs():
+ self.filesystem.patch_open_code = False
+ super(FakeFilePatchedOpenCodeTest, self).tearDown()
+
+ def test_invalid_path(self):
+ with self.assertRaises(TypeError):
+ self.open_code(4)
+
+ def test_byte_contents_open_code(self):
+ byte_fractions = b'\xe2\x85\x93 \xe2\x85\x94 \xe2\x85\x95 \xe2\x85\x96'
+ file_path = self.make_path('foo')
+ self.create_file(file_path, contents=byte_fractions)
+ with self.open_code(file_path) as f:
+ contents = f.read()
+ self.assertEqual(contents, byte_fractions)
+
+ def test_open_code_in_real_fs(self):
+ self.skip_real_fs()
+ file_path = __file__
+ with self.assertRaises(OSError):
+ self.open_code(file_path)
+
+
+class RealPatchedFileOpenCodeTest(FakeFilePatchedOpenCodeTest):
+ def use_real_fs(self):
+ return True
+
+
+@unittest.skipIf(sys.version_info < (3, 8),
+ 'open_code only present since Python 3.8')
+class FakeFileUnpatchedOpenCodeTest(FakeFileOpenTestBase):
+
+ def setUp(self):
+ super(FakeFileUnpatchedOpenCodeTest, self).setUp()
+ if self.use_real_fs():
+ self.open_code = io.open_code
+ else:
+ self.open_code = self.fake_io_module.open_code
+
+ def test_invalid_path(self):
+ with self.assertRaises(TypeError):
+ self.open_code(4)
+
+ def test_open_code_in_real_fs(self):
+ file_path = __file__
+
+ with self.open_code(file_path) as f:
+ contents = f.read()
+ self.assertTrue(len(contents) > 100)
+
+
+class RealUnpatchedFileOpenCodeTest(FakeFileUnpatchedOpenCodeTest):
+ def use_real_fs(self):
+ return True
+
+ def test_byte_contents_open_code(self):
+ byte_fractions = b'\xe2\x85\x93 \xe2\x85\x94 \xe2\x85\x95 \xe2\x85\x96'
+ file_path = self.make_path('foo')
+ self.create_file(file_path, contents=byte_fractions)
+ with self.open_code(file_path) as f:
+ contents = f.read()
+ self.assertEqual(contents, byte_fractions)
+
+
+class BufferingModeTest(FakeFileOpenTestBase):
+ def test_no_buffering(self):
+ file_path = self.make_path("buffertest.bin")
+ with self.open(file_path, 'wb', buffering=0) as f:
+ f.write(b'a' * 128)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ self.assertEqual(b'a' * 128, x)
+
+ def test_no_buffering_not_allowed_in_textmode(self):
+ file_path = self.make_path("buffertest.txt")
+ with self.assertRaises(ValueError):
+ self.open(file_path, 'w', buffering=0)
+
+ def test_default_buffering_no_flush(self):
+ file_path = self.make_path("buffertest.bin")
+ with self.open(file_path, 'wb') as f:
+ f.write(b'a' * 2048)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ self.assertEqual(b'', x)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ self.assertEqual(b'a' * 2048, x)
+
+ def test_default_buffering_flush(self):
+ file_path = self.make_path("buffertest.bin")
+ with self.open(file_path, 'wb') as f:
+ f.write(b'a' * 2048)
+ f.flush()
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ self.assertEqual(b'a' * 2048, x)
+
+ def test_writing_with_specific_buffer(self):
+ file_path = self.make_path("buffertest.bin")
+ with self.open(file_path, 'wb', buffering=512) as f:
+ f.write(b'a' * 500)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ # buffer not filled - not written
+ self.assertEqual(0, len(x))
+ f.write(b'a' * 400)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ # buffer exceeded, but new buffer (400) not - previous written
+ self.assertEqual(500, len(x))
+ f.write(b'a' * 100)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ # buffer not full (500) not written
+ self.assertEqual(500, len(x))
+ f.write(b'a' * 100)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ # buffer exceeded (600) -> write previous
+ # new buffer not full (100) - not written
+ self.assertEqual(1000, len(x))
+ f.write(b'a' * 600)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ # new buffer exceeded (600) -> all written
+ self.assertEqual(1700, len(x))
+
+ def test_writing_text_with_line_buffer(self):
+ file_path = self.make_path("buffertest.bin")
+ with self.open(file_path, 'w', buffering=1) as f:
+ f.write('test' * 100)
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # no new line - not written
+ self.assertEqual(0, len(x))
+ f.write('\ntest')
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # new line - buffer written
+ self.assertEqual(405, len(x))
+ f.write('test' * 10)
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # buffer not filled - not written
+ self.assertEqual(405, len(x))
+ f.write('\ntest')
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # new line - buffer written
+ self.assertEqual(450, len(x))
+
+ def test_writing_large_text_with_line_buffer(self):
+ file_path = self.make_path("buffertest.bin")
+ with self.open(file_path, 'w', buffering=1) as f:
+ f.write('test' * 4000)
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # buffer larger than default - written
+ self.assertEqual(16000, len(x))
+ f.write('test')
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # buffer not filled - not written
+ self.assertEqual(16000, len(x))
+ f.write('\ntest')
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # new line - buffer written
+ self.assertEqual(16009, len(x))
+ f.write('\ntest')
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # another new line - buffer written
+ self.assertEqual(16014, len(x))
+
+ def test_writing_text_with_default_buffer(self):
+ file_path = self.make_path("buffertest.txt")
+ with self.open(file_path, 'w') as f:
+ f.write('test' * 5)
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # buffer not filled - not written
+ self.assertEqual(0, len(x))
+ f.write('\ntest')
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # buffer exceeded, but new buffer (400) not - previous written
+ self.assertEqual(0, len(x))
+ f.write('test' * 10)
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # buffer not filled - not written
+ self.assertEqual(0, len(x))
+ f.write('\ntest')
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ self.assertEqual(0, len(x))
+
+ def test_writing_text_with_specific_buffer(self):
+ file_path = self.make_path("buffertest.txt")
+ with self.open(file_path, 'w', buffering=2) as f:
+ f.write('a' * 8000)
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # buffer not filled - not written
+ self.assertEqual(0, len(x))
+ f.write('test')
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # buffer exceeded, but new buffer (400) not - previous written
+ self.assertEqual(0, len(x))
+ f.write('test')
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ # buffer not filled - not written
+ self.assertEqual(0, len(x))
+ f.write('test')
+ with self.open(file_path, "r") as r:
+ x = r.read()
+ self.assertEqual(0, len(x))
+ # with self.open(file_path, "r") as r:
+ # x = r.read()
+ # self.assertEqual(35, len(x))
+
+ def test_append_with_specific_buffer(self):
+ file_path = self.make_path("buffertest.bin")
+ with self.open(file_path, 'wb', buffering=512) as f:
+ f.write(b'a' * 500)
+ with self.open(file_path, 'ab', buffering=512) as f:
+ f.write(b'a' * 500)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ # buffer not filled - not written
+ self.assertEqual(500, len(x))
+ f.write(b'a' * 400)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ # buffer exceeded, but new buffer (400) not - previous written
+ self.assertEqual(1000, len(x))
+ f.write(b'a' * 100)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ # buffer not full (500) not written
+ self.assertEqual(1000, len(x))
+ f.write(b'a' * 100)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ # buffer exceeded (600) -> write previous
+ # new buffer not full (100) - not written
+ self.assertEqual(1500, len(x))
+ f.write(b'a' * 600)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ # new buffer exceeded (600) -> all written
+ self.assertEqual(2200, len(x))
+
+ def test_failed_flush_does_not_truncate_file(self):
+ # regression test for #548
+ self.skip_real_fs() # cannot set fs size in real fs
+ self.filesystem.set_disk_usage(100)
+ self.os.makedirs("foo")
+ file_path = self.os.path.join('foo', 'bar.txt')
+ with self.open(file_path, 'wb') as f:
+ f.write(b'a' * 50)
+ f.flush()
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ self.assertTrue(x.startswith(b'a' * 50))
+ with self.assertRaises(OSError):
+ f.write(b'b' * 200)
+ f.flush()
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ self.assertTrue(x.startswith(b'a' * 50))
+ f.truncate(50)
+
+ def test_failed_write_does_not_truncate_file(self):
+ # test the same with no buffering and no flush
+ self.skip_real_fs() # cannot set fs size in real fs
+ self.filesystem.set_disk_usage(100)
+ self.os.makedirs("foo")
+ file_path = self.os.path.join('foo', 'bar.txt')
+ with self.open(file_path, 'wb', buffering=0) as f:
+ f.write(b'a' * 50)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ self.assertEqual(b'a' * 50, x)
+ with self.assertRaises(OSError):
+ f.write(b'b' * 200)
+ with self.open(file_path, "rb") as r:
+ x = r.read()
+ self.assertEqual(b'a' * 50, x)
+
+
+class RealBufferingTest(BufferingModeTest):
+ def use_real_fs(self):
+ return True
+
+
class OpenFileWithEncodingTest(FakeFileOpenTestBase):
"""Tests that are similar to some open file tests above but using
an explicit text encoding."""
def setUp(self):
super(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):
@@ -921,7 +1281,8 @@ class OpenFileWithEncodingTest(FakeFileOpenTestBase):
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.assertRaises(UnicodeEncodeError):
+ f.write(str_contents)
with self.open(self.file_path, 'w', encoding='ascii',
errors='xmlcharrefreplace') as f:
@@ -949,7 +1310,8 @@ class OpenFileWithEncodingTest(FakeFileOpenTestBase):
# default strict encoding
with self.open(self.file_path, encoding='ascii') as f:
- self.assertRaises(UnicodeDecodeError, f.read)
+ with self.assertRaises(UnicodeDecodeError):
+ f.read()
with self.open(self.file_path, encoding='ascii',
errors='replace') as f:
contents = f.read()
@@ -1021,8 +1383,10 @@ class OpenFileWithEncodingTest(FakeFileOpenTestBase):
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)
+ with self.assertRaises(io.UnsupportedOperation):
+ fake_file.read(0)
+ with self.assertRaises(io.UnsupportedOperation):
+ fake_file.readline()
self.assertEqual(len(''.join(contents)), fake_file.tell())
fake_file.seek(0)
self.assertEqual(0, fake_file.tell())
@@ -1070,13 +1434,13 @@ class FakeFileOpenLineEndingTest(FakeFileOpenTestBase):
def setUp(self):
super(FakeFileOpenLineEndingTest, self).setUp()
- def test_read_universal_newline_mode(self):
+ def test_read_default_newline_mode(self):
file_path = self.make_path('some_file')
for contents in (b'1\n2', b'1\r\n2', b'1\r2'):
self.create_file(file_path, contents=contents)
- with self.open(file_path, mode='rU') as f:
+ with self.open(file_path, mode='r') as f:
self.assertEqual(['1\n', '2'], f.readlines())
- with self.open(file_path, mode='rU') as f:
+ with self.open(file_path, mode='r') as f:
self.assertEqual('1\n2', f.read())
with self.open(file_path, mode='rb') as f:
self.assertEqual(contents, f.read())
@@ -1183,19 +1547,15 @@ class RealFileOpenLineEndingTest(FakeFileOpenLineEndingTest):
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):
+ def test_read_standard_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',
+ with self.open(file_path, mode='r',
encoding='cyrillic') as fake_file:
self.assertEqual([u'раз\n', u'два'], fake_file.readlines())
- with self.open(file_path, mode='rU',
+ with self.open(file_path, mode='r',
encoding='cyrillic') as fake_file:
self.assertEqual(u'раз\nдва', fake_file.read())
@@ -1346,25 +1706,26 @@ class OpenWithBinaryFlagsTest(OpenWithFlagsTestBase):
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())
+ with self.open_file('rb') as fake_file:
+ 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)
+ with self.open_file_and_seek('wb') as f:
+ self.assertEqual(0, f.tell())
+ with self.write_and_reopen_file(f, mode='rb') as f1:
+ self.assertEqual(self.file_contents, f1.read())
+ # Attempt to reopen the file in text mode
+ with self.open_file('wb') as f2:
+ with self.write_and_reopen_file(f2, mode='r',
+ encoding='ascii') as f3:
+ with self.assertRaises(UnicodeDecodeError):
+ f3.read()
def test_write_and_read_binary(self):
- 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())
+ with self.open_file_and_seek('w+b') as f:
+ self.assertEqual(0, f.tell())
+ with self.write_and_reopen_file(f, mode='rb') as f1:
+ self.assertEqual(self.file_contents, f1.read())
class RealOpenWithBinaryFlagsTest(OpenWithBinaryFlagsTest):
@@ -1393,7 +1754,8 @@ class OpenWithTextModeFlagsTest(OpenWithFlagsTestBase):
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')
+ with self.assertRaises(ValueError):
+ self.open_file_and_seek('w+bt')
class RealOpenWithTextModeFlagsTest(OpenWithTextModeFlagsTest):
@@ -1403,19 +1765,24 @@ class RealOpenWithTextModeFlagsTest(OpenWithTextModeFlagsTest):
class OpenWithInvalidFlagsTest(FakeFileOpenTestBase):
def test_capital_r(self):
- self.assertRaises(ValueError, self.open, 'some_file', 'R')
+ with self.assertRaises(ValueError):
+ self.open('some_file', 'R')
def test_capital_w(self):
- self.assertRaises(ValueError, self.open, 'some_file', 'W')
+ with self.assertRaises(ValueError):
+ self.open('some_file', 'W')
def test_capital_a(self):
- self.assertRaises(ValueError, self.open, 'some_file', 'A')
+ with self.assertRaises(ValueError):
+ self.open('some_file', 'A')
def test_lower_u(self):
- self.assertRaises(ValueError, self.open, 'some_file', 'u')
+ with self.assertRaises(ValueError):
+ self.open('some_file', 'u')
def test_lower_rw(self):
- self.assertRaises(ValueError, self.open, 'some_file', 'rw')
+ with self.assertRaises(ValueError):
+ self.open('some_file', 'rw')
class OpenWithInvalidFlagsRealFsTest(OpenWithInvalidFlagsTest):
@@ -1429,10 +1796,12 @@ class ResolvePathTest(FakeFileOpenTestBase):
fh.write('x')
def test_none_filepath_raises_type_error(self):
- self.assertRaises(TypeError, self.open, None, 'w')
+ with self.assertRaises(TypeError):
+ self.open(None, 'w')
def test_empty_filepath_raises_io_error(self):
- self.assertRaises(OSError, self.open, '', 'w')
+ with self.assertRaises(OSError):
+ self.open('', 'w')
def test_normal_path(self):
file_path = self.make_path('foo')
@@ -1547,7 +1916,8 @@ class ResolvePathTest(FakeFileOpenTestBase):
if self.is_pypy:
# unclear behavior with PyPi
self.skip_real_fs()
- self.assert_raises_os_error(errno.EBADF, self.os.chdir, 10)
+ self.assert_raises_os_error(
+ [errno.ENOTDIR, errno.EBADF], self.os.chdir, 500)
dir_path = self.make_path('foo', 'bar')
self.create_dir(dir_path)
diff --git a/pyfakefs/tests/fake_os_test.py b/pyfakefs/tests/fake_os_test.py
index 4b24dee..7408d17 100644
--- a/pyfakefs/tests/fake_os_test.py
+++ b/pyfakefs/tests/fake_os_test.py
@@ -20,10 +20,9 @@ import errno
import os
import stat
import sys
-import time
import unittest
-from pyfakefs.helpers import IN_DOCKER
+from pyfakefs.helpers import IN_DOCKER, IS_PYPY
from pyfakefs import fake_filesystem
from pyfakefs.fake_filesystem import FakeFileOpen, is_root
@@ -31,7 +30,7 @@ from pyfakefs.extra_packages import (
use_scandir, use_scandir_package, use_builtin_scandir
)
-from pyfakefs.tests.test_utils import DummyTime, TestCase, RealFsTestCase
+from pyfakefs.tests.test_utils import TestCase, RealFsTestCase
class FakeOsModuleTestBase(RealFsTestCase):
@@ -177,12 +176,14 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
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')
+ with self.assertRaises(TypeError):
+ self.os.fdopen(None)
+ with self.assertRaises(TypeError):
+ self.os.fdopen('a string')
def test_out_of_range_fdopen(self):
# test some file descriptor that is clearly out of range
- self.assert_raises_os_error(errno.EBADF, self.os.fdopen, 100)
+ self.assert_raises_os_error(errno.EBADF, self.os.fdopen, 500)
def test_closed_file_descriptor(self):
first_path = self.make_path('some_file1')
@@ -221,7 +222,8 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
self.os.fdopen(fileno1)
self.os.fdopen(fileno1, 'r')
if not is_root():
- self.assertRaises(OSError, self.os.fdopen, fileno1, 'w')
+ with self.assertRaises(OSError):
+ self.os.fdopen(fileno1, 'w')
else:
self.os.fdopen(fileno1, 'w')
self.os.close(fileno1)
@@ -245,6 +247,28 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
self.assertTrue(stat.S_IFREG & self.os.stat(file_path).st_mode)
self.assertEqual(5, self.os.stat(file_path)[stat.ST_SIZE])
+ def test_stat_with_unc_path(self):
+ self.skip_real_fs()
+ self.check_windows_only()
+ directory = '//root/share/dir'
+ file_path = self.os.path.join(directory, 'plugh')
+ self.create_file(file_path, contents='ABCDE')
+ self.assertTrue(stat.S_IFDIR & self.os.stat(directory)[stat.ST_MODE])
+ self.assertTrue(stat.S_IFREG & self.os.stat(file_path)[stat.ST_MODE])
+ self.assertTrue(stat.S_IFREG & self.os.stat(file_path).st_mode)
+ self.assertEqual(5, self.os.stat(file_path)[stat.ST_SIZE])
+
+ def test_stat_with_drive(self):
+ self.skip_real_fs()
+ self.check_windows_only()
+ directory = 'C:/foo/dir'
+ file_path = self.os.path.join(directory, 'plugh')
+ self.create_file(file_path, contents='ABCDE')
+ self.assertTrue(stat.S_IFDIR & self.os.stat(directory)[stat.ST_MODE])
+ self.assertTrue(stat.S_IFREG & self.os.stat(file_path)[stat.ST_MODE])
+ self.assertTrue(stat.S_IFREG & self.os.stat(file_path).st_mode)
+ self.assertEqual(5, self.os.stat(file_path)[stat.ST_SIZE])
+
def test_stat_uses_open_fd_as_path(self):
self.skip_real_fs()
self.assert_raises_os_error(errno.EBADF, self.os.stat, 5)
@@ -329,10 +353,10 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
def test_lstat_trailing_sep(self):
# regression test for #342
- stat = self.os.lstat(self.base_path)
- self.assertEqual(stat,
+ stat_result = self.os.lstat(self.base_path)
+ self.assertEqual(stat_result,
self.os.lstat(self.base_path + self.path_separator()))
- self.assertEqual(stat, self.os.lstat(
+ self.assertEqual(stat_result, self.os.lstat(
self.base_path + self.path_separator() + self.path_separator()))
def test_stat_with_byte_string(self):
@@ -544,6 +568,11 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
self.create_file(file_path)
self.assertFalse(self.os.path.isfile(file_path + self.os.sep))
+ def test_isfile_not_readable_file(self):
+ file_path = self.make_path('foo')
+ self.create_file(file_path, perm=0)
+ self.assertTrue(self.os.path.isfile(file_path))
+
def check_stat_with_trailing_separator(self, error_nr):
# regression test for #376
file_path = self.make_path('foo')
@@ -618,7 +647,8 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
def test_readlink_raises_if_path_is_none(self):
self.skip_if_symlink_not_supported()
- self.assertRaises(TypeError, self.os.readlink, None)
+ with self.assertRaises(TypeError):
+ self.os.readlink(None)
def test_broken_symlink_with_trailing_separator_linux(self):
self.check_linux_only()
@@ -1115,7 +1145,8 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
# 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)
+ with self.assertRaises(OSError):
+ self.os.rename(old_path, new_path)
def test_rename_to_another_device_should_raise(self):
"""Renaming to another filesystem device raises OSError."""
@@ -1702,7 +1733,8 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
directory = self.make_path('a', 'b')
if not is_root():
- self.assertRaises(Exception, self.os.makedirs, directory)
+ with self.assertRaises(Exception):
+ self.os.makedirs(directory)
else:
self.os.makedirs(directory)
self.assertTrue(self.os.path.exists(directory))
@@ -1739,20 +1771,21 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
# test fsync and fdatasync
def test_fsync_raises_on_non_int(self):
- self.assertRaises(TypeError, self.os.fsync, "zero")
+ with self.assertRaises(TypeError):
+ self.os.fsync("zero")
def test_fdatasync_raises_on_non_int(self):
self.check_linux_only()
self.assertRaises(TypeError, self.os.fdatasync, "zero")
def test_fsync_raises_on_invalid_fd(self):
- self.assert_raises_os_error(errno.EBADF, self.os.fsync, 100)
+ self.assert_raises_os_error(errno.EBADF, self.os.fsync, 500)
def test_fdatasync_raises_on_invalid_fd(self):
# No open files yet
self.check_linux_only()
self.assert_raises_os_error(errno.EINVAL, self.os.fdatasync, 0)
- self.assert_raises_os_error(errno.EBADF, self.os.fdatasync, 100)
+ self.assert_raises_os_error(errno.EBADF, self.os.fdatasync, 500)
def test_fsync_pass_posix(self):
self.check_posix_only()
@@ -1764,7 +1797,7 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
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)
+ self.os.fsync, test_fd + 500)
def test_fsync_pass_windows(self):
self.check_windows_only()
@@ -1776,7 +1809,7 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
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)
+ self.os.fsync, test_fd + 500)
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)
@@ -1792,7 +1825,7 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
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)
+ self.os.fdatasync, test_fd + 500)
def test_access700(self):
# set up
@@ -1889,6 +1922,12 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
self.assertFalse(self.os.access(path, self.rwx))
self.assertFalse(self.os.access(path, self.rw))
+ def test_effective_ids_not_supported_under_windows(self):
+ self.check_windows_only()
+ path = self.make_path('foo', 'bar')
+ with self.assertRaises(NotImplementedError):
+ self.os.access(path, self.os.F_OK, effective_ids=True)
+
def test_chmod(self):
# set up
self.check_posix_only()
@@ -1916,8 +1955,6 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
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')
@@ -1927,22 +1964,24 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
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)
+ # the exact mode depends on OS and Python version
+ self.assertEqual(stat.S_IMODE(0o700), stat.S_IMODE(st.st_mode) & 0o700)
def test_chmod_no_follow_symlink(self):
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)
+ if os.chmod not in os.supports_follow_symlinks or IS_PYPY:
+ with self.assertRaises(NotImplementedError):
+ self.os.chmod(link_path, 0o6543, follow_symlinks=False)
+ else:
+ self.os.chmod(link_path, 0o6543, follow_symlinks=False)
+ st = self.os.stat(link_path)
+ 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."""
@@ -2144,6 +2183,9 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
def test_mknod_raises_if_unsupported_options(self):
self.check_posix_only()
+ # behavior seems to have changed in ubuntu-20.04, version 20210606.1
+ # skipping real fs tests for now
+ self.skip_real_fs()
filename = 'abcde'
if not is_root():
self.assert_raises_os_error(errno.EPERM, self.os.mknod, filename,
@@ -2687,6 +2729,31 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
self.os.close(write_fd)
self.os.close(fd)
+ def test_read_write_pipe(self):
+ read_fd, write_fd = self.os.pipe()
+ self.assertEqual(4, self.os.write(write_fd, b'test'))
+ self.assertEqual(b'test', self.os.read(read_fd, 4))
+ self.os.close(read_fd)
+ self.os.close(write_fd)
+
+ def test_open_existing_pipe(self):
+ # create some regular files to ensure that real and fake fd
+ # are out of sync (see #581)
+ fds = []
+ for i in range(5):
+ path = self.make_path('file' + str(i))
+ fds.append(self.os.open(path, os.O_CREAT))
+ file_path = self.make_path('file.txt')
+ self.create_file(file_path)
+ with self.open(file_path):
+ read_fd, write_fd = self.os.pipe()
+ with self.open(write_fd, 'wb') as f:
+ self.assertEqual(4, f.write(b'test'))
+ with self.open(read_fd, 'rb') as f:
+ self.assertEqual(b'test', f.read())
+ for fd in fds:
+ self.os.close(fd)
+
def test_write_to_pipe(self):
read_fd, write_fd = self.os.pipe()
self.os.write(write_fd, b'test')
@@ -2701,6 +2768,52 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
self.os.close(read_fd)
self.os.close(write_fd)
+ def test_truncate(self):
+ file_path = self.make_path('foo', 'bar')
+ self.create_file(file_path, contents='012345678901234567')
+ self.os.truncate(file_path, 10)
+ with self.open(file_path) as f:
+ self.assertEqual('0123456789', f.read())
+
+ def test_truncate_non_existing(self):
+ self.assert_raises_os_error(errno.ENOENT, self.os.truncate, 'foo', 10)
+
+ def test_truncate_to_larger(self):
+ file_path = self.make_path('foo', 'bar')
+ self.create_file(file_path, contents='0123456789')
+ fd = self.os.open(file_path, os.O_RDWR)
+ self.os.truncate(fd, 20)
+ self.assertEqual(20, self.os.stat(file_path).st_size)
+ with self.open(file_path) as f:
+ self.assertEqual('0123456789' + '\0' * 10, f.read())
+
+ def test_truncate_with_fd(self):
+ if os.truncate not in os.supports_fd:
+ self.skip_real_fs()
+ self.assert_raises_os_error(errno.EBADF, self.os.ftruncate, 50, 10)
+ file_path = self.make_path('some_file')
+ self.create_file(file_path, contents='01234567890123456789')
+
+ fd = self.os.open(file_path, os.O_RDWR)
+ self.os.truncate(fd, 10)
+ self.assertEqual(10, self.os.stat(file_path).st_size)
+ with self.open(file_path) as f:
+ self.assertEqual('0123456789', f.read())
+
+ def test_ftruncate(self):
+ if self.is_pypy:
+ # not correctly supported
+ self.skip_real_fs()
+ self.assert_raises_os_error(errno.EBADF, self.os.ftruncate, 50, 10)
+ file_path = self.make_path('some_file')
+ self.create_file(file_path, contents='0123456789012345')
+
+ fd = self.os.open(file_path, os.O_RDWR)
+ self.os.truncate(fd, 10)
+ self.assertEqual(10, self.os.stat(file_path).st_size)
+ with self.open(file_path) as f:
+ self.assertEqual('0123456789', f.read())
+
class RealOsModuleTest(FakeOsModuleTest):
def use_real_fs(self):
@@ -2991,6 +3104,8 @@ class FakeOsModuleTestCaseInsensitiveFS(FakeOsModuleTestBase):
# Regression test for #317
self.check_posix_only()
dest_dir_path = self.make_path('Dest')
+ # seems to behave differently under different MacOS versions
+ self.skip_real_fs()
new_dest_dir_path = self.make_path('dest')
self.os.mkdir(dest_dir_path)
source_dir_path = self.make_path('src')
@@ -3537,6 +3652,7 @@ class FakeOsModuleTestCaseInsensitiveFS(FakeOsModuleTestBase):
self.os.fsync(test_fd)
# And just for sanity, double-check that this still raises
self.assert_raises_os_error(errno.EBADF, self.os.fsync, test_fd + 10)
+ test_file.close()
def test_chmod(self):
# set up
@@ -3615,118 +3731,107 @@ class RealOsModuleTestCaseInsensitiveFS(FakeOsModuleTestCaseInsensitiveFS):
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()
+ with self.mock_time(start=200):
+ file_path = 'some_file'
+ self.filesystem.create_file(file_path)
+ self.assertTrue(self.os.path.exists(file_path))
- st = self.os.stat(file_path)
- self.assertEqual(200, st.st_ctime)
- # tests
- self.os.chmod(file_path, 0o765)
- st = self.os.stat(file_path)
- self.assertEqual(220, st.st_ctime)
+ 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)
+ with self.mock_time(start=200):
+ self.os.utime(path, times=None)
+ st = self.os.stat(path)
+ self.assertEqual(200, st.st_atime)
+ self.assertEqual(200, st.st_mtime)
def test_utime_sets_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'
+ stat_float_times = fake_filesystem.FakeOsModule.stat_float_times()
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)
+ try:
+ with self.mock_time(start=200.9124):
+ path = '/some_file'
+ self.createTestFile(path)
+
+ 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(220, st.st_mtime)
+ self.assertTrue(isinstance(st.st_mtime, int))
+
+ self.assertEqual(200912400000, st.st_atime_ns)
+ self.assertEqual(220912400000, st.st_mtime_ns)
+
+ self.assertEqual(220, st.st_mtime)
+ self.assertEqual(240, st.st_ctime)
+ # actual tests
+ self.os.utime(path, times=None)
+ st = self.os.stat(path)
+ self.assertEqual(260, st.st_atime)
+ self.assertTrue(isinstance(st.st_atime, int))
+ self.assertEqual(260, st.st_mtime)
+ self.assertTrue(isinstance(st.st_mtime, int))
+ self.assertEqual(260912400000, st.st_atime_ns)
+ self.assertEqual(260912400000, st.st_mtime_ns)
+ finally:
+ fake_filesystem.FakeOsModule.stat_float_times(stat_float_times)
def test_utime_sets_current_time_if_args_is_none_with_floats_n_sec(self):
+ stat_float_times = fake_filesystem.FakeOsModule.stat_float_times()
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)
+ try:
+ with self.mock_time(start=200.9123):
+ path = self.make_path('some_file')
+ self.createTestFile(path)
+ test_file = self.filesystem.get_object(path)
+
+ st = self.os.stat(path)
+ self.assertEqual(200, st.st_atime)
+ self.assertEqual(220, st.st_mtime)
+ self.assertEqual(240, st.st_ctime)
+ self.assertEqual(240, 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(240, st.st_ctime) # st does not change
+ self.assertEqual(240.9123, test_file.st_ctime) # but the file
+ 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(240, test_file.st_ctime)
+ self.assertTrue(isinstance(test_file.st_ctime, int))
+
+ self.assertEqual(240, st.st_ctime)
+ self.assertTrue(isinstance(st.st_ctime, int))
+
+ self.os.stat_float_times(True)
+ st = self.os.stat(path)
+ # float time not converted to int
+ self.assertAlmostEqual(200.9123, st.st_atime)
+ self.assertAlmostEqual(220.9123, st.st_mtime)
+ self.assertAlmostEqual(240.9123, test_file.st_ctime,
+ st.st_ctime)
+ self.os.utime(path, times=None)
+ st = self.os.stat(path)
+ self.assertAlmostEqual(260.9123, st.st_atime)
+ self.assertAlmostEqual(260.9123, st.st_mtime)
+ finally:
+ fake_filesystem.FakeOsModule.stat_float_times(stat_float_times)
def test_utime_sets_specified_time(self):
# set up
@@ -3792,7 +3897,6 @@ class FakeOsModuleTimeTest(FakeOsModuleTestBase):
# set up
path = self.make_path('some_file')
self.createTestFile(path)
- self.dummy_time.start()
self.os.stat(path)
# actual tests
@@ -4025,6 +4129,7 @@ class FakeOsModuleLowLevelFileOpTest(FakeOsModuleTestBase):
file_des = self.os.open(file_path, os.O_WRONLY | os.O_CREAT, 0o442)
self.assert_mode_equal(0o444, self.os.stat(file_path).st_mode)
self.os.close(file_des)
+ self.os.chmod(file_path, 0o666)
def testOpenCreateMode666Windows(self):
self.check_windows_only()
@@ -4093,6 +4198,7 @@ class FakeOsModuleLowLevelFileOpTest(FakeOsModuleTestBase):
self.create_dir(dir_path)
file_des = self.os.open(dir_path, os.O_RDONLY)
self.assertEqual(3, file_des)
+ self.os.close(file_des)
def test_opening_existing_directory_in_creation_mode(self):
self.check_linux_only()
@@ -4500,6 +4606,20 @@ class FakeOsModuleWalkTest(FakeOsModuleTestBase):
self.os.path.join(base_dir, 'created_link'),
followlinks=True)
+ def test_walk_linked_file_in_subdir(self):
+ # regression test for #559 (tested for link on incomplete path)
+ self.check_posix_only()
+ # need to have a top-level link to reproduce the bug - skip real fs
+ self.skip_real_fs()
+ file_path = '/foo/bar/baz'
+ self.create_file(file_path)
+ self.create_symlink('bar', file_path)
+ expected = [
+ ('/foo', ['bar'], []),
+ ('/foo/bar', [], ['baz'])
+ ]
+ self.assertWalkResults(expected, '/foo')
+
def test_base_dirpath(self):
# regression test for #512
file_path = self.make_path('foo', 'bar', 'baz')
@@ -4513,14 +4633,12 @@ class FakeOsModuleWalkTest(FakeOsModuleTestBase):
]
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))
@@ -4592,6 +4710,7 @@ class FakeOsModuleDirFdTest(FakeOsModuleTestBase):
self.assertTrue(self.os.path.exists('/bat'))
def test_readlink(self):
+ self.skip_if_symlink_not_supported()
self.filesystem.create_symlink('/meyer/lemon/pie', '/foo/baz')
self.filesystem.create_symlink('/geo/metro', '/meyer')
self.assertRaises(
@@ -4874,7 +4993,7 @@ class FakeScandirTest(FakeOsModuleTestBase):
def tearDown(self):
self.os.chdir(self.pretest_cwd)
- super(FakeScandirTest, self).tearDown()
+ super().tearDown()
def do_scandir(self):
"""Hook to override how scandir is called."""
@@ -5152,6 +5271,9 @@ class FakeExtendedAttributeTest(FakeOsModuleTestBase):
class FakeOsUnreadableDirTest(FakeOsModuleTestBase):
def setUp(self):
+ if self.use_real_fs():
+ # make sure no dir is created if skipped
+ self.check_posix_only()
super(FakeOsUnreadableDirTest, self).setUp()
self.check_posix_only()
self.dir_path = self.make_path('some_dir')
diff --git a/pyfakefs/tests/fake_pathlib_test.py b/pyfakefs/tests/fake_pathlib_test.py
index 7309cdb..1a3cdfc 100644
--- a/pyfakefs/tests/fake_pathlib_test.py
+++ b/pyfakefs/tests/fake_pathlib_test.py
@@ -22,38 +22,28 @@ python docs.
import errno
import os
+import pathlib
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.helpers import IS_PYPY
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.pathlib = pathlib
self.path = None
def setUp(self):
- super(RealPathlibTestCase, self).setUp()
+ super().setUp()
if not self.use_real_fs():
self.pathlib = fake_pathlib.FakePathlibModule(self.filesystem)
self.path = self.pathlib.Path
@@ -67,13 +57,21 @@ class FakePathlibInitializationTest(RealPathlibTestCase):
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)
+ # in fake fs, we allow to use the other OS implementation
+ if self.use_real_fs():
+ with self.assertRaises(NotImplementedError):
+ self.pathlib.PosixPath()
+ else:
+ self.assertTrue(self.pathlib.PosixPath())
else:
self.assertTrue(isinstance(path, self.pathlib.PosixPath))
self.assertTrue(isinstance(path, self.pathlib.PurePosixPath))
self.assertTrue(self.pathlib.PureWindowsPath())
- self.assertRaises(NotImplementedError,
- self.pathlib.WindowsPath)
+ if self.use_real_fs():
+ with self.assertRaises(NotImplementedError):
+ self.pathlib.WindowsPath()
+ else:
+ self.assertTrue(self.pathlib.WindowsPath())
def test_init_with_segments(self):
"""Basic initialization tests - taken from pathlib.Path documentation
@@ -228,16 +226,23 @@ class FakePathlibPurePathTest(RealPathlibTestCase):
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')
+ with self.assertRaises(ValueError):
+ self.path('passwd').relative_to('/usr')
+
+ @unittest.skipIf(sys.version_info < (3, 9),
+ 'is_relative_to new in Python 3.9')
+ def test_is_relative_to(self):
+ path = self.path('/etc/passwd')
+ self.assertTrue(path.is_relative_to('/etc'))
+ self.assertFalse(path.is_relative_to('/src'))
def test_with_name(self):
self.check_windows_only()
self.assertEqual(
self.path('c:/Downloads/pathlib.tar.gz').with_name('setup.py'),
self.path('c:/Downloads/setup.py'))
- self.assertRaises(ValueError, self.path('c:/').with_name,
- 'setup.py')
+ with self.assertRaises(ValueError):
+ self.path('c:/').with_name('setup.py')
def test_with_suffix(self):
self.assertEqual(
@@ -374,42 +379,52 @@ class FakePathlibFileObjectPropertyTest(RealPathlibTestCase):
# 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)
+ with 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)
+ # the exact mode depends on OS and Python version
+ self.assertEqual(link_stat.st_mode & 0o777700,
+ stat.S_IFLNK | 0o700)
+
+ @unittest.skipIf(sys.version_info < (3, 10),
+ "follow_symlinks argument new in Python 3.10")
+ def test_chmod_no_followsymlinks(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 os.chmod not in os.supports_follow_symlinks or IS_PYPY:
+ with self.assertRaises(NotImplementedError):
+ self.path(self.file_link_path).chmod(0o444,
+ follow_symlinks=False)
+ else:
+ self.path(self.file_link_path).chmod(0o444, follow_symlinks=False)
+ self.assertEqual(file_stat.st_mode, stat.S_IFREG | 0o666)
+ # the exact mode depends on OS and Python version
+ self.assertEqual(link_stat.st_mode & 0o777700,
+ stat.S_IFLNK | 0o700)
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.assert_equal_paths(self.path().resolve(),
+ self.path(self.os.path.realpath(
+ self.make_path('antoine'))))
+ self.assert_equal_paths(
self.path(
self.os.path.join('docs', '..', 'setup.py')).resolve(),
self.path(
self.os.path.realpath(
self.make_path('antoine', 'setup.py'))))
- def test_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')
@@ -435,25 +450,7 @@ class FakePathlibFileObjectPropertyTest(RealPathlibTestCase):
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()
+ def test_resolve_nonexisting_file(self):
path = self.path(
self.make_path('/path', 'to', 'file', 'this can not exist'))
self.assertEqual(path, path.resolve())
@@ -462,8 +459,8 @@ class FakePathlibFileObjectPropertyTest(RealPathlibTestCase):
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)))
+ self.assert_equal_paths(self.path.cwd(),
+ self.path(self.os.path.realpath(dir_path)))
def test_expanduser(self):
if is_windows:
@@ -477,13 +474,12 @@ class FakePathlibFileObjectPropertyTest(RealPathlibTestCase):
def test_home(self):
if is_windows:
- self.assertEqual(self.path.home(),
- self.path(
- os.environ['USERPROFILE'].replace('\\',
- '/')))
+ self.assertEqual(self.path(
+ os.environ['USERPROFILE'].replace('\\', '/')),
+ self.path.home())
else:
- self.assertEqual(self.path.home(),
- self.path(os.environ['HOME']))
+ self.assertEqual(self.path(os.environ['HOME']),
+ self.path.home())
class RealPathlibFileObjectPropertyTest(FakePathlibFileObjectPropertyTest):
@@ -513,8 +509,8 @@ class FakePathlibPathFileOperationTest(RealPathlibTestCase):
def test_open(self):
self.create_dir(self.make_path('foo'))
- self.assertRaises(OSError,
- self.path(self.make_path('foo', 'bar.txt')).open)
+ with 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')))
@@ -545,6 +541,19 @@ class FakePathlibPathFileOperationTest(RealPathlibTestCase):
self.assertTrue(self.os.path.exists(path_name))
self.check_contents(path_name, 'ανοησίες'.encode('greek'))
+ @unittest.skipIf(sys.version_info < (3, 10),
+ "newline argument new in Python 3.10")
+ def test_write_with_newline_arg(self):
+ path = self.path(self.make_path('some_file'))
+ path.write_text('1\r\n2\n3\r4', newline='')
+ self.check_contents(path, b'1\r\n2\n3\r4')
+ path.write_text('1\r\n2\n3\r4', newline='\n')
+ self.check_contents(path, b'1\r\n2\n3\r4')
+ path.write_text('1\r\n2\n3\r4', newline='\r\n')
+ self.check_contents(path, b'1\r\r\n2\r\n3\r4')
+ path.write_text('1\r\n2\n3\r4', newline='\r')
+ self.check_contents(path, b'1\r\r2\r3\r4')
+
def test_read_bytes(self):
path_name = self.make_path('binary_file')
self.create_file(path_name, contents=b'Binary file contents')
@@ -590,6 +599,7 @@ class FakePathlibPathFileOperationTest(RealPathlibTestCase):
self.check_contents(file_name, '')
self.assertTrue(self.os.stat(file_name).st_mode,
stat.S_IFREG | 0o444)
+ self.os.chmod(file_name, mode=0o666)
def test_touch_existing(self):
file_name = self.make_path('foo', 'bar.txt')
@@ -605,14 +615,15 @@ class FakePathlibPathFileOperationTest(RealPathlibTestCase):
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'))
+ with self.assertRaises(OSError):
+ self.path(self.make_path('foo', 'other')).samefile(
+ self.make_path('foo', 'other.txt'))
path = self.path(file_name)
other_name = self.make_path('foo', 'other.txt')
- self.assertRaises(OSError, path.samefile, other_name)
- self.assertRaises(OSError, path.samefile, self.path(other_name))
+ with self.assertRaises(OSError):
+ path.samefile(other_name)
+ with self.assertRaises(OSError):
+ path.samefile(self.path(other_name))
self.assertFalse(path.samefile(file_name2))
self.assertFalse(path.samefile(self.path(file_name2)))
self.assertTrue(
@@ -628,12 +639,46 @@ class FakePathlibPathFileOperationTest(RealPathlibTestCase):
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())
+ @unittest.skipIf(sys.version_info < (3, 8),
+ 'link_to new in Python 3.8')
+ def test_link_to(self):
+ self.skip_if_symlink_not_supported()
+ file_name = self.make_path('foo', 'bar.txt')
+ self.create_file(file_name)
+ self.assertEqual(1, self.os.stat(file_name).st_nlink)
+ link_name = self.make_path('link_to_bar')
+ path = self.path(file_name)
+ path.link_to(link_name)
+ self.assertTrue(self.os.path.exists(link_name))
+ self.assertFalse(path.is_symlink())
+ self.assertEqual(2, self.os.stat(file_name).st_nlink)
+
+ @unittest.skipIf(sys.version_info < (3, 10),
+ 'hardlink_to new in Python 3.10')
+ def test_hardlink_to(self):
+ self.skip_if_symlink_not_supported()
+ file_name = self.make_path('foo', 'bar.txt')
+ self.create_file(file_name)
+ self.assertEqual(1, self.os.stat(file_name).st_nlink)
+ link_path = self.path(self.make_path('link_to_bar'))
+ path = self.path(file_name)
+ link_path.hardlink_to(path)
+ self.assertTrue(self.os.path.exists(link_path))
+ self.assertFalse(path.is_symlink())
+ self.assertEqual(2, self.os.stat(file_name).st_nlink)
+
+ @unittest.skipIf(sys.version_info < (3, 9),
+ 'readlink new in Python 3.9')
+ def test_readlink(self):
+ self.skip_if_symlink_not_supported()
+ link_path = self.make_path('foo', 'bar', 'baz')
+ target = self.make_path('tarJAY')
+ self.create_symlink(link_path, target)
+ path = self.path(link_path)
+ self.assert_equal_paths(path.readlink(), self.path(target))
+
def test_mkdir(self):
dir_name = self.make_path('foo', 'bar')
self.assert_raises_os_error(errno.ENOENT,
@@ -660,7 +705,8 @@ class FakePathlibPathFileOperationTest(RealPathlibTestCase):
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)
+ with self.assertRaises(OSError):
+ self.path(self.make_path('foo')).rmdir()
self.assertTrue(self.os.path.exists(self.make_path('foo')))
def test_iterdir(self):
@@ -831,6 +877,12 @@ class FakePathlibUsageInOsFunctionsTest(RealPathlibTestCase):
self.assertEqual(self.os.path.isfile(path),
self.os.path.isfile(self.path(path)))
+ def test_isfile_not_readable(self):
+ path = self.make_path('foo', 'bar', 'baz')
+ self.create_file(path, perm=0)
+ self.assertEqual(self.os.path.isfile(path),
+ self.os.path.isfile(self.path(path)))
+
def test_islink(self):
path = self.make_path('foo', 'bar', 'baz')
self.create_file(path)
@@ -859,7 +911,7 @@ class FakePathlibUsageInOsFunctionsTest(RealPathlibTestCase):
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())
+ self.assert_equal_paths(self.os.path.realpath(path), self.os.getcwd())
def test_chmod(self):
path = self.make_path('some_file')
@@ -900,13 +952,25 @@ class FakePathlibUsageInOsFunctionsTest(RealPathlibTestCase):
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')
+ @unittest.skipIf(is_windows and sys.version_info < (3, 8),
+ 'os.readlink does not to support path-like objects '
+ 'under Windows before Python 3.8')
def test_readlink(self):
+ 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.assertEqual(self.os.readlink(self.path(link_path)), target)
+ self.assert_equal_paths(self.os.readlink(self.path(link_path)), target)
+
+ @unittest.skipIf(is_windows and sys.version_info < (3, 8),
+ 'os.readlink does not to support path-like objects '
+ 'under Windows before Python 3.8')
+ def test_readlink_bytes(self):
+ self.skip_if_symlink_not_supported()
+ link_path = self.make_path(b'foo', b'bar', b'baz')
+ target = self.make_path(b'tarJAY')
+ self.create_symlink(link_path, target)
+ self.assert_equal_paths(self.os.readlink(self.path(link_path)), target)
def test_remove(self):
path = self.make_path('test.txt')
@@ -960,7 +1024,22 @@ class FakePathlibUsageInOsFunctionsTest(RealPathlibTestCase):
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)))
+ self.assertEqual(self.os.stat(path), self.path(path).stat())
+
+ @unittest.skipIf(sys.version_info < (3, 10), "New in Python 3.10")
+ def test_stat_follow_symlinks(self):
+ self.check_posix_only()
+ directory = self.make_path('foo')
+ base_name = 'bar'
+ file_path = self.path(self.os.path.join(directory, base_name))
+ link_path = self.path(self.os.path.join(directory, 'link'))
+ contents = "contents"
+ self.create_file(file_path, contents=contents)
+ self.create_symlink(link_path, base_name)
+ self.assertEqual(len(contents),
+ link_path.stat(follow_symlinks=True)[stat.ST_SIZE])
+ self.assertEqual(len(base_name),
+ link_path.stat(follow_symlinks=False)[stat.ST_SIZE])
def test_utime(self):
path = self.make_path('some_file')
@@ -970,6 +1049,31 @@ class FakePathlibUsageInOsFunctionsTest(RealPathlibTestCase):
self.assertEqual(1, st.st_atime)
self.assertEqual(2, st.st_mtime)
+ def test_truncate(self):
+ path = self.make_path('some_file')
+ self.create_file(path, contents='test_test')
+ self.os.truncate(self.path(path), length=4)
+ st = self.os.stat(path)
+ self.assertEqual(4, st.st_size)
+
+ @unittest.skipIf(sys.platform == 'win32',
+ 'no pwd and grp modules in Windows')
+ def test_owner_and_group_posix(self):
+ self.check_posix_only()
+ path = self.make_path('some_file')
+ self.create_file(path)
+ self.assertTrue(self.path(path).owner())
+ self.assertTrue(self.path(path).group())
+
+ def test_owner_and_group_windows(self):
+ self.check_windows_only()
+ path = self.make_path('some_file')
+ self.create_file(path)
+ with self.assertRaises(NotImplementedError):
+ self.path(path).owner()
+ with self.assertRaises(NotImplementedError):
+ self.path(path).group()
+
class RealPathlibUsageInOsFunctionsTest(FakePathlibUsageInOsFunctionsTest):
def use_real_fs(self):
@@ -1036,5 +1140,4 @@ class FakeFilesystemPathLikeObjectTest(unittest.TestCase):
if __name__ == '__main__':
- if pathlib:
- unittest.main()
+ unittest.main(verbosity=2)
diff --git a/pyfakefs/tests/fake_stat_time_test.py b/pyfakefs/tests/fake_stat_time_test.py
index 4521365..866a1a1 100644
--- a/pyfakefs/tests/fake_stat_time_test.py
+++ b/pyfakefs/tests/fake_stat_time_test.py
@@ -23,7 +23,7 @@ FileTime = namedtuple('FileTime', 'st_ctime, st_atime, st_mtime')
class FakeStatTestBase(RealFsTestCase):
def setUp(self):
- super(FakeStatTestBase, self).setUp()
+ super().setUp()
# we disable the tests for MacOS to avoid very long builds due
# to the 1s time resolution - we know that the functionality is
# similar to Linux
@@ -35,8 +35,12 @@ class FakeStatTestBase(RealFsTestCase):
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)
+ if self.use_real_fs():
+ # sleep a bit so in the next call the time has changed
+ time.sleep(self.sleep_time)
+ else:
+ # calling time.time() advances mocked time
+ time.time()
return FileTime(st_ctime=stat.st_ctime,
st_atime=stat.st_atime,
st_mtime=stat.st_mtime)
@@ -54,103 +58,111 @@ class FakeStatTestBase(RealFsTestCase):
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
+ with self.mock_time():
+ 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)
+ with self.mock_time():
+ 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)
+ with self.mock_time():
+ self.create_file(self.file_path)
- before = self.stat_time(self.file_path)
- with self.open(self.file_path, self.mode):
- opened = self.stat_time(self.file_path)
- closed = self.stat_time(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
+ return before, opened, closed
def open_write_close(self):
- self.create_file(self.file_path)
+ with self.mock_time():
+ self.create_file(self.file_path)
- before = self.stat_time(self.file_path)
- with self.open(self.file_path, self.mode) 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)
+ 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
+ return before, opened, written, closed
def open_flush_close(self):
- self.create_file(self.file_path)
+ with self.mock_time():
+ self.create_file(self.file_path)
- before = self.stat_time(self.file_path)
- with self.open(self.file_path, self.mode) 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)
+ 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
+ return before, opened, flushed, closed
def open_write_flush(self):
- self.create_file(self.file_path)
+ with self.mock_time():
+ self.create_file(self.file_path)
- before = self.stat_time(self.file_path)
- with self.open(self.file_path, self.mode) 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)
+ 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
+ return before, opened, written, flushed, closed
def open_read_flush(self):
- self.create_file(self.file_path)
+ with self.mock_time():
+ 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)
+ 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
+ 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)
+ with self.mock_time():
+ 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
+ return created, read, closed
def open_read_close(self):
- self.create_file(self.file_path)
+ with self.mock_time():
+ self.create_file(self.file_path)
- before = self.stat_time(self.file_path)
- with self.open(self.file_path, self.mode) 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)
+ 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
+ return before, opened, read, closed
def check_open_close_new_file(self):
"""
@@ -334,15 +346,15 @@ class FakeStatTestBase(RealFsTestCase):
"""
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.assertLessEqual(before.st_ctime, opened.st_ctime)
+ self.assertLessEqual(written.st_ctime, flushed.st_ctime)
self.assertEqual(opened.st_ctime, written.st_ctime)
self.assertEqual(flushed.st_ctime, closed.st_ctime)
self.assertLessEqual(before.st_atime, opened.st_atime)
self.assertEqual(opened.st_atime, written.st_atime)
self.assertLessEqual(written.st_atime, flushed.st_atime)
- self.assertEqual(flushed.st_atime, closed.st_atime)
+ self.assertLessEqual(flushed.st_atime, closed.st_atime)
self.assertLess(before.st_mtime, opened.st_mtime)
self.assertEqual(opened.st_mtime, written.st_mtime)
@@ -365,7 +377,7 @@ class FakeStatTestBase(RealFsTestCase):
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.assertLessEqual(flushed.st_atime, closed.st_atime)
self.assertEqual(before.st_mtime, opened.st_mtime)
self.assertEqual(opened.st_mtime, written.st_mtime)
diff --git a/pyfakefs/tests/fake_tempfile_test.py b/pyfakefs/tests/fake_tempfile_test.py
index 8b34b81..7ddd4e1 100644
--- a/pyfakefs/tests/fake_tempfile_test.py
+++ b/pyfakefs/tests/fake_tempfile_test.py
@@ -34,7 +34,8 @@ class FakeTempfileModuleTest(fake_filesystem_unittest.TestCase):
obj = tempfile.NamedTemporaryFile()
self.assertTrue(self.fs.get_object(obj.name))
obj.close()
- self.assertRaises(OSError, self.fs.get_object, obj.name)
+ with self.assertRaises(OSError):
+ self.fs.get_object(obj.name)
def test_named_temporary_file_no_delete(self):
obj = tempfile.NamedTemporaryFile(delete=False)
@@ -67,7 +68,8 @@ class FakeTempfileModuleTest(fake_filesystem_unittest.TestCase):
def test_mkstemp_dir(self):
"""test tempfile.mkstemp(dir=)."""
# expect fail: /dir does not exist
- self.assertRaises(OSError, tempfile.mkstemp, dir='/dir')
+ with self.assertRaises(OSError):
+ tempfile.mkstemp(dir='/dir')
# expect pass: /dir exists
self.fs.create_dir('/dir')
next_fd = len(self.fs.open_files)
diff --git a/pyfakefs/tests/fixtures/config_module.py b/pyfakefs/tests/fixtures/config_module.py
new file mode 100644
index 0000000..d384c67
--- /dev/null
+++ b/pyfakefs/tests/fixtures/config_module.py
@@ -0,0 +1 @@
+configurable_value = 'another value'
diff --git a/pyfakefs/tests/fixtures/deprecated_property.py b/pyfakefs/tests/fixtures/deprecated_property.py
new file mode 100644
index 0000000..1c04119
--- /dev/null
+++ b/pyfakefs/tests/fixtures/deprecated_property.py
@@ -0,0 +1,29 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Used for testing suppression of deprecation warnings while iterating
+over modules. The code is modeled after code in xmlbuilder.py in Python 3.6.
+See issue #542.
+"""
+import warnings
+
+
+class DeprecatedProperty:
+
+ def __get__(self, instance, cls):
+ warnings.warn("async is deprecated", DeprecationWarning)
+ warnings.warn("async will be replaced", FutureWarning)
+ return instance
+
+
+class DeprecationTest:
+ locals()['async'] = DeprecatedProperty()
diff --git a/pyfakefs/tests/fixtures/excel_test.xlsx b/pyfakefs/tests/fixtures/excel_test.xlsx
new file mode 100644
index 0000000..6b6b64d
--- /dev/null
+++ b/pyfakefs/tests/fixtures/excel_test.xlsx
Binary files differ
diff --git a/pyfakefs/tests/import_as_example.py b/pyfakefs/tests/import_as_example.py
index 3d000d7..c358a30 100644
--- a/pyfakefs/tests/import_as_example.py
+++ b/pyfakefs/tests/import_as_example.py
@@ -15,24 +15,16 @@ Example module that is used for testing modules that import file system modules
to be patched under another name.
"""
import os as my_os
+import pathlib
+import sys
+from builtins import open as bltn_open
from io import open as io_open
from os import path
from os import stat
from os import stat as my_stat
from os.path import exists
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
+from pathlib import Path
def check_if_exists1(filepath):
@@ -45,10 +37,9 @@ def check_if_exists2(filepath):
return path.exists(filepath)
-if Path:
- def check_if_exists3(filepath):
- # tests patching Path imported from pathlib
- return Path(filepath).exists()
+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):
@@ -65,6 +56,11 @@ def check_if_exists6(filepath):
return my_exists(filepath)
+def check_if_exists7(filepath):
+ # tests patching pathlib
+ return pathlib.Path(filepath).exists()
+
+
def file_stat1(filepath):
# tests patching `stat` imported from os
return stat(filepath)
@@ -94,10 +90,21 @@ def file_contents2(filepath):
def exists_this_file():
- "Returns True in real fs only"
+ """Returns True in real fs only"""
return exists(__file__)
+def open_this_file():
+ """Works only in real fs"""
+ with open(__file__):
+ pass
+
+
+def return_this_file_path():
+ """Works only in real fs"""
+ return Path(__file__)
+
+
class TestDefaultArg:
def check_if_exists(self, filepath, file_exists=my_os.path.exists):
# this is a similar case as in the tempfile implementation under Posix
diff --git a/pyfakefs/tests/logsio.py b/pyfakefs/tests/logsio.py
new file mode 100644
index 0000000..53f4e7a
--- /dev/null
+++ b/pyfakefs/tests/logsio.py
@@ -0,0 +1,22 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Example module that is used for a regression test where a module with
+a name ending with "io" was skipped from patching (see #569).
+"""
+
+
+def file_contents(path):
+ """Return the contents of the given path as byte array."""
+ with open(path, 'rb') as f:
+ return f.read()
diff --git a/pyfakefs/tests/patched_packages_test.py b/pyfakefs/tests/patched_packages_test.py
new file mode 100644
index 0000000..f8d8a1a
--- /dev/null
+++ b/pyfakefs/tests/patched_packages_test.py
@@ -0,0 +1,73 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Provides patches for some commonly used modules that enable them to work
+with pyfakefs.
+"""
+import os
+
+from pyfakefs import fake_filesystem_unittest
+
+try:
+ import pandas as pd
+except ImportError:
+ pd = None
+
+try:
+ import xlrd
+except ImportError:
+ xlrd = None
+
+try:
+ import openpyxl
+except ImportError:
+ openpyxl = None
+
+
+class TestPatchedPackages(fake_filesystem_unittest.TestCase):
+ def setUp(self):
+ self.setUpPyfakefs()
+
+ if pd is not None:
+ def test_read_csv(self):
+ path = '/foo/bar.csv'
+ self.fs.create_file(path, contents='1,2,3,4')
+ df = pd.read_csv(path)
+ assert (df.columns == ['1', '2', '3', '4']).all()
+
+ def test_read_table(self):
+ path = '/foo/bar.csv'
+ self.fs.create_file(path, contents='1|2|3|4')
+ df = pd.read_table(path, delimiter='|')
+ assert (df.columns == ['1', '2', '3', '4']).all()
+
+ if pd is not None and xlrd is not None:
+ def test_read_excel(self):
+ path = '/foo/bar.xlsx'
+ src_path = os.path.dirname(os.path.abspath(__file__))
+ src_path = os.path.join(src_path, 'fixtures', 'excel_test.xlsx')
+ # map the file into another location to be sure that
+ # the real fs is not used
+ self.fs.add_real_file(src_path, target_path=path)
+ df = pd.read_excel(path)
+ assert (df.columns == [1, 2, 3, 4]).all()
+
+ if pd is not None and openpyxl is not None:
+ def test_write_excel(self):
+ self.fs.create_dir('/foo')
+ path = '/foo/bar.xlsx'
+ df = pd.DataFrame([[0, 1, 2, 3]])
+ with pd.ExcelWriter(path) as writer:
+ df.to_excel(writer)
+ df = pd.read_excel(path)
+ assert (df.columns == ['Unnamed: 0', 0, 1, 2, 3]).all()
diff --git a/pyfakefs/tests/performance_test.py b/pyfakefs/tests/performance_test.py
new file mode 100644
index 0000000..5d0c67c
--- /dev/null
+++ b/pyfakefs/tests/performance_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.
+"""Shall provide tests to check performance overhead of pyfakefs."""
+import os
+import time
+import unittest
+
+from pyfakefs.fake_filesystem_unittest import TestCase
+from pyfakefs.helpers import IS_PYPY
+
+if os.environ.get('TEST_PERFORMANCE'):
+
+ class SetupPerformanceTest(TestCase):
+ @classmethod
+ def setUpClass(cls) -> None:
+ cls.start_time = time.time()
+
+ @classmethod
+ def tearDownClass(cls) -> None:
+ cls.elapsed_time = time.time() - cls.start_time
+ print('Elapsed time per test for cached setup: {:.3f} ms'.format(
+ cls.elapsed_time * 10))
+
+ def setUp(self) -> None:
+ self.setUpPyfakefs()
+
+ class SetupNoCachePerformanceTest(TestCase):
+ @classmethod
+ def setUpClass(cls) -> None:
+ cls.start_time = time.time()
+
+ @classmethod
+ def tearDownClass(cls) -> None:
+ cls.elapsed_time = time.time() - cls.start_time
+ print('Elapsed time per test for uncached setup: {:.3f} ms'.format(
+ cls.elapsed_time * 10))
+
+ def setUp(self) -> None:
+ self.setUpPyfakefs(use_cache=False)
+
+ @unittest.skipIf(IS_PYPY, 'PyPy times are not comparable')
+ class TimePerformanceTest(TestCase):
+ """Make sure performance degradation in setup is noticed.
+ The numbers are related to the CI builds and may fail in local builds.
+ """
+
+ def test_cached_time(self):
+ self.assertLess(SetupPerformanceTest.elapsed_time, 0.4)
+
+ def test_uncached_time(self):
+ self.assertLess(SetupNoCachePerformanceTest.elapsed_time, 6)
+
+ def test_setup(self):
+ pass
+
+ for n in range(100):
+ test_name = "test_" + str(n)
+ setattr(SetupPerformanceTest, test_name, test_setup)
+ test_name = "test_nocache" + str(n)
+ setattr(SetupNoCachePerformanceTest, test_name, test_setup)
+
+ if __name__ == "__main__":
+ unittest.main()
diff --git a/pyfakefs/tests/test_utils.py b/pyfakefs/tests/test_utils.py
index da52fbe..eea81c4 100644
--- a/pyfakefs/tests/test_utils.py
+++ b/pyfakefs/tests/test_utils.py
@@ -14,8 +14,6 @@
# limitations under the License.
"""Common helper classes used in tests, or as test class base."""
-
-import errno
import os
import platform
import shutil
@@ -23,6 +21,8 @@ import stat
import sys
import tempfile
import unittest
+from contextlib import contextmanager
+from unittest import mock
from pyfakefs import fake_filesystem
from pyfakefs.helpers import is_byte_string, to_string
@@ -32,17 +32,32 @@ class DummyTime:
"""Mock replacement for time.time. Increases returned time on access."""
def __init__(self, curr_time, increment):
- self.curr_time = curr_time
+ self.current_time = curr_time
self.increment = increment
- self.started = False
+ def __call__(self, *args, **kwargs):
+ current_time = self.current_time
+ self.current_time += self.increment
+ return current_time
+
+
+class DummyMock:
def start(self):
- self.started = True
+ pass
- def __call__(self, *args, **kwargs):
- if self.started:
- self.curr_time += self.increment
- return self.curr_time
+ def stop(self):
+ pass
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ pass
+
+
+def time_mock(start=200, step=20):
+ return mock.patch('pyfakefs.fake_filesystem.now',
+ DummyTime(start, step))
class TestCase(unittest.TestCase):
@@ -55,23 +70,27 @@ class TestCase(unittest.TestCase):
def assert_mode_equal(self, expected, actual):
return self.assertEqual(stat.S_IMODE(expected), stat.S_IMODE(actual))
+ @contextmanager
+ def raises_os_error(self, subtype):
+ try:
+ yield
+ self.fail('No exception was raised, OSError expected')
+ except OSError as exc:
+ if isinstance(subtype, list):
+ self.assertIn(exc.errno, subtype)
+ else:
+ self.assertEqual(subtype, exc.errno)
+
def 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)
+ if isinstance(subtype, list):
+ self.assertIn(exc.errno, subtype)
+ else:
+ self.assertEqual(subtype, exc.errno)
class RealFsTestMixin:
@@ -94,9 +113,19 @@ class RealFsTestMixin:
self.open = open
self.os = os
self.base_path = None
+
+ def setUp(self):
+ if not os.environ.get('TEST_REAL_FS'):
+ self.skip_real_fs()
if self.use_real_fs():
self.base_path = tempfile.mkdtemp()
+ def tearDown(self):
+ if self.use_real_fs():
+ self.os.chdir(os.path.dirname(self.base_path))
+ shutil.rmtree(self.base_path, ignore_errors=True)
+ os.chdir(self.cwd)
+
@property
def is_windows_fs(self):
return TestCase.is_windows
@@ -233,15 +262,18 @@ class RealFsTestMixin:
raise unittest.SkipTest(
'Skipping because FakeFS does not match real FS')
- def symlink_can_be_tested(self):
+ def symlink_can_be_tested(self, force_real_fs=False):
"""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():
+ if (not TestCase.is_windows or
+ (not force_real_fs and not self.use_real_fs())):
return True
if TestCase.symlinks_can_be_tested is None:
+ if force_real_fs:
+ self.base_path = tempfile.mkdtemp()
link_path = self.make_path('link')
try:
self.os.symlink(self.base_path, link_path)
@@ -249,12 +281,14 @@ class RealFsTestMixin:
self.os.remove(link_path)
except (OSError, NotImplementedError):
TestCase.symlinks_can_be_tested = False
+ if force_real_fs:
+ self.base_path = None
return TestCase.symlinks_can_be_tested
- def skip_if_symlink_not_supported(self):
+ def skip_if_symlink_not_supported(self, force_real_fs=False):
"""If called at test start, tests are skipped if symlinks are not
supported."""
- if not self.symlink_can_be_tested():
+ if not self.symlink_can_be_tested(force_real_fs):
raise unittest.SkipTest(
'Symlinks under Windows need admin privileges')
@@ -281,13 +315,18 @@ class RealFsTestMixin:
components = []
while existing_path and not self.os.path.exists(existing_path):
existing_path, component = self.os.path.split(existing_path)
+ if not component and existing_path:
+ # existing path is a drive or UNC root
+ if not self.os.path.exists(existing_path):
+ self.filesystem.add_mount_point(existing_path)
+ break
components.insert(0, component)
for component in components:
existing_path = self.os.path.join(existing_path, component)
self.os.mkdir(existing_path)
self.os.chmod(existing_path, 0o777)
- def create_file(self, file_path, contents=None, encoding=None):
+ def create_file(self, file_path, contents=None, encoding=None, perm=0o666):
"""Create the given file at `file_path` with optional contents,
including subdirectories. `file_path` shall be composed using
`make_path()`.
@@ -301,7 +340,7 @@ class RealFsTestMixin:
with self.open(file_path, mode) as f:
if contents is not None:
f.write(contents)
- self.os.chmod(file_path, 0o666)
+ self.os.chmod(file_path, perm)
def create_symlink(self, link_path, target_path):
"""Create the path at `link_path`, and a symlink to this path at
@@ -318,10 +357,6 @@ class RealFsTestMixin:
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:
@@ -337,6 +372,36 @@ class RealFsTestMixin:
if old_base_path is not None:
self.setUpFileSystem()
+ def assert_equal_paths(self, actual, expected):
+ if self.is_windows:
+ actual = str(actual).replace('\\\\?\\', '')
+ expected = str(expected).replace('\\\\?\\', '')
+ if os.name == 'nt' and self.use_real_fs():
+ # work around a problem that the user name, but not the full
+ # path is shown as the short name
+ self.assertEqual(self.path_with_short_username(actual),
+ self.path_with_short_username(expected))
+ else:
+ self.assertEqual(actual, expected)
+ elif self.is_macos:
+ self.assertEqual(str(actual).replace('/private/var/', '/var/'),
+ str(expected).replace('/private/var/', '/var/'))
+ else:
+ self.assertEqual(actual, expected)
+
+ @staticmethod
+ def path_with_short_username(path):
+ components = path.split(os.sep)
+ if len(components) >= 3:
+ components[2] = components[2][:6].upper() + '~1'
+ return os.sep.join(components)
+
+ def mock_time(self, start=200, step=20):
+ if not self.use_real_fs():
+ return mock.patch('pyfakefs.fake_filesystem.now',
+ DummyTime(start, step))
+ return DummyMock()
+
class RealFsTestCase(TestCase, RealFsTestMixin):
"""Can be used as base class for tests also running in the real
@@ -347,6 +412,7 @@ class RealFsTestCase(TestCase, RealFsTestMixin):
RealFsTestMixin.__init__(self)
def setUp(self):
+ RealFsTestMixin.setUp(self)
self.cwd = os.getcwd()
if not self.use_real_fs():
self.filesystem = fake_filesystem.FakeFilesystem(
@@ -354,11 +420,12 @@ class RealFsTestCase(TestCase, RealFsTestMixin):
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 tearDown(self):
+ RealFsTestMixin.tearDown(self)
+
def setUpFileSystem(self):
pass
@@ -373,9 +440,3 @@ class RealFsTestCase(TestCase, RealFsTestMixin):
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/setup.cfg b/setup.cfg
index f19282a..0379a60 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,4 +1,4 @@
[metadata]
-description-file = README.md
+description_file = README.md
[bdist_wheel]
universal=0
diff --git a/setup.py b/setup.py
index bcf4e7f..61060ec 100644
--- a/setup.py
+++ b/setup.py
@@ -16,13 +16,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
+from typing import List
from setuptools import setup, find_packages
-from pyfakefs.fake_filesystem import __version__
+from pyfakefs import __version__
NAME = 'pyfakefs'
-REQUIRES = []
+REQUIRES: List[str] = []
DESCRIPTION = ('pyfakefs implements a fake file system that mocks '
'the Python file system modules.')
@@ -37,10 +38,12 @@ CLASSIFIERS = [
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
- 'Programming Language :: Python :: 3.5',
+ "Programming Language :: Python :: 3",
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Operating System :: POSIX',
@@ -50,6 +53,7 @@ CLASSIFIERS = [
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Software Development :: Testing',
'Topic :: System :: Filesystems',
+ 'Framework :: Pytest',
]
AUTHOR = 'Google'
@@ -72,13 +76,14 @@ params = dict(
author_email=AUTHOR_EMAIL,
maintainer=MAINTAINER,
maintainer_email=MAINTAINER_EMAIL,
+ license='http://www.apache.org/licenses/LICENSE-2.0',
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
long_description_content_type='text/markdown',
keywords=KEYWORDS,
url=URL,
classifiers=CLASSIFIERS,
- python_requires='>=3.5',
+ python_requires='>=3.6',
test_suite='pyfakefs.tests',
packages=find_packages(exclude=['docs'])
)
diff --git a/tox.ini b/tox.ini
index 0bc891a..c5c0c90 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist=py35,py36,py37,py38,pypy
+envlist=py36,py37,py38,py39,py310,pypy3
[testenv]
deps =