Snap for 8564071 from b5020682ff2f5a20c90be123957f25c9e285486c to mainline-wifi-release

Change-Id: I4378345dc755b31dacf352eb03f1a2d2f9c98036
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 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
similarity index 100%
rename from .travis/dockerfiles/Dockerfile_centos
rename to .github/workflows/dockerfiles/Dockerfile_centos
diff --git a/.travis/dockerfiles/Dockerfile_debian b/.github/workflows/dockerfiles/Dockerfile_debian
similarity index 100%
rename from .travis/dockerfiles/Dockerfile_debian
rename to .github/workflows/dockerfiles/Dockerfile_debian
diff --git a/.travis/dockerfiles/Dockerfile_fedora b/.github/workflows/dockerfiles/Dockerfile_fedora
similarity index 98%
rename from .travis/dockerfiles/Dockerfile_fedora
rename to .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
similarity index 100%
rename from .travis/dockerfiles/Dockerfile_ubuntu
rename to .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.0.2](https://pypi.python.org/pypi/pyfakefs/4.0.2)
+## [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)
+
+#### 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.
 
-  * pyfakefs 4.0.0 drops support for Python 2.7. If you still need
-    Python 2.7, you can continue to use pyfakefs 3.7.x. 
-    
+_Note_: This version has been yanked from PyPi as it erroneously allowed
+installation under Python 2. This has been fixed in version 4.0.2.
+
 #### Changes
   * Removed Python 2.7 and 3.4 support (see [#492](../../issues/492))
   
@@ -52,7 +309,7 @@
   * 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 @@
   * 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 @@
   * 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 @@
   * 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 @@
   * 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 @@
   * 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 @@
   * 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 @@
     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 @@
   * 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 @@
   * 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 @@
     ([#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 @@
   * 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 @@
     * `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 @@
    `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 @@
   * 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 @@
     ([#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 @@
  * 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 @@
 #### 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 @@
  * 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 @@
   * 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 @@
     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 @@
 * 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 @@
 
 
 ## 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 @@
 
 ### 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 @@
 
 #### 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 @@
 
 ### 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 @@
 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 @@
 # 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 @@
 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 @@
 - 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 @@
   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 @@
   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 @@
 
 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 @@
             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 @@
        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 @@
 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 the argument name ``fs``, which is mandatory.
+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:
 
-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.
+.. 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::
+  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.
+
+.. 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 @@
         def test_something(self, fs):
             fs.create_file('/foo/bar', contents='test')
 
-If you want to pass additional arguments to the patcher you can just
-pass them to the decorator:
-
-.. code:: python
-
-    @patchfs(allow_root_user=False)
-    def test_something(fs):
-        # now always called as non-root user
-        os.makedirs('/foo/bar')
-
-Patch using unittest.mock (deprecated)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-You can also use ``mock.patch()`` to patch the modules manually. This approach will
-only work for the directly imported modules, therefore it is not suited for testing
-larger code bases. As the other approaches are more convenient, this one is considered
-deprecated and will not be described in detail.
 
 .. _customizing_patcher:
 
-Customizing Patcher and TestCase
---------------------------------
+Customizing patching
+--------------------
 
-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()``.
+``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``.
 
-.. 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.
+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.
 
-  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.
+Patcher
+.......
+If you use the ``Patcher`` directly, you can just pass the arguments in the
+constructor:
+
+.. code:: python
+
+  from pyfakefs.fake_filesystem_unittest import Patcher
+
+  with Patcher(allow_root_user=False) as 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:
+
+.. 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 @@
   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 @@
   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 @@
   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 @@
 
 
 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 @@
 
   # 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 @@
   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 @@
                 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 @@
 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 @@
 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 @@
 
         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 @@
 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 @@
         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 @@
   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 @@
 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 @@
         @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 @@
 
 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 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_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 @@
     '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 @@
     _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()
 
 
-def set_uid(uid):
+class OSType(Enum):
+    """Defines the real or simulated OS of the underlying file system."""
+    LINUX = "linux"
+    MACOS = "macos"
+    WINDOWS = "windows"
+
+
+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 @@
     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 @@
     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 @@
     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 @@
         '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 @@
         # 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 @@
         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 @@
               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 @@
         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 @@
         """
         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 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 @@
     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 @@
             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 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 @@
                 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 @@
                 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 @@
             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 @@
             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 @@
                 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 @@
         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 @@
     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 @@
         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 @@
         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 @@
             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 @@
                 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 @@
             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 @@
         >>> 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 @@
 
         # 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 @@
         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 @@
                                '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 @@
                                '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 @@
         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)
+            raise OSError(err_no, message, filename, winerror)
+        raise OSError(err_no, message, filename)
 
-    @staticmethod
-    def _matching_string(matched, string):
-        """Return the string as byte or unicode depending
-        on the type of matched, assuming string is an ASCII string.
-        """
-        if string is None:
-            return string
-        if isinstance(matched, bytes) and isinstance(string, str):
-            return string.encode(locale.getpreferredencoding(False))
-        return string
-
-    def _path_separator(self, path):
+    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 add_mount_point(self, path, total_size=None):
+    def _starts_with_sep(self, path: AnyStr) -> bool:
+        """Return True if path starts with a path separator."""
+        sep = self.get_path_separator(path)
+        altsep = self._alternative_path_separator(path)
+        return (path.startswith(sep) or altsep is not None and
+                path.startswith(altsep))
+
+    def add_mount_point(self, path: AnyStr,
+                        total_size: Optional[int] = None) -> Dict:
         """Add a new mount point for a filesystem device.
         The mount point gets a new unique device number.
 
@@ -1011,44 +1108,42 @@
         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 @@
                 `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 @@
         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 @@
         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 @@
                     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 @@
             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 @@
                             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 @@
         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 @@
             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 @@
         """
         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 @@
         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 @@
         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 @@
         """
         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]
+        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):
+    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 @@
         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 @@
         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 @@
             (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 @@
             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 @@
         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 @@
         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 @@
             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 @@
             (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 ('', '')
+        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
 
-        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):
+    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 @@
             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
+                        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):
+    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 @@
                 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 @@
             (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 @@
                     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)
 
-    def _path_components(self, path):
+    @overload
+    def _path_components(self, path: str) -> List[str]: ...
+
+    @overload
+    def _path_components(self, path: bytes) -> List[bytes]: ...
+
+    def _path_components(self, path: AnyStr) -> List[AnyStr]:
         """Breaks the path into a list of component names.
 
         Does not include the root directory as a component, as all paths
@@ -1621,7 +1725,7 @@
             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 @@
         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 @@
             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 @@
             `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))))
+        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):
+    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 @@
         """
         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 @@
         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 @@
 
         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 @@
                 # 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 @@
                     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 @@
                 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 @@
                 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 @@
             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)
+        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, check_read_perm=True):
+    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 @@
         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 @@
         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 @@
             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 @@
         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 @@
         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 @@
             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 @@
                          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 @@
                 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 @@
             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 @@
                 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 @@
         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 @@
         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 @@
                 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 @@
 
         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 @@
             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 @@
             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 @@
         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 @@
         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 @@
                 (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 @@
             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 @@
             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 @@
                                        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 @@
             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 @@
             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 @@
             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 @@
                 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,107 @@
             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)
+            link_path, st_mode=S_IFLNK | PERM_DEF,
+            contents=link_target_path,
+            create_missing_dirs=create_missing_dirs)
 
-    def link(self, old_path, new_path, follow_symlinks=True):
+    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:
+            old_path: An existing link to the target file.
+            new_path: The destination path to create a new link at.
+            follow_symlinks: If False and old_path is a symlink, link the
+                symlink instead of the object it points to.
+            create_missing_dirs: If `True`, any missing parent directories of
+                file_path will be created
+
+        Returns:
+            The FakeFile object referred to by old_path.
+
+        Raises:
+            OSError:  if something already exists at new_path.
+            OSError:  if old_path is a directory.
+            OSError:  if the parent directory doesn't exist.
+        """
+        old_path_str = make_string_path(old_path)
+        new_path_str = make_string_path(new_path)
+        new_path_normalized = self.absnormpath(new_path_str)
+        if self.exists(new_path_normalized, check_link=True):
+            self.raise_os_error(errno.EEXIST, new_path_str)
+
+        new_parent_directory, new_basename = self.splitpath(
+            new_path_normalized)
+        if not new_parent_directory:
+            new_parent_directory = matching_string(new_path_str, self.cwd)
+
+        if not self.exists(new_parent_directory):
+            if create_missing_dirs:
+                self.create_dir(new_parent_directory)
+            else:
+                self.raise_os_error(errno.ENOENT, new_parent_directory)
+
+        if self.ends_with_path_separator(old_path_str):
+            error = errno.EINVAL if self.is_windows_fs else errno.ENOTDIR
+            self.raise_os_error(error, old_path_str)
+
+        if not self.is_windows_fs and self.ends_with_path_separator(new_path):
+            self.raise_os_error(errno.ENOENT, old_path_str)
+
+        # Retrieve the target file
+        try:
+            old_file = self.resolve(old_path_str,
+                                    follow_symlinks=follow_symlinks)
+        except OSError:
+            self.raise_os_error(errno.ENOENT, old_path_str)
+
+        if old_file.st_mode & S_IFDIR:
+            self.raise_os_error(
+                errno.EACCES if self.is_windows_fs
+                else errno.EPERM, old_path_str
+            )
+
+        # abuse the name field to control the filename of the
+        # newly created link
+        old_file.name = new_basename  # type: ignore[assignment]
+        self.add_object(new_parent_directory, old_file)
+        return old_file
+
+    def link(self, old_path: AnyPath,
+             new_path: AnyPath,
+             follow_symlinks: bool = True) -> FakeFile:
         """Create a hard link at new_path, pointing at old_path.
 
         Args:
@@ -2646,49 +2897,18 @@
             OSError:  if old_path is a directory.
             OSError:  if the parent directory doesn't exist.
         """
-        new_path_normalized = self.absnormpath(new_path)
-        if self.exists(new_path_normalized, check_link=True):
-            self.raise_os_error(errno.EEXIST, new_path)
+        return self.create_link(old_path, new_path, follow_symlinks,
+                                create_missing_dirs=False)
 
-        new_parent_directory, new_basename = self.splitpath(
-            new_path_normalized)
-        if not new_parent_directory:
-            new_parent_directory = self.cwd
-
-        if not self.exists(new_parent_directory):
-            self.raise_os_error(errno.ENOENT, new_parent_directory)
-
-        if self.ends_with_path_separator(old_path):
-            error = errno.EINVAL if self.is_windows_fs else errno.ENOTDIR
-            self.raise_os_error(error, old_path)
-
-        if not self.is_windows_fs and self.ends_with_path_separator(new_path):
-            self.raise_os_error(errno.ENOENT, old_path)
-
-        # Retrieve the target file
+    def _is_circular_link(self, link_obj: FakeFile) -> bool:
         try:
-            old_file = self.resolve(old_path, follow_symlinks=follow_symlinks)
-        except OSError:
-            self.raise_os_error(errno.ENOENT, old_path)
-
-        if old_file.st_mode & S_IFDIR:
-            self.raise_os_error(
-                errno.EACCES if self.is_windows_fs else errno.EPERM, old_path)
-
-        # abuse the name field to control the filename of the
-        # newly created link
-        old_file.name = new_basename
-        self.add_object(new_parent_directory, old_file)
-        return old_file
-
-    def _is_circular_link(self, link_obj):
-        try:
+            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 @@
         """
         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 @@
             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 @@
         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 @@
         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 @@
         """
         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 @@
             # 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 @@
                     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 @@
         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 @@
         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 @@
         """
         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 @@
         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 @@
         """
         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 @@
         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 @@
             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 @@
                         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 @@
 
         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 @@
             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 @@
                     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 @@
 
         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 @@
     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 @@
             '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 @@
         """
         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
+        self._os_path.os = self.os = os_module  # type: ignore[attr-defined]
+        self.reset(filesystem)
 
-    def exists(self, path):
+    @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 @@
         """
         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 @@
         """
         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 @@
             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 @@
         """
         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 @@
         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 @@
             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 @@
             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 @@
                 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 @@
             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 @@
         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 @@
         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 @@
             # 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 @@
             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 @@
             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 @@
     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 @@
             '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 @@
             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 @@
             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 @@
             # 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 @@
         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 @@
         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 @@
             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 @@
             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 @@
         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 @@
         """
         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 @@
         """
         # 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 @@
         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 @@
             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 @@
     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 @@
                                            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 @@
             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 @@
         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 @@
             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 @@
         """
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         """
         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 @@
             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 @@
                 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 @@
             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 @@
                     '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 @@
                 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 @@
             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 @@
             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 @@
           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 @@
         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 @@
         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 @@
           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 @@
             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 @@
             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 @@
         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 @@
             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 @@
         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 @@
         # 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 @@
             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 @@
             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 @@
             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 @@
     """
 
     @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 @@
     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 @@
         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 @@
         # 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 @@
         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 @@
             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 @@
         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 @@
             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 @@
 
         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 @@
             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 @@
                 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 @@
 
         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 @@
             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 @@
 
         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 @@
 
         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 @@
     """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 close(self):
+    def read(self, n: int = -1) -> bytes:
+        return cast(bytes, self._stream_object.read())
+
+    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 @@
     """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 @@
     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 @@
     """
     __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 @@
         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 @@
             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 @@
                  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 @@
         # 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 @@
                                    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 @@
             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
+            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, newline, open_modes):
+    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 @@
             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 @@
 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 @@
     _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 @@
                     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 @@
             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 @@
         """
         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 @@
     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 @@
             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 @@
         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 @@
     '''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')
+    SKIPNAMES = {'os', 'path', 'io', 'genericpath', 'fcntl',
+                 OS_MODULE, PATH_MODULE}
 
-    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__"""
+    # 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)
+            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 = {}
-        self._class_modules = {}
+        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
+            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
 
-        self._fake_module_functions = {}
+        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 @@
             '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 @@
             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 @@
         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
+            # 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:
+            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):
+    def _is_fs_function(self, fct: FunctionType) -> bool:
         try:
-            return ((inspect.isfunction(fct) or
-                     inspect.isbuiltin(fct)) and
-                    fct.__name__ in self._fake_module_functions and
+            # 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
+                                yield f, i, d
+        except Exception:
+            # Ignore any exception, examples:
+            # ImportError: No module named '_gdbm'
+            # _DontDoThat() (see #523)
+            pass
 
-    def _find_modules(self):
+    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)}
+            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)}
+            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)}
 
-            # 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.__class__.FS_FUNCTIONS.setdefault(
+                        (name, fct.__name__, fct.__module__),
+                        set()).add(module)
 
-            for name, fct in functions.items():
-                self._fct_modules.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)
 
-    def _refresh(self):
+            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 @@
                                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 @@
                 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 @@
         """
         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 @@
     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 @@
                 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 @@
                              '"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 @@
         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 @@
             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 @@
             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 @@
 (including PurePosixPath, PosixPath, PureWindowsPath and WindowsPath)
 get the properties of the underlying fake filesystem.
 """
-import fnmatch
-import os
-import re
-
-try:
-    from urllib.parse import quote_from_bytes as urlquote_from_bytes
-except ImportError:
-    from urllib import quote as urlquote_from_bytes
-
-import sys
-
-import functools
-
 import errno
+import fnmatch
+import functools
+import os
+import pathlib
+from pathlib import PurePath
+import re
+import sys
+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 _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 @@
 
 
 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 @@
 
     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 @@
         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 @@
         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 @@
     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 @@
         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 @@
         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 @@
             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 @@
         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 @@
         """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 @@
     `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 @@
         """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 @@
     """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 @@
 
     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 @@
         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 @@
                 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: ...
 
 
-def to_string(path):
+@overload
+def make_string_path(dir_name: os.PathLike) -> str: ...
+
+
+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 @@
     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
+    _stat_float_times: bool = 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
+    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 @@
                 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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 __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 encoding(self):
-        return self._encoding or locale.getpreferredencoding(False)
+    def getvalue(self) -> bytes:
+        return self._bytestream.getvalue()
 
-    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 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 @@
     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 @@
     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 @@
     """ 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 @@
 
 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 @@
         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
similarity index 100%
rename from pyfakefs/pytest_tests/pytest_plugin_failing_test.py
rename to 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 @@
     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 @@
 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 @@
             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 @@
         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 @@
     >>> 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 @@
         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 @@
         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 @@
         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 @@
         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 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 @@
         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 @@
             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 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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
             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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 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 @@
             '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 @@
         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 @@
     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 @@
                                                   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 @@
         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 @@
     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 @@
     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 @@
         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 @@
     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 @@
         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 @@
             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 @@
     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 @@
     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 @@
         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 @@
     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 @@
             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 @@
 
     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 @@
         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 @@
     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 @@
     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 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 @@
     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 @@
         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 @@
         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 @@
 
     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 @@
 
     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 @@
     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 @@
         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 @@
         # 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 @@
     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 @@
             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 @@
         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 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 @@
                          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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
 
         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 @@
         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 @@
 
     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 @@
         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 @@
 
     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 @@
     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 @@
         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 @@
         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 @@
             '!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 @@
             '!!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 @@
 
     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 @@
             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 @@
     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 @@
         )
 
     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 @@
                 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 @@
             '/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 @@
                 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)
+        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
+            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 @@
         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 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 @@
             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 @@
         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 @@
         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 @@
     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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
     """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 @@
             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 @@
             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 @@
 
     @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 @@
         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(TestPyfakefsUnittestBase):
+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()
+
     def test_pause_resume(self):
         fake_temp_file = tempfile.NamedTemporaryFile()
         self.assertTrue(self.fs.exists(fake_temp_file.name))
@@ -379,11 +555,11 @@
         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 @@
         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 @@
         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 @@
         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 @@
             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 @@
                 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 @@
         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 unittest
 
 from pyfakefs import fake_filesystem
+from pyfakefs.helpers import IS_PYPY
 
 
 def sep(path):
@@ -181,8 +182,8 @@
         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 @@
             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 @@
             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 @@
         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 @@
             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 @@
         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 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 @@
         # 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 @@
     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 @@
         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 @@
         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 @@
         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 @@
         # 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 @@
         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 @@
         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 @@
     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 @@
         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 @@
         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 @@
             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 @@
             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 @@
     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 @@
 
         # 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 @@
         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 @@
     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 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 @@
         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 @@
             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 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 @@
             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 @@
         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 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 @@
     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 @@
             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 @@
         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 @@
         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 @@
 
     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 @@
         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 @@
 
     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 @@
         # 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 @@
 
         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 @@
 
     # 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 @@
             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 @@
             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 @@
         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 @@
         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 @@
 
     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 @@
         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 @@
 
     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 @@
         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 @@
         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 @@
         # 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 @@
         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 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()
+        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(200, st.st_mtime)
-        self.assertTrue(isinstance(st.st_mtime, int))
+                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(200912300000, st.st_atime_ns)
-        self.assertEqual(200912300000, st.st_mtime_ns)
+                self.assertEqual(200912400000, st.st_atime_ns)
+                self.assertEqual(220912400000, 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)
+                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)
+        try:
+            with self.mock_time(start=200.9123):
+                path = self.make_path('some_file')
+                self.createTestFile(path)
+                test_file = self.filesystem.get_object(path)
 
-        self.setDummyTime(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.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(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(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(240, test_file.st_ctime)
+                self.assertTrue(isinstance(test_file.st_ctime, int))
 
-        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(240, st.st_ctime)
+                self.assertTrue(isinstance(st.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)
+                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 @@
         # 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 @@
         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 @@
         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 @@
                                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 @@
         ]
         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 @@
         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 @@
 
     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 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 @@
 
 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 @@
             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 @@
                          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 @@
         # 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 @@
             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 @@
         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 @@
 
     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 @@
 
     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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
     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 @@
         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 @@
 
 
 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 @@
 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 @@
 
     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 @@
             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 @@
         """
         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 @@
         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 @@
         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 @@
     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 @@
 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 @@
     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 @@
     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 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 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 @@
     """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 start(self):
-        self.started = True
 
     def __call__(self, *args, **kwargs):
-        if self.started:
-            self.curr_time += self.increment
-        return self.curr_time
+        current_time = self.current_time
+        self.current_time += self.increment
+        return current_time
+
+
+class DummyMock:
+    def start(self):
+        pass
+
+    def stop(self):
+        pass
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        pass
+
+
+def time_mock(start=200, step=20):
+    return mock.patch('pyfakefs.fake_filesystem.now',
+                      DummyTime(start, step))
 
 
 class TestCase(unittest.TestCase):
@@ -55,23 +70,27 @@
     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 @@
         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 @@
                 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 @@
                 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 @@
         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 @@
         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 @@
         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 @@
                 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 @@
         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 @@
             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 @@
         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 @@
     '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 @@
     'Topic :: Software Development :: Libraries :: Python Modules',
     'Topic :: Software Development :: Testing',
     'Topic :: System :: Filesystems',
+    'Framework :: Pytest',
 ]
 
 AUTHOR = 'Google'
@@ -72,13 +76,14 @@
     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 =