Merge pull request #6563 from blueyed/merge-master-into-features
Merge master into features
This commit is contained in:
		
						commit
						498884a2a0
					
				| 
						 | 
					@ -45,6 +45,8 @@ jobs:
 | 
				
			||||||
          "macos-py38",
 | 
					          "macos-py38",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          "linting",
 | 
					          "linting",
 | 
				
			||||||
 | 
					          "docs",
 | 
				
			||||||
 | 
					          "doctesting",
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        include:
 | 
					        include:
 | 
				
			||||||
| 
						 | 
					@ -114,7 +116,17 @@ jobs:
 | 
				
			||||||
          - name: "linting"
 | 
					          - name: "linting"
 | 
				
			||||||
            python: "3.7"
 | 
					            python: "3.7"
 | 
				
			||||||
            os: ubuntu-latest
 | 
					            os: ubuntu-latest
 | 
				
			||||||
            tox_env: "linting,docs,doctesting"
 | 
					            tox_env: "linting"
 | 
				
			||||||
 | 
					            skip_coverage: true
 | 
				
			||||||
 | 
					          - name: "docs"
 | 
				
			||||||
 | 
					            python: "3.7"
 | 
				
			||||||
 | 
					            os: ubuntu-latest
 | 
				
			||||||
 | 
					            tox_env: "docs"
 | 
				
			||||||
 | 
					            skip_coverage: true
 | 
				
			||||||
 | 
					          - name: "doctesting"
 | 
				
			||||||
 | 
					            python: "3.7"
 | 
				
			||||||
 | 
					            os: ubuntu-latest
 | 
				
			||||||
 | 
					            tox_env: "doctesting"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - uses: actions/checkout@v1
 | 
					    - uses: actions/checkout@v1
 | 
				
			||||||
| 
						 | 
					@ -144,21 +156,11 @@ jobs:
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
        python scripts/append_codecov_token.py
 | 
					        python scripts/append_codecov_token.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Combine coverage
 | 
					    - name: Report coverage
 | 
				
			||||||
      if: (!matrix.skip_coverage)
 | 
					      if: (!matrix.skip_coverage)
 | 
				
			||||||
      run: |
 | 
					      env:
 | 
				
			||||||
        python -m coverage combine
 | 
					        CODECOV_NAME: ${{ matrix.name }}
 | 
				
			||||||
        python -m coverage xml
 | 
					      run: bash scripts/report-coverage.sh -F GHA,${{ runner.os }}
 | 
				
			||||||
 | 
					 | 
				
			||||||
    - name: Codecov upload
 | 
					 | 
				
			||||||
      if: (!matrix.skip_coverage)
 | 
					 | 
				
			||||||
      uses: codecov/codecov-action@v1
 | 
					 | 
				
			||||||
      with:
 | 
					 | 
				
			||||||
        token: ${{ secrets.codecov }}
 | 
					 | 
				
			||||||
        file: ./coverage.xml
 | 
					 | 
				
			||||||
        flags: ${{ runner.os }}
 | 
					 | 
				
			||||||
        fail_ci_if_error: false
 | 
					 | 
				
			||||||
        name: ${{ matrix.name }}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  deploy:
 | 
					  deploy:
 | 
				
			||||||
    if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest'
 | 
					    if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -64,7 +64,7 @@ repos:
 | 
				
			||||||
                _code\.|
 | 
					                _code\.|
 | 
				
			||||||
                builtin\.|
 | 
					                builtin\.|
 | 
				
			||||||
                code\.|
 | 
					                code\.|
 | 
				
			||||||
                io\.(BytesIO|saferepr)|
 | 
					                io\.(BytesIO|saferepr|TerminalWriter)|
 | 
				
			||||||
                path\.local\.sysfind|
 | 
					                path\.local\.sysfind|
 | 
				
			||||||
                process\.|
 | 
					                process\.|
 | 
				
			||||||
                std\.
 | 
					                std\.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,11 +47,6 @@ jobs:
 | 
				
			||||||
      python: '3.5.1'
 | 
					      python: '3.5.1'
 | 
				
			||||||
      dist: trusty
 | 
					      dist: trusty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - env: TOXENV=linting,docs,doctesting PYTEST_COVERAGE=1
 | 
					 | 
				
			||||||
      cache:
 | 
					 | 
				
			||||||
        directories:
 | 
					 | 
				
			||||||
          - $HOME/.cache/pre-commit
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
before_script:
 | 
					before_script:
 | 
				
			||||||
  - |
 | 
					  - |
 | 
				
			||||||
    # Do not (re-)upload coverage with cron runs.
 | 
					    # Do not (re-)upload coverage with cron runs.
 | 
				
			||||||
| 
						 | 
					@ -71,7 +66,7 @@ script: tox
 | 
				
			||||||
after_success:
 | 
					after_success:
 | 
				
			||||||
  - |
 | 
					  - |
 | 
				
			||||||
    if [[ "$PYTEST_COVERAGE" = 1 ]]; then
 | 
					    if [[ "$PYTEST_COVERAGE" = 1 ]]; then
 | 
				
			||||||
      env CODECOV_NAME="$TOXENV-$TRAVIS_OS_NAME" scripts/report-coverage.sh
 | 
					      env CODECOV_NAME="$TOXENV-$TRAVIS_OS_NAME" scripts/report-coverage.sh -F Travis
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
notifications:
 | 
					notifications:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -166,7 +166,7 @@ Short version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#. Fork the repository.
 | 
					#. Fork the repository.
 | 
				
			||||||
#. Enable and install `pre-commit <https://pre-commit.com>`_ to ensure style-guides and code checks are followed.
 | 
					#. Enable and install `pre-commit <https://pre-commit.com>`_ to ensure style-guides and code checks are followed.
 | 
				
			||||||
#. Target ``master`` for bugfixes and doc changes.
 | 
					#. Target ``master`` for bug fixes and doc changes.
 | 
				
			||||||
#. Target ``features`` for new features or functionality changes.
 | 
					#. Target ``features`` for new features or functionality changes.
 | 
				
			||||||
#. Follow **PEP-8** for naming and `black <https://github.com/psf/black>`_ for formatting.
 | 
					#. Follow **PEP-8** for naming and `black <https://github.com/psf/black>`_ for formatting.
 | 
				
			||||||
#. Tests are run using ``tox``::
 | 
					#. Tests are run using ``tox``::
 | 
				
			||||||
| 
						 | 
					@ -212,7 +212,7 @@ Here is a simple overview, with pytest-specific bits:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $ git checkout -b your-feature-branch-name features
 | 
					        $ git checkout -b your-feature-branch-name features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   Given we have "major.minor.micro" version numbers, bugfixes will usually
 | 
					   Given we have "major.minor.micro" version numbers, bug fixes will usually
 | 
				
			||||||
   be released in micro releases whereas features will be released in
 | 
					   be released in micro releases whereas features will be released in
 | 
				
			||||||
   minor releases and incompatible changes in major releases.
 | 
					   minor releases and incompatible changes in major releases.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -294,7 +294,7 @@ Here is a simple overview, with pytest-specific bits:
 | 
				
			||||||
    compare: your-branch-name
 | 
					    compare: your-branch-name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    base-fork: pytest-dev/pytest
 | 
					    base-fork: pytest-dev/pytest
 | 
				
			||||||
    base: master          # if it's a bugfix
 | 
					    base: master          # if it's a bug fix
 | 
				
			||||||
    base: features        # if it's a feature
 | 
					    base: features        # if it's a feature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,14 +1,14 @@
 | 
				
			||||||
Release Procedure
 | 
					Release Procedure
 | 
				
			||||||
-----------------
 | 
					-----------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Our current policy for releasing is to aim for a bugfix every few weeks and a minor release every 2-3 months. The idea
 | 
					Our current policy for releasing is to aim for a bug-fix release every few weeks and a minor release every 2-3 months. The idea
 | 
				
			||||||
is to get fixes and new features out instead of trying to cram a ton of features into a release and by consequence
 | 
					is to get fixes and new features out instead of trying to cram a ton of features into a release and by consequence
 | 
				
			||||||
taking a lot of time to make a new one.
 | 
					taking a lot of time to make a new one.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. important::
 | 
					.. important::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pytest releases must be prepared on **Linux** because the docs and examples expect
 | 
					    pytest releases must be prepared on **Linux** because the docs and examples expect
 | 
				
			||||||
    to be executed in that platform.
 | 
					    to be executed on that platform.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#. Create a branch ``release-X.Y.Z`` with the version for the release.
 | 
					#. Create a branch ``release-X.Y.Z`` with the version for the release.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,80 +0,0 @@
 | 
				
			||||||
trigger:
 | 
					 | 
				
			||||||
- master
 | 
					 | 
				
			||||||
- features
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
variables:
 | 
					 | 
				
			||||||
  PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml -vv"
 | 
					 | 
				
			||||||
  PYTEST_COVERAGE: '0'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
jobs:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- job: 'Test'
 | 
					 | 
				
			||||||
  pool:
 | 
					 | 
				
			||||||
    vmImage: "vs2017-win2016"
 | 
					 | 
				
			||||||
  strategy:
 | 
					 | 
				
			||||||
    matrix:
 | 
					 | 
				
			||||||
      # -- pypy3 disabled for now: #5279 --
 | 
					 | 
				
			||||||
      # pypy3:
 | 
					 | 
				
			||||||
      #   python.version: 'pypy3'
 | 
					 | 
				
			||||||
      #   tox.env: 'pypy3'
 | 
					 | 
				
			||||||
      py35-xdist:
 | 
					 | 
				
			||||||
        python.version: '3.5'
 | 
					 | 
				
			||||||
        tox.env: 'py35-xdist'
 | 
					 | 
				
			||||||
        # Coverage for:
 | 
					 | 
				
			||||||
        # - test_supports_breakpoint_module_global
 | 
					 | 
				
			||||||
        PYTEST_COVERAGE: '1'
 | 
					 | 
				
			||||||
      py36-xdist:
 | 
					 | 
				
			||||||
        python.version: '3.6'
 | 
					 | 
				
			||||||
        tox.env: 'py36-xdist'
 | 
					 | 
				
			||||||
      py37:
 | 
					 | 
				
			||||||
        python.version: '3.7'
 | 
					 | 
				
			||||||
        tox.env: 'py37-twisted-numpy'
 | 
					 | 
				
			||||||
        # Coverage for:
 | 
					 | 
				
			||||||
        # - _py36_windowsconsoleio_workaround (with py36+)
 | 
					 | 
				
			||||||
        # - test_request_garbage (no xdist)
 | 
					 | 
				
			||||||
        PYTEST_COVERAGE: '1'
 | 
					 | 
				
			||||||
      py37-linting/docs/doctesting:
 | 
					 | 
				
			||||||
        python.version: '3.7'
 | 
					 | 
				
			||||||
        tox.env: 'linting,docs,doctesting'
 | 
					 | 
				
			||||||
      py37-pluggymaster-xdist:
 | 
					 | 
				
			||||||
        python.version: '3.7'
 | 
					 | 
				
			||||||
        tox.env: 'py37-pluggymaster-xdist'
 | 
					 | 
				
			||||||
      py38-xdist:
 | 
					 | 
				
			||||||
        python.version: '3.8'
 | 
					 | 
				
			||||||
        tox.env: 'py38-xdist'
 | 
					 | 
				
			||||||
    maxParallel: 10
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  steps:
 | 
					 | 
				
			||||||
  - task: UsePythonVersion@0
 | 
					 | 
				
			||||||
    inputs:
 | 
					 | 
				
			||||||
      versionSpec: '$(python.version)'
 | 
					 | 
				
			||||||
      architecture: 'x64'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  - script: python -m pip install --upgrade pip && python -m pip install tox
 | 
					 | 
				
			||||||
    displayName: 'Install tox'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  - bash: |
 | 
					 | 
				
			||||||
      if [[ "$PYTEST_COVERAGE" == "1" ]]; then
 | 
					 | 
				
			||||||
        export _PYTEST_TOX_COVERAGE_RUN="coverage run -m"
 | 
					 | 
				
			||||||
        export _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
 | 
					 | 
				
			||||||
        export COVERAGE_FILE="$PWD/.coverage"
 | 
					 | 
				
			||||||
        export COVERAGE_PROCESS_START="$PWD/.coveragerc"
 | 
					 | 
				
			||||||
      fi
 | 
					 | 
				
			||||||
      python -m tox -e $(tox.env)
 | 
					 | 
				
			||||||
    displayName: 'Run tests'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  - task: PublishTestResults@2
 | 
					 | 
				
			||||||
    inputs:
 | 
					 | 
				
			||||||
      testResultsFiles: 'build/test-results/$(tox.env).xml'
 | 
					 | 
				
			||||||
      testRunTitle: '$(tox.env)'
 | 
					 | 
				
			||||||
    condition: succeededOrFailed()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  - bash: |
 | 
					 | 
				
			||||||
      if [[ "$PYTEST_COVERAGE" == 1 ]]; then
 | 
					 | 
				
			||||||
        scripts/report-coverage.sh
 | 
					 | 
				
			||||||
      fi
 | 
					 | 
				
			||||||
    env:
 | 
					 | 
				
			||||||
      CODECOV_NAME: $(tox.env)
 | 
					 | 
				
			||||||
      CODECOV_TOKEN: $(CODECOV_TOKEN)
 | 
					 | 
				
			||||||
    displayName: Report and upload coverage
 | 
					 | 
				
			||||||
    condition: eq(variables['PYTEST_COVERAGE'], '1')
 | 
					 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* ``feature``: new user facing features, like new command-line options and new behavior.
 | 
					* ``feature``: new user facing features, like new command-line options and new behavior.
 | 
				
			||||||
* ``improvement``: improvement of existing functionality, usually without requiring user intervention (for example, new fields being written in ``--junitxml``, improved colors in terminal, etc).
 | 
					* ``improvement``: improvement of existing functionality, usually without requiring user intervention (for example, new fields being written in ``--junitxml``, improved colors in terminal, etc).
 | 
				
			||||||
* ``bugfix``: fixes a reported bug.
 | 
					* ``bugfix``: fixes a bug.
 | 
				
			||||||
* ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
 | 
					* ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
 | 
				
			||||||
* ``deprecation``: feature deprecation.
 | 
					* ``deprecation``: feature deprecation.
 | 
				
			||||||
* ``breaking``: a change which may break existing suites, such as feature removal or behavior change.
 | 
					* ``breaking``: a change which may break existing suites, such as feature removal or behavior change.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2357,7 +2357,7 @@ Deprecations and Removals
 | 
				
			||||||
- `#4036 <https://github.com/pytest-dev/pytest/issues/4036>`_: The ``item`` parameter of ``pytest_warning_captured`` hook is now documented as deprecated. We realized only after
 | 
					- `#4036 <https://github.com/pytest-dev/pytest/issues/4036>`_: The ``item`` parameter of ``pytest_warning_captured`` hook is now documented as deprecated. We realized only after
 | 
				
			||||||
  the ``3.8`` release that this parameter is incompatible with ``pytest-xdist``.
 | 
					  the ``3.8`` release that this parameter is incompatible with ``pytest-xdist``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Our policy is to not deprecate features during bugfix releases, but in this case we believe it makes sense as we are
 | 
					  Our policy is to not deprecate features during bug-fix releases, but in this case we believe it makes sense as we are
 | 
				
			||||||
  only documenting it as deprecated, without issuing warnings which might potentially break test suites. This will get
 | 
					  only documenting it as deprecated, without issuing warnings which might potentially break test suites. This will get
 | 
				
			||||||
  the word out that hook implementers should not use this parameter at all.
 | 
					  the word out that hook implementers should not use this parameter at all.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5380,7 +5380,7 @@ time or change existing behaviors in order to make them less surprising/more use
 | 
				
			||||||
  Thanks Daniel Grunwald for the report and Bruno Oliveira for the PR.
 | 
					  Thanks Daniel Grunwald for the report and Bruno Oliveira for the PR.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- (experimental) adapt more SEMVER style versioning and change meaning of
 | 
					- (experimental) adapt more SEMVER style versioning and change meaning of
 | 
				
			||||||
  master branch in git repo: "master" branch now keeps the bugfixes, changes
 | 
					  master branch in git repo: "master" branch now keeps the bug fixes, changes
 | 
				
			||||||
  aimed for micro releases.  "features" branch will only be released
 | 
					  aimed for micro releases.  "features" branch will only be released
 | 
				
			||||||
  with minor or major pytest releases.
 | 
					  with minor or major pytest releases.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ Branches
 | 
				
			||||||
 | 
					
 | 
				
			||||||
We have two long term branches:
 | 
					We have two long term branches:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* ``master``: contains the code for the next bugfix release.
 | 
					* ``master``: contains the code for the next bug-fix release.
 | 
				
			||||||
* ``features``: contains the code with new features for the next minor release.
 | 
					* ``features``: contains the code with new features for the next minor release.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The official repository usually does not contain topic branches, developers and contributors should create topic
 | 
					The official repository usually does not contain topic branches, developers and contributors should create topic
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,20 +0,0 @@
 | 
				
			||||||
import pytest
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@pytest.fixture(scope="session")
 | 
					 | 
				
			||||||
def setup(request):
 | 
					 | 
				
			||||||
    setup = CostlySetup()
 | 
					 | 
				
			||||||
    yield setup
 | 
					 | 
				
			||||||
    setup.finalize()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CostlySetup:
 | 
					 | 
				
			||||||
    def __init__(self):
 | 
					 | 
				
			||||||
        import time
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        print("performing costly setup")
 | 
					 | 
				
			||||||
        time.sleep(5)
 | 
					 | 
				
			||||||
        self.timecostly = 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def finalize(self):
 | 
					 | 
				
			||||||
        del self.timecostly
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1 +0,0 @@
 | 
				
			||||||
#
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,2 +0,0 @@
 | 
				
			||||||
def test_quick(setup):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1 +0,0 @@
 | 
				
			||||||
#
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +0,0 @@
 | 
				
			||||||
def test_something(setup):
 | 
					 | 
				
			||||||
    assert setup.timecostly == 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def test_something_more(setup):
 | 
					 | 
				
			||||||
    assert setup.timecostly == 1
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1042,11 +1042,13 @@ file:
 | 
				
			||||||
    import pytest
 | 
					    import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.fixture()
 | 
					    @pytest.fixture
 | 
				
			||||||
    def cleandir():
 | 
					    def cleandir():
 | 
				
			||||||
 | 
					        old_cwd = os.getcwd()
 | 
				
			||||||
        newpath = tempfile.mkdtemp()
 | 
					        newpath = tempfile.mkdtemp()
 | 
				
			||||||
        os.chdir(newpath)
 | 
					        os.chdir(newpath)
 | 
				
			||||||
        yield
 | 
					        yield
 | 
				
			||||||
 | 
					        os.chdir(old_cwd)
 | 
				
			||||||
        shutil.rmtree(newpath)
 | 
					        shutil.rmtree(newpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
and declare its use in a test module via a ``usefixtures`` marker:
 | 
					and declare its use in a test module via a ``usefixtures`` marker:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ The pytest team is proud to announce the {version} release!
 | 
				
			||||||
pytest is a mature Python testing tool with more than a 2000 tests
 | 
					pytest is a mature Python testing tool with more than a 2000 tests
 | 
				
			||||||
against itself, passing on many different interpreters and platforms.
 | 
					against itself, passing on many different interpreters and platforms.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This release contains a number of bugs fixes and improvements, so users are encouraged
 | 
					This release contains a number of bug fixes and improvements, so users are encouraged
 | 
				
			||||||
to take a look at the CHANGELOG:
 | 
					to take a look at the CHANGELOG:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    https://docs.pytest.org/en/latest/changelog.html
 | 
					    https://docs.pytest.org/en/latest/changelog.html
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ For complete documentation, please visit:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    https://docs.pytest.org/en/latest/
 | 
					    https://docs.pytest.org/en/latest/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
As usual, you can upgrade from pypi via:
 | 
					As usual, you can upgrade from PyPI via:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pip install -U pytest
 | 
					    pip install -U pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,4 +24,4 @@ Thanks to all who contributed to this release, among them:
 | 
				
			||||||
{contributors}
 | 
					{contributors}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Happy testing,
 | 
					Happy testing,
 | 
				
			||||||
The Pytest Development Team
 | 
					The pytest Development Team
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,4 +15,4 @@ python -m coverage xml
 | 
				
			||||||
python -m coverage report -m
 | 
					python -m coverage report -m
 | 
				
			||||||
# Set --connect-timeout to work around https://github.com/curl/curl/issues/4461
 | 
					# Set --connect-timeout to work around https://github.com/curl/curl/issues/4461
 | 
				
			||||||
curl -S -L --connect-timeout 5 --retry 6 -s https://codecov.io/bash -o codecov-upload.sh
 | 
					curl -S -L --connect-timeout 5 --retry 6 -s https://codecov.io/bash -o codecov-upload.sh
 | 
				
			||||||
bash codecov-upload.sh -Z -X fix -f coverage.xml
 | 
					bash codecov-upload.sh -Z -X fix -f coverage.xml "$@"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,21 +0,0 @@
 | 
				
			||||||
@echo off
 | 
					 | 
				
			||||||
rem Source: https://github.com/appveyor/ci/blob/master/scripts/appveyor-retry.cmd
 | 
					 | 
				
			||||||
rem initiate the retry number
 | 
					 | 
				
			||||||
set retryNumber=0
 | 
					 | 
				
			||||||
set maxRetries=3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
:RUN
 | 
					 | 
				
			||||||
%*
 | 
					 | 
				
			||||||
set LastErrorLevel=%ERRORLEVEL%
 | 
					 | 
				
			||||||
IF %LastErrorLevel% == 0 GOTO :EOF
 | 
					 | 
				
			||||||
set /a retryNumber=%retryNumber%+1
 | 
					 | 
				
			||||||
IF %reTryNumber% == %maxRetries% (GOTO :FAILED)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
:RETRY
 | 
					 | 
				
			||||||
set /a retryNumberDisp=%retryNumber%+1
 | 
					 | 
				
			||||||
@echo Command "%*" failed with exit code %LastErrorLevel%. Retrying %retryNumberDisp% of %maxRetries%
 | 
					 | 
				
			||||||
GOTO :RUN
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
: FAILED
 | 
					 | 
				
			||||||
@echo Sorry, we tried running command for %maxRetries% times and all attempts were unsuccessful!
 | 
					 | 
				
			||||||
EXIT /B %LastErrorLevel%
 | 
					 | 
				
			||||||
| 
						 | 
					@ -29,6 +29,7 @@ import pluggy
 | 
				
			||||||
import py
 | 
					import py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import _pytest
 | 
					import _pytest
 | 
				
			||||||
 | 
					from _pytest._io import TerminalWriter
 | 
				
			||||||
from _pytest._io.saferepr import safeformat
 | 
					from _pytest._io.saferepr import safeformat
 | 
				
			||||||
from _pytest._io.saferepr import saferepr
 | 
					from _pytest._io.saferepr import saferepr
 | 
				
			||||||
from _pytest.compat import overload
 | 
					from _pytest.compat import overload
 | 
				
			||||||
| 
						 | 
					@ -913,14 +914,14 @@ class TerminalRepr:
 | 
				
			||||||
        # FYI this is called from pytest-xdist's serialization of exception
 | 
					        # FYI this is called from pytest-xdist's serialization of exception
 | 
				
			||||||
        # information.
 | 
					        # information.
 | 
				
			||||||
        io = StringIO()
 | 
					        io = StringIO()
 | 
				
			||||||
        tw = py.io.TerminalWriter(file=io)
 | 
					        tw = TerminalWriter(file=io)
 | 
				
			||||||
        self.toterminal(tw)
 | 
					        self.toterminal(tw)
 | 
				
			||||||
        return io.getvalue().strip()
 | 
					        return io.getvalue().strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __repr__(self) -> str:
 | 
					    def __repr__(self) -> str:
 | 
				
			||||||
        return "<{} instance at {:0x}>".format(self.__class__, id(self))
 | 
					        return "<{} instance at {:0x}>".format(self.__class__, id(self))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def toterminal(self, tw: py.io.TerminalWriter) -> None:
 | 
					    def toterminal(self, tw: TerminalWriter) -> None:
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -931,7 +932,7 @@ class ExceptionRepr(TerminalRepr):
 | 
				
			||||||
    def addsection(self, name: str, content: str, sep: str = "-") -> None:
 | 
					    def addsection(self, name: str, content: str, sep: str = "-") -> None:
 | 
				
			||||||
        self.sections.append((name, content, sep))
 | 
					        self.sections.append((name, content, sep))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def toterminal(self, tw: py.io.TerminalWriter) -> None:
 | 
					    def toterminal(self, tw: TerminalWriter) -> None:
 | 
				
			||||||
        for name, content, sep in self.sections:
 | 
					        for name, content, sep in self.sections:
 | 
				
			||||||
            tw.sep(sep, name)
 | 
					            tw.sep(sep, name)
 | 
				
			||||||
            tw.line(content)
 | 
					            tw.line(content)
 | 
				
			||||||
| 
						 | 
					@ -951,7 +952,7 @@ class ExceptionChainRepr(ExceptionRepr):
 | 
				
			||||||
        self.reprtraceback = chain[-1][0]
 | 
					        self.reprtraceback = chain[-1][0]
 | 
				
			||||||
        self.reprcrash = chain[-1][1]
 | 
					        self.reprcrash = chain[-1][1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def toterminal(self, tw: py.io.TerminalWriter) -> None:
 | 
					    def toterminal(self, tw: TerminalWriter) -> None:
 | 
				
			||||||
        for element in self.chain:
 | 
					        for element in self.chain:
 | 
				
			||||||
            element[0].toterminal(tw)
 | 
					            element[0].toterminal(tw)
 | 
				
			||||||
            if element[2] is not None:
 | 
					            if element[2] is not None:
 | 
				
			||||||
| 
						 | 
					@ -968,7 +969,7 @@ class ReprExceptionInfo(ExceptionRepr):
 | 
				
			||||||
        self.reprtraceback = reprtraceback
 | 
					        self.reprtraceback = reprtraceback
 | 
				
			||||||
        self.reprcrash = reprcrash
 | 
					        self.reprcrash = reprcrash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def toterminal(self, tw: py.io.TerminalWriter) -> None:
 | 
					    def toterminal(self, tw: TerminalWriter) -> None:
 | 
				
			||||||
        self.reprtraceback.toterminal(tw)
 | 
					        self.reprtraceback.toterminal(tw)
 | 
				
			||||||
        super().toterminal(tw)
 | 
					        super().toterminal(tw)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -986,7 +987,7 @@ class ReprTraceback(TerminalRepr):
 | 
				
			||||||
        self.extraline = extraline
 | 
					        self.extraline = extraline
 | 
				
			||||||
        self.style = style
 | 
					        self.style = style
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def toterminal(self, tw: py.io.TerminalWriter) -> None:
 | 
					    def toterminal(self, tw: TerminalWriter) -> None:
 | 
				
			||||||
        # the entries might have different styles
 | 
					        # the entries might have different styles
 | 
				
			||||||
        for i, entry in enumerate(self.reprentries):
 | 
					        for i, entry in enumerate(self.reprentries):
 | 
				
			||||||
            if entry.style == "long":
 | 
					            if entry.style == "long":
 | 
				
			||||||
| 
						 | 
					@ -1018,7 +1019,7 @@ class ReprEntryNative(TerminalRepr):
 | 
				
			||||||
    def __init__(self, tblines: Sequence[str]) -> None:
 | 
					    def __init__(self, tblines: Sequence[str]) -> None:
 | 
				
			||||||
        self.lines = tblines
 | 
					        self.lines = tblines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def toterminal(self, tw: py.io.TerminalWriter) -> None:
 | 
					    def toterminal(self, tw: TerminalWriter) -> None:
 | 
				
			||||||
        tw.write("".join(self.lines))
 | 
					        tw.write("".join(self.lines))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1037,7 +1038,7 @@ class ReprEntry(TerminalRepr):
 | 
				
			||||||
        self.reprfileloc = filelocrepr
 | 
					        self.reprfileloc = filelocrepr
 | 
				
			||||||
        self.style = style
 | 
					        self.style = style
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def toterminal(self, tw: py.io.TerminalWriter) -> None:
 | 
					    def toterminal(self, tw: TerminalWriter) -> None:
 | 
				
			||||||
        if self.style == "short":
 | 
					        if self.style == "short":
 | 
				
			||||||
            assert self.reprfileloc is not None
 | 
					            assert self.reprfileloc is not None
 | 
				
			||||||
            self.reprfileloc.toterminal(tw)
 | 
					            self.reprfileloc.toterminal(tw)
 | 
				
			||||||
| 
						 | 
					@ -1072,7 +1073,7 @@ class ReprFileLocation(TerminalRepr):
 | 
				
			||||||
        self.lineno = lineno
 | 
					        self.lineno = lineno
 | 
				
			||||||
        self.message = message
 | 
					        self.message = message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def toterminal(self, tw: py.io.TerminalWriter) -> None:
 | 
					    def toterminal(self, tw: TerminalWriter) -> None:
 | 
				
			||||||
        # filename and lineno output for each entry,
 | 
					        # filename and lineno output for each entry,
 | 
				
			||||||
        # using an output format that most editors understand
 | 
					        # using an output format that most editors understand
 | 
				
			||||||
        msg = self.message
 | 
					        msg = self.message
 | 
				
			||||||
| 
						 | 
					@ -1087,7 +1088,7 @@ class ReprLocals(TerminalRepr):
 | 
				
			||||||
    def __init__(self, lines: Sequence[str]) -> None:
 | 
					    def __init__(self, lines: Sequence[str]) -> None:
 | 
				
			||||||
        self.lines = lines
 | 
					        self.lines = lines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def toterminal(self, tw: py.io.TerminalWriter, indent="") -> None:
 | 
					    def toterminal(self, tw: TerminalWriter, indent="") -> None:
 | 
				
			||||||
        for line in self.lines:
 | 
					        for line in self.lines:
 | 
				
			||||||
            tw.line(indent + line)
 | 
					            tw.line(indent + line)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1096,7 +1097,7 @@ class ReprFuncArgs(TerminalRepr):
 | 
				
			||||||
    def __init__(self, args: Sequence[Tuple[str, object]]) -> None:
 | 
					    def __init__(self, args: Sequence[Tuple[str, object]]) -> None:
 | 
				
			||||||
        self.args = args
 | 
					        self.args = args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def toterminal(self, tw: py.io.TerminalWriter) -> None:
 | 
					    def toterminal(self, tw: TerminalWriter) -> None:
 | 
				
			||||||
        if self.args:
 | 
					        if self.args:
 | 
				
			||||||
            linesofar = ""
 | 
					            linesofar = ""
 | 
				
			||||||
            for name, value in self.args:
 | 
					            for name, value in self.args:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -277,7 +277,7 @@ def compile_(  # noqa: F811
 | 
				
			||||||
    return s.compile(filename, mode, flags, _genframe=_genframe)
 | 
					    return s.compile(filename, mode, flags, _genframe=_genframe)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def getfslineno(obj) -> Tuple[Union[str, py.path.local], int]:
 | 
					def getfslineno(obj) -> Tuple[Optional[Union["Literal['']", py.path.local]], int]:
 | 
				
			||||||
    """ Return source location (path, lineno) for the given object.
 | 
					    """ Return source location (path, lineno) for the given object.
 | 
				
			||||||
    If the source cannot be determined return ("", -1).
 | 
					    If the source cannot be determined return ("", -1).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					# Reexport TerminalWriter from here instead of py, to make it easier to
 | 
				
			||||||
 | 
					# extend or swap our own implementation in the future.
 | 
				
			||||||
 | 
					from py.io import TerminalWriter as TerminalWriter  # noqa: F401
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@ from .pathlib import Path
 | 
				
			||||||
from .pathlib import resolve_from_str
 | 
					from .pathlib import resolve_from_str
 | 
				
			||||||
from .pathlib import rm_rf
 | 
					from .pathlib import rm_rf
 | 
				
			||||||
from _pytest import nodes
 | 
					from _pytest import nodes
 | 
				
			||||||
 | 
					from _pytest._io import TerminalWriter
 | 
				
			||||||
from _pytest.config import Config
 | 
					from _pytest.config import Config
 | 
				
			||||||
from _pytest.main import Session
 | 
					from _pytest.main import Session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -418,7 +419,7 @@ def pytest_report_header(config):
 | 
				
			||||||
def cacheshow(config, session):
 | 
					def cacheshow(config, session):
 | 
				
			||||||
    from pprint import pformat
 | 
					    from pprint import pformat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tw = py.io.TerminalWriter()
 | 
					    tw = TerminalWriter()
 | 
				
			||||||
    tw.line("cachedir: " + str(config.cache._cachedir))
 | 
					    tw.line("cachedir: " + str(config.cache._cachedir))
 | 
				
			||||||
    if not config.cache._cachedir.is_dir():
 | 
					    if not config.cache._cachedir.is_dir():
 | 
				
			||||||
        tw.line("cache is empty")
 | 
					        tw.line("cache is empty")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -308,7 +308,7 @@ def get_real_method(obj, holder):
 | 
				
			||||||
    return obj
 | 
					    return obj
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def getfslineno(obj):
 | 
					def getfslineno(obj) -> Tuple[Union[str, py.path.local], int]:
 | 
				
			||||||
    # xxx let decorators etc specify a sane ordering
 | 
					    # xxx let decorators etc specify a sane ordering
 | 
				
			||||||
    obj = get_real_func(obj)
 | 
					    obj = get_real_func(obj)
 | 
				
			||||||
    if hasattr(obj, "place_as"):
 | 
					    if hasattr(obj, "place_as"):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,6 +36,7 @@ from .findpaths import determine_setup
 | 
				
			||||||
from .findpaths import exists
 | 
					from .findpaths import exists
 | 
				
			||||||
from _pytest._code import ExceptionInfo
 | 
					from _pytest._code import ExceptionInfo
 | 
				
			||||||
from _pytest._code import filter_traceback
 | 
					from _pytest._code import filter_traceback
 | 
				
			||||||
 | 
					from _pytest._io import TerminalWriter
 | 
				
			||||||
from _pytest.compat import importlib_metadata
 | 
					from _pytest.compat import importlib_metadata
 | 
				
			||||||
from _pytest.compat import TYPE_CHECKING
 | 
					from _pytest.compat import TYPE_CHECKING
 | 
				
			||||||
from _pytest.outcomes import fail
 | 
					from _pytest.outcomes import fail
 | 
				
			||||||
| 
						 | 
					@ -75,7 +76,7 @@ def main(args=None, plugins=None) -> "Union[int, _pytest.main.ExitCode]":
 | 
				
			||||||
            config = _prepareconfig(args, plugins)
 | 
					            config = _prepareconfig(args, plugins)
 | 
				
			||||||
        except ConftestImportFailure as e:
 | 
					        except ConftestImportFailure as e:
 | 
				
			||||||
            exc_info = ExceptionInfo(e.excinfo)
 | 
					            exc_info = ExceptionInfo(e.excinfo)
 | 
				
			||||||
            tw = py.io.TerminalWriter(sys.stderr)
 | 
					            tw = TerminalWriter(sys.stderr)
 | 
				
			||||||
            tw.line(
 | 
					            tw.line(
 | 
				
			||||||
                "ImportError while loading conftest '{e.path}'.".format(e=e), red=True
 | 
					                "ImportError while loading conftest '{e.path}'.".format(e=e), red=True
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
| 
						 | 
					@ -101,7 +102,7 @@ def main(args=None, plugins=None) -> "Union[int, _pytest.main.ExitCode]":
 | 
				
			||||||
            finally:
 | 
					            finally:
 | 
				
			||||||
                config._ensure_unconfigure()
 | 
					                config._ensure_unconfigure()
 | 
				
			||||||
    except UsageError as e:
 | 
					    except UsageError as e:
 | 
				
			||||||
        tw = py.io.TerminalWriter(sys.stderr)
 | 
					        tw = TerminalWriter(sys.stderr)
 | 
				
			||||||
        for msg in e.args:
 | 
					        for msg in e.args:
 | 
				
			||||||
            tw.line("ERROR: {}\n".format(msg), red=True)
 | 
					            tw.line("ERROR: {}\n".format(msg), red=True)
 | 
				
			||||||
        return ExitCode.USAGE_ERROR
 | 
					        return ExitCode.USAGE_ERROR
 | 
				
			||||||
| 
						 | 
					@ -1177,12 +1178,12 @@ def setns(obj, dic):
 | 
				
			||||||
            setattr(pytest, name, value)
 | 
					            setattr(pytest, name, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_terminal_writer(config, *args, **kwargs):
 | 
					def create_terminal_writer(config: Config, *args, **kwargs) -> TerminalWriter:
 | 
				
			||||||
    """Create a TerminalWriter instance configured according to the options
 | 
					    """Create a TerminalWriter instance configured according to the options
 | 
				
			||||||
    in the config object. Every code which requires a TerminalWriter object
 | 
					    in the config object. Every code which requires a TerminalWriter object
 | 
				
			||||||
    and has access to a config object should use this function.
 | 
					    and has access to a config object should use this function.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    tw = py.io.TerminalWriter(*args, **kwargs)
 | 
					    tw = TerminalWriter(*args, **kwargs)
 | 
				
			||||||
    if config.option.color == "yes":
 | 
					    if config.option.color == "yes":
 | 
				
			||||||
        tw.hasmarkup = True
 | 
					        tw.hasmarkup = True
 | 
				
			||||||
    if config.option.color == "no":
 | 
					    if config.option.color == "no":
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -102,8 +102,8 @@ class Parser:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.optparser = self._getparser()
 | 
					        self.optparser = self._getparser()
 | 
				
			||||||
        try_argcomplete(self.optparser)
 | 
					        try_argcomplete(self.optparser)
 | 
				
			||||||
        args = [str(x) if isinstance(x, py.path.local) else x for x in args]
 | 
					        strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
 | 
				
			||||||
        return self.optparser.parse_args(args, namespace=namespace)
 | 
					        return self.optparser.parse_args(strargs, namespace=namespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _getparser(self) -> "MyOptionParser":
 | 
					    def _getparser(self) -> "MyOptionParser":
 | 
				
			||||||
        from _pytest._argcomplete import filescompleter
 | 
					        from _pytest._argcomplete import filescompleter
 | 
				
			||||||
| 
						 | 
					@ -154,8 +154,8 @@ class Parser:
 | 
				
			||||||
        the remaining arguments unknown at this point.
 | 
					        the remaining arguments unknown at this point.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        optparser = self._getparser()
 | 
					        optparser = self._getparser()
 | 
				
			||||||
        args = [str(x) if isinstance(x, py.path.local) else x for x in args]
 | 
					        strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
 | 
				
			||||||
        return optparser.parse_known_args(args, namespace=namespace)
 | 
					        return optparser.parse_known_args(strargs, namespace=namespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def addini(
 | 
					    def addini(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,9 @@
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					from typing import Any
 | 
				
			||||||
 | 
					from typing import Iterable
 | 
				
			||||||
from typing import List
 | 
					from typing import List
 | 
				
			||||||
from typing import Optional
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					from typing import Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import py
 | 
					import py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,7 +63,7 @@ def getcfg(args, config=None):
 | 
				
			||||||
    return None, None, None
 | 
					    return None, None, None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_common_ancestor(paths):
 | 
					def get_common_ancestor(paths: Iterable[py.path.local]) -> py.path.local:
 | 
				
			||||||
    common_ancestor = None
 | 
					    common_ancestor = None
 | 
				
			||||||
    for path in paths:
 | 
					    for path in paths:
 | 
				
			||||||
        if not path.exists():
 | 
					        if not path.exists():
 | 
				
			||||||
| 
						 | 
					@ -113,7 +116,7 @@ def determine_setup(
 | 
				
			||||||
    args: List[str],
 | 
					    args: List[str],
 | 
				
			||||||
    rootdir_cmd_arg: Optional[str] = None,
 | 
					    rootdir_cmd_arg: Optional[str] = None,
 | 
				
			||||||
    config: Optional["Config"] = None,
 | 
					    config: Optional["Config"] = None,
 | 
				
			||||||
):
 | 
					) -> Tuple[py.path.local, Optional[str], Any]:
 | 
				
			||||||
    dirs = get_dirs_from_args(args)
 | 
					    dirs = get_dirs_from_args(args)
 | 
				
			||||||
    if inifile:
 | 
					    if inifile:
 | 
				
			||||||
        iniconfig = py.iniconfig.IniConfig(inifile)
 | 
					        iniconfig = py.iniconfig.IniConfig(inifile)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,13 +13,14 @@ from typing import Sequence
 | 
				
			||||||
from typing import Tuple
 | 
					from typing import Tuple
 | 
				
			||||||
from typing import Union
 | 
					from typing import Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import py
 | 
					import py.path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
from _pytest import outcomes
 | 
					from _pytest import outcomes
 | 
				
			||||||
from _pytest._code.code import ExceptionInfo
 | 
					from _pytest._code.code import ExceptionInfo
 | 
				
			||||||
from _pytest._code.code import ReprFileLocation
 | 
					from _pytest._code.code import ReprFileLocation
 | 
				
			||||||
from _pytest._code.code import TerminalRepr
 | 
					from _pytest._code.code import TerminalRepr
 | 
				
			||||||
 | 
					from _pytest._io import TerminalWriter
 | 
				
			||||||
from _pytest.compat import safe_getattr
 | 
					from _pytest.compat import safe_getattr
 | 
				
			||||||
from _pytest.compat import TYPE_CHECKING
 | 
					from _pytest.compat import TYPE_CHECKING
 | 
				
			||||||
from _pytest.fixtures import FixtureRequest
 | 
					from _pytest.fixtures import FixtureRequest
 | 
				
			||||||
| 
						 | 
					@ -139,7 +140,7 @@ class ReprFailDoctest(TerminalRepr):
 | 
				
			||||||
    ):
 | 
					    ):
 | 
				
			||||||
        self.reprlocation_lines = reprlocation_lines
 | 
					        self.reprlocation_lines = reprlocation_lines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def toterminal(self, tw: py.io.TerminalWriter) -> None:
 | 
					    def toterminal(self, tw: TerminalWriter) -> None:
 | 
				
			||||||
        for reprlocation, lines in self.reprlocation_lines:
 | 
					        for reprlocation, lines in self.reprlocation_lines:
 | 
				
			||||||
            for line in lines:
 | 
					            for line in lines:
 | 
				
			||||||
                tw.line(line)
 | 
					                tw.line(line)
 | 
				
			||||||
| 
						 | 
					@ -312,7 +313,7 @@ class DoctestItem(pytest.Item):
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return super().repr_failure(excinfo)
 | 
					            return super().repr_failure(excinfo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def reportinfo(self) -> Tuple[str, int, str]:
 | 
					    def reportinfo(self) -> Tuple[py.path.local, int, str]:
 | 
				
			||||||
        return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
 | 
					        return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,7 @@ import py
 | 
				
			||||||
import _pytest
 | 
					import _pytest
 | 
				
			||||||
from _pytest._code.code import FormattedExcinfo
 | 
					from _pytest._code.code import FormattedExcinfo
 | 
				
			||||||
from _pytest._code.code import TerminalRepr
 | 
					from _pytest._code.code import TerminalRepr
 | 
				
			||||||
 | 
					from _pytest._io import TerminalWriter
 | 
				
			||||||
from _pytest.compat import _format_args
 | 
					from _pytest.compat import _format_args
 | 
				
			||||||
from _pytest.compat import _PytestWrapper
 | 
					from _pytest.compat import _PytestWrapper
 | 
				
			||||||
from _pytest.compat import get_real_func
 | 
					from _pytest.compat import get_real_func
 | 
				
			||||||
| 
						 | 
					@ -352,7 +353,7 @@ class FixtureRequest:
 | 
				
			||||||
        self.fixturename = None
 | 
					        self.fixturename = None
 | 
				
			||||||
        #: Scope string, one of "function", "class", "module", "session"
 | 
					        #: Scope string, one of "function", "class", "module", "session"
 | 
				
			||||||
        self.scope = "function"
 | 
					        self.scope = "function"
 | 
				
			||||||
        self._fixture_defs = {}  # argname -> FixtureDef
 | 
					        self._fixture_defs = {}  # type: Dict[str, FixtureDef]
 | 
				
			||||||
        fixtureinfo = pyfuncitem._fixtureinfo
 | 
					        fixtureinfo = pyfuncitem._fixtureinfo
 | 
				
			||||||
        self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
 | 
					        self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
 | 
				
			||||||
        self._arg2index = {}
 | 
					        self._arg2index = {}
 | 
				
			||||||
| 
						 | 
					@ -427,7 +428,8 @@ class FixtureRequest:
 | 
				
			||||||
    @scopeproperty()
 | 
					    @scopeproperty()
 | 
				
			||||||
    def fspath(self) -> py.path.local:
 | 
					    def fspath(self) -> py.path.local:
 | 
				
			||||||
        """ the file system path of the test module which collected this test. """
 | 
					        """ the file system path of the test module which collected this test. """
 | 
				
			||||||
        return self._pyfuncitem.fspath
 | 
					        # TODO: Remove ignore once _pyfuncitem is properly typed.
 | 
				
			||||||
 | 
					        return self._pyfuncitem.fspath  # type: ignore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def keywords(self):
 | 
					    def keywords(self):
 | 
				
			||||||
| 
						 | 
					@ -547,7 +549,9 @@ class FixtureRequest:
 | 
				
			||||||
                source_path = py.path.local(frameinfo.filename)
 | 
					                source_path = py.path.local(frameinfo.filename)
 | 
				
			||||||
                source_lineno = frameinfo.lineno
 | 
					                source_lineno = frameinfo.lineno
 | 
				
			||||||
                if source_path.relto(funcitem.config.rootdir):
 | 
					                if source_path.relto(funcitem.config.rootdir):
 | 
				
			||||||
                    source_path = source_path.relto(funcitem.config.rootdir)
 | 
					                    source_path_str = source_path.relto(funcitem.config.rootdir)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    source_path_str = str(source_path)
 | 
				
			||||||
                msg = (
 | 
					                msg = (
 | 
				
			||||||
                    "The requested fixture has no parameter defined for test:\n"
 | 
					                    "The requested fixture has no parameter defined for test:\n"
 | 
				
			||||||
                    "    {}\n\n"
 | 
					                    "    {}\n\n"
 | 
				
			||||||
| 
						 | 
					@ -556,7 +560,7 @@ class FixtureRequest:
 | 
				
			||||||
                        funcitem.nodeid,
 | 
					                        funcitem.nodeid,
 | 
				
			||||||
                        fixturedef.argname,
 | 
					                        fixturedef.argname,
 | 
				
			||||||
                        getlocation(fixturedef.func, funcitem.config.rootdir),
 | 
					                        getlocation(fixturedef.func, funcitem.config.rootdir),
 | 
				
			||||||
                        source_path,
 | 
					                        source_path_str,
 | 
				
			||||||
                        source_lineno,
 | 
					                        source_lineno,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
| 
						 | 
					@ -749,7 +753,7 @@ class FixtureLookupErrorRepr(TerminalRepr):
 | 
				
			||||||
        self.firstlineno = firstlineno
 | 
					        self.firstlineno = firstlineno
 | 
				
			||||||
        self.argname = argname
 | 
					        self.argname = argname
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def toterminal(self, tw: py.io.TerminalWriter) -> None:
 | 
					    def toterminal(self, tw: TerminalWriter) -> None:
 | 
				
			||||||
        # tw.line("FixtureLookupError: %s" %(self.argname), red=True)
 | 
					        # tw.line("FixtureLookupError: %s" %(self.argname), red=True)
 | 
				
			||||||
        for tbline in self.tblines:
 | 
					        for tbline in self.tblines:
 | 
				
			||||||
            tw.line(tbline.rstrip())
 | 
					            tw.line(tbline.rstrip())
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@ import py
 | 
				
			||||||
import _pytest._code
 | 
					import _pytest._code
 | 
				
			||||||
from _pytest import nodes
 | 
					from _pytest import nodes
 | 
				
			||||||
from _pytest.compat import TYPE_CHECKING
 | 
					from _pytest.compat import TYPE_CHECKING
 | 
				
			||||||
 | 
					from _pytest.config import Config
 | 
				
			||||||
from _pytest.config import directory_arg
 | 
					from _pytest.config import directory_arg
 | 
				
			||||||
from _pytest.config import hookimpl
 | 
					from _pytest.config import hookimpl
 | 
				
			||||||
from _pytest.config import UsageError
 | 
					from _pytest.config import UsageError
 | 
				
			||||||
| 
						 | 
					@ -375,9 +376,9 @@ class Failed(Exception):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@attr.s
 | 
					@attr.s
 | 
				
			||||||
class _bestrelpath_cache(dict):
 | 
					class _bestrelpath_cache(dict):
 | 
				
			||||||
    path = attr.ib()
 | 
					    path = attr.ib(type=py.path.local)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __missing__(self, path: str) -> str:
 | 
					    def __missing__(self, path: py.path.local) -> str:
 | 
				
			||||||
        r = self.path.bestrelpath(path)  # type: str
 | 
					        r = self.path.bestrelpath(path)  # type: str
 | 
				
			||||||
        self[path] = r
 | 
					        self[path] = r
 | 
				
			||||||
        return r
 | 
					        return r
 | 
				
			||||||
| 
						 | 
					@ -391,7 +392,7 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
    # Set on the session by fixtures.pytest_sessionstart.
 | 
					    # Set on the session by fixtures.pytest_sessionstart.
 | 
				
			||||||
    _fixturemanager = None  # type: FixtureManager
 | 
					    _fixturemanager = None  # type: FixtureManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, config) -> None:
 | 
					    def __init__(self, config: Config) -> None:
 | 
				
			||||||
        nodes.FSCollector.__init__(
 | 
					        nodes.FSCollector.__init__(
 | 
				
			||||||
            self, config.rootdir, parent=None, config=config, session=self, nodeid=""
 | 
					            self, config.rootdir, parent=None, config=config, session=self, nodeid=""
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
| 
						 | 
					@ -411,7 +412,7 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._bestrelpathcache = _bestrelpath_cache(
 | 
					        self._bestrelpathcache = _bestrelpath_cache(
 | 
				
			||||||
            config.rootdir
 | 
					            config.rootdir
 | 
				
			||||||
        )  # type: Dict[str, str]
 | 
					        )  # type: Dict[py.path.local, str]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.config.pluginmanager.register(self, name="session")
 | 
					        self.config.pluginmanager.register(self, name="session")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -428,7 +429,7 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
            self.testscollected,
 | 
					            self.testscollected,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _node_location_to_relpath(self, node_path: str) -> str:
 | 
					    def _node_location_to_relpath(self, node_path: py.path.local) -> str:
 | 
				
			||||||
        # bestrelpath is a quite slow function
 | 
					        # bestrelpath is a quite slow function
 | 
				
			||||||
        return self._bestrelpathcache[node_path]
 | 
					        return self._bestrelpathcache[node_path]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -482,6 +482,10 @@ class Item(Node):
 | 
				
			||||||
    @cached_property
 | 
					    @cached_property
 | 
				
			||||||
    def location(self) -> Tuple[str, Optional[int], str]:
 | 
					    def location(self) -> Tuple[str, Optional[int], str]:
 | 
				
			||||||
        location = self.reportinfo()
 | 
					        location = self.reportinfo()
 | 
				
			||||||
        fspath = self.session._node_location_to_relpath(location[0])
 | 
					        if isinstance(location[0], py.path.local):
 | 
				
			||||||
 | 
					            fspath = location[0]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            fspath = py.path.local(location[0])
 | 
				
			||||||
 | 
					        relfspath = self.session._node_location_to_relpath(fspath)
 | 
				
			||||||
        assert type(location[2]) is str
 | 
					        assert type(location[2]) is str
 | 
				
			||||||
        return (fspath, location[1], location[2])
 | 
					        return (relfspath, location[1], location[2])
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,7 @@ from textwrap import dedent
 | 
				
			||||||
from typing import List
 | 
					from typing import List
 | 
				
			||||||
from typing import Optional
 | 
					from typing import Optional
 | 
				
			||||||
from typing import Tuple
 | 
					from typing import Tuple
 | 
				
			||||||
 | 
					from typing import Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import py
 | 
					import py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -282,15 +283,16 @@ class PyobjMixin(PyobjContext):
 | 
				
			||||||
        parts.reverse()
 | 
					        parts.reverse()
 | 
				
			||||||
        return ".".join(parts)
 | 
					        return ".".join(parts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def reportinfo(self) -> Tuple[str, int, str]:
 | 
					    def reportinfo(self) -> Tuple[Union[py.path.local, str], int, str]:
 | 
				
			||||||
        # XXX caching?
 | 
					        # XXX caching?
 | 
				
			||||||
        obj = self.obj
 | 
					        obj = self.obj
 | 
				
			||||||
        compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None)
 | 
					        compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None)
 | 
				
			||||||
        if isinstance(compat_co_firstlineno, int):
 | 
					        if isinstance(compat_co_firstlineno, int):
 | 
				
			||||||
            # nose compatibility
 | 
					            # nose compatibility
 | 
				
			||||||
            fspath = sys.modules[obj.__module__].__file__
 | 
					            file_path = sys.modules[obj.__module__].__file__
 | 
				
			||||||
            if fspath.endswith(".pyc"):
 | 
					            if file_path.endswith(".pyc"):
 | 
				
			||||||
                fspath = fspath[:-1]
 | 
					                file_path = file_path[:-1]
 | 
				
			||||||
 | 
					            fspath = file_path  # type: Union[py.path.local, str]
 | 
				
			||||||
            lineno = compat_co_firstlineno
 | 
					            lineno = compat_co_firstlineno
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            fspath, lineno = getfslineno(obj)
 | 
					            fspath, lineno = getfslineno(obj)
 | 
				
			||||||
| 
						 | 
					@ -369,7 +371,12 @@ class PyCollector(PyobjMixin, nodes.Collector):
 | 
				
			||||||
                if not isinstance(res, list):
 | 
					                if not isinstance(res, list):
 | 
				
			||||||
                    res = [res]
 | 
					                    res = [res]
 | 
				
			||||||
                values.extend(res)
 | 
					                values.extend(res)
 | 
				
			||||||
        values.sort(key=lambda item: item.reportinfo()[:2])
 | 
					
 | 
				
			||||||
 | 
					        def sort_key(item):
 | 
				
			||||||
 | 
					            fspath, lineno, _ = item.reportinfo()
 | 
				
			||||||
 | 
					            return (str(fspath), lineno)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        values.sort(key=sort_key)
 | 
				
			||||||
        return values
 | 
					        return values
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _makeitem(self, name, obj):
 | 
					    def _makeitem(self, name, obj):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,7 @@ from _pytest._code.code import ReprFuncArgs
 | 
				
			||||||
from _pytest._code.code import ReprLocals
 | 
					from _pytest._code.code import ReprLocals
 | 
				
			||||||
from _pytest._code.code import ReprTraceback
 | 
					from _pytest._code.code import ReprTraceback
 | 
				
			||||||
from _pytest._code.code import TerminalRepr
 | 
					from _pytest._code.code import TerminalRepr
 | 
				
			||||||
 | 
					from _pytest._io import TerminalWriter
 | 
				
			||||||
from _pytest.compat import TYPE_CHECKING
 | 
					from _pytest.compat import TYPE_CHECKING
 | 
				
			||||||
from _pytest.nodes import Node
 | 
					from _pytest.nodes import Node
 | 
				
			||||||
from _pytest.outcomes import skip
 | 
					from _pytest.outcomes import skip
 | 
				
			||||||
| 
						 | 
					@ -80,7 +81,7 @@ class BaseReport:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .. versionadded:: 3.0
 | 
					        .. versionadded:: 3.0
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        tw = py.io.TerminalWriter(stringio=True)
 | 
					        tw = TerminalWriter(stringio=True)
 | 
				
			||||||
        tw.hasmarkup = False
 | 
					        tw.hasmarkup = False
 | 
				
			||||||
        self.toterminal(tw)
 | 
					        self.toterminal(tw)
 | 
				
			||||||
        exc = tw.stringio.getvalue()
 | 
					        exc = tw.stringio.getvalue()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,8 @@ import pytest
 | 
				
			||||||
from _pytest._code.code import ExceptionChainRepr
 | 
					from _pytest._code.code import ExceptionChainRepr
 | 
				
			||||||
from _pytest._code.code import ExceptionInfo
 | 
					from _pytest._code.code import ExceptionInfo
 | 
				
			||||||
from _pytest._code.code import FormattedExcinfo
 | 
					from _pytest._code.code import FormattedExcinfo
 | 
				
			||||||
 | 
					from _pytest._io import TerminalWriter
 | 
				
			||||||
 | 
					from _pytest.pytester import LineMatcher
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    import importlib
 | 
					    import importlib
 | 
				
			||||||
| 
						 | 
					@ -775,14 +776,43 @@ raise ValueError()
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        excinfo = pytest.raises(ValueError, mod.entry)
 | 
					        excinfo = pytest.raises(ValueError, mod.entry)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        p = FormattedExcinfo()
 | 
					        p = FormattedExcinfo(abspath=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        raised = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        orig_getcwd = os.getcwd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def raiseos():
 | 
					        def raiseos():
 | 
				
			||||||
            raise OSError(2)
 | 
					            nonlocal raised
 | 
				
			||||||
 | 
					            if sys._getframe().f_back.f_code.co_name == "checked_call":
 | 
				
			||||||
 | 
					                # Only raise with expected calls, but not via e.g. inspect for
 | 
				
			||||||
 | 
					                # py38-windows.
 | 
				
			||||||
 | 
					                raised += 1
 | 
				
			||||||
 | 
					                raise OSError(2, "custom_oserror")
 | 
				
			||||||
 | 
					            return orig_getcwd()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        monkeypatch.setattr(os, "getcwd", raiseos)
 | 
					        monkeypatch.setattr(os, "getcwd", raiseos)
 | 
				
			||||||
        assert p._makepath(__file__) == __file__
 | 
					        assert p._makepath(__file__) == __file__
 | 
				
			||||||
        p.repr_traceback(excinfo)
 | 
					        assert raised == 1
 | 
				
			||||||
 | 
					        repr_tb = p.repr_traceback(excinfo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        matcher = LineMatcher(str(repr_tb).splitlines())
 | 
				
			||||||
 | 
					        matcher.fnmatch_lines(
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                "def entry():",
 | 
				
			||||||
 | 
					                ">       f(0)",
 | 
				
			||||||
 | 
					                "",
 | 
				
			||||||
 | 
					                "{}:5: ".format(mod.__file__),
 | 
				
			||||||
 | 
					                "_ _ *",
 | 
				
			||||||
 | 
					                "",
 | 
				
			||||||
 | 
					                "    def f(x):",
 | 
				
			||||||
 | 
					                ">       raise ValueError(x)",
 | 
				
			||||||
 | 
					                "E       ValueError: 0",
 | 
				
			||||||
 | 
					                "",
 | 
				
			||||||
 | 
					                "{}:3: ValueError".format(mod.__file__),
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert raised == 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_repr_excinfo_addouterr(self, importasmod, tw_mock):
 | 
					    def test_repr_excinfo_addouterr(self, importasmod, tw_mock):
 | 
				
			||||||
        mod = importasmod(
 | 
					        mod = importasmod(
 | 
				
			||||||
| 
						 | 
					@ -855,7 +885,7 @@ raise ValueError()
 | 
				
			||||||
        from _pytest._code.code import TerminalRepr
 | 
					        from _pytest._code.code import TerminalRepr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class MyRepr(TerminalRepr):
 | 
					        class MyRepr(TerminalRepr):
 | 
				
			||||||
            def toterminal(self, tw: py.io.TerminalWriter) -> None:
 | 
					            def toterminal(self, tw: TerminalWriter) -> None:
 | 
				
			||||||
                tw.line("я")
 | 
					                tw.line("я")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        x = str(MyRepr())
 | 
					        x = str(MyRepr())
 | 
				
			||||||
| 
						 | 
					@ -1005,7 +1035,7 @@ raise ValueError()
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        excinfo = pytest.raises(ValueError, mod.f)
 | 
					        excinfo = pytest.raises(ValueError, mod.f)
 | 
				
			||||||
        tw = py.io.TerminalWriter(stringio=True)
 | 
					        tw = TerminalWriter(stringio=True)
 | 
				
			||||||
        repr = excinfo.getrepr(**reproptions)
 | 
					        repr = excinfo.getrepr(**reproptions)
 | 
				
			||||||
        repr.toterminal(tw)
 | 
					        repr.toterminal(tw)
 | 
				
			||||||
        assert tw.stringio.getvalue()
 | 
					        assert tw.stringio.getvalue()
 | 
				
			||||||
| 
						 | 
					@ -1200,8 +1230,6 @@ raise ValueError()
 | 
				
			||||||
        real traceback, such as those raised in a subprocess submitted by the multiprocessing
 | 
					        real traceback, such as those raised in a subprocess submitted by the multiprocessing
 | 
				
			||||||
        module (#1984).
 | 
					        module (#1984).
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        from _pytest.pytester import LineMatcher
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        exc_handling_code = " from e" if reason == "cause" else ""
 | 
					        exc_handling_code = " from e" if reason == "cause" else ""
 | 
				
			||||||
        mod = importasmod(
 | 
					        mod = importasmod(
 | 
				
			||||||
            """
 | 
					            """
 | 
				
			||||||
| 
						 | 
					@ -1225,7 +1253,7 @@ raise ValueError()
 | 
				
			||||||
        getattr(excinfo.value, attr).__traceback__ = None
 | 
					        getattr(excinfo.value, attr).__traceback__ = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        r = excinfo.getrepr()
 | 
					        r = excinfo.getrepr()
 | 
				
			||||||
        tw = py.io.TerminalWriter(stringio=True)
 | 
					        tw = TerminalWriter(stringio=True)
 | 
				
			||||||
        tw.hasmarkup = False
 | 
					        tw.hasmarkup = False
 | 
				
			||||||
        r.toterminal(tw)
 | 
					        r.toterminal(tw)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1320,7 +1348,6 @@ def test_exception_repr_extraction_error_on_recursion():
 | 
				
			||||||
    Ensure we can properly detect a recursion error even
 | 
					    Ensure we can properly detect a recursion error even
 | 
				
			||||||
    if some locals raise error on comparison (#2459).
 | 
					    if some locals raise error on comparison (#2459).
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    from _pytest.pytester import LineMatcher
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class numpy_like:
 | 
					    class numpy_like:
 | 
				
			||||||
        def __eq__(self, other):
 | 
					        def __eq__(self, other):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import py.io
 | 
					from _pytest._io import TerminalWriter
 | 
				
			||||||
 | 
					 | 
				
			||||||
from _pytest.logging import ColoredLevelFormatter
 | 
					from _pytest.logging import ColoredLevelFormatter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +21,7 @@ def test_coloredlogformatter():
 | 
				
			||||||
        class option:
 | 
					        class option:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tw = py.io.TerminalWriter()
 | 
					    tw = TerminalWriter()
 | 
				
			||||||
    tw.hasmarkup = True
 | 
					    tw.hasmarkup = True
 | 
				
			||||||
    formatter = ColoredLevelFormatter(tw, logfmt)
 | 
					    formatter = ColoredLevelFormatter(tw, logfmt)
 | 
				
			||||||
    output = formatter.format(record)
 | 
					    output = formatter.format(record)
 | 
				
			||||||
| 
						 | 
					@ -142,7 +141,7 @@ def test_colored_short_level():
 | 
				
			||||||
        class option:
 | 
					        class option:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tw = py.io.TerminalWriter()
 | 
					    tw = TerminalWriter()
 | 
				
			||||||
    tw.hasmarkup = True
 | 
					    tw.hasmarkup = True
 | 
				
			||||||
    formatter = ColoredLevelFormatter(tw, logfmt)
 | 
					    formatter = ColoredLevelFormatter(tw, logfmt)
 | 
				
			||||||
    output = formatter.format(record)
 | 
					    output = formatter.format(record)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,7 +68,7 @@ class TestModule:
 | 
				
			||||||
    def test_invalid_test_module_name(self, testdir):
 | 
					    def test_invalid_test_module_name(self, testdir):
 | 
				
			||||||
        a = testdir.mkdir("a")
 | 
					        a = testdir.mkdir("a")
 | 
				
			||||||
        a.ensure("test_one.part1.py")
 | 
					        a.ensure("test_one.part1.py")
 | 
				
			||||||
        result = testdir.runpytest("-rw")
 | 
					        result = testdir.runpytest()
 | 
				
			||||||
        result.stdout.fnmatch_lines(
 | 
					        result.stdout.fnmatch_lines(
 | 
				
			||||||
            [
 | 
					            [
 | 
				
			||||||
                "ImportError while importing test module*test_one.part1*",
 | 
					                "ImportError while importing test module*test_one.part1*",
 | 
				
			||||||
| 
						 | 
					@ -137,7 +137,7 @@ class TestClass:
 | 
				
			||||||
                    pass
 | 
					                    pass
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        result = testdir.runpytest("-rw")
 | 
					        result = testdir.runpytest()
 | 
				
			||||||
        result.stdout.fnmatch_lines(
 | 
					        result.stdout.fnmatch_lines(
 | 
				
			||||||
            [
 | 
					            [
 | 
				
			||||||
                "*cannot collect test class 'TestClass1' because it has "
 | 
					                "*cannot collect test class 'TestClass1' because it has "
 | 
				
			||||||
| 
						 | 
					@ -153,7 +153,7 @@ class TestClass:
 | 
				
			||||||
                    pass
 | 
					                    pass
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        result = testdir.runpytest("-rw")
 | 
					        result = testdir.runpytest()
 | 
				
			||||||
        result.stdout.fnmatch_lines(
 | 
					        result.stdout.fnmatch_lines(
 | 
				
			||||||
            [
 | 
					            [
 | 
				
			||||||
                "*cannot collect test class 'TestClass1' because it has "
 | 
					                "*cannot collect test class 'TestClass1' because it has "
 | 
				
			||||||
| 
						 | 
					@ -230,7 +230,7 @@ class TestClass:
 | 
				
			||||||
            TestCase = collections.namedtuple('TestCase', ['a'])
 | 
					            TestCase = collections.namedtuple('TestCase', ['a'])
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        result = testdir.runpytest("-rw")
 | 
					        result = testdir.runpytest()
 | 
				
			||||||
        result.stdout.fnmatch_lines(
 | 
					        result.stdout.fnmatch_lines(
 | 
				
			||||||
            "*cannot collect test class 'TestCase' "
 | 
					            "*cannot collect test class 'TestCase' "
 | 
				
			||||||
            "because it has a __new__ constructor*"
 | 
					            "because it has a __new__ constructor*"
 | 
				
			||||||
| 
						 | 
					@ -1162,7 +1162,7 @@ def test_dont_collect_non_function_callable(testdir):
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    result = testdir.runpytest("-rw")
 | 
					    result = testdir.runpytest()
 | 
				
			||||||
    result.stdout.fnmatch_lines(
 | 
					    result.stdout.fnmatch_lines(
 | 
				
			||||||
        [
 | 
					        [
 | 
				
			||||||
            "*collected 1 item*",
 | 
					            "*collected 1 item*",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4207,3 +4207,38 @@ def test_fixture_parametrization_nparray(testdir):
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    result = testdir.runpytest()
 | 
					    result = testdir.runpytest()
 | 
				
			||||||
    result.assert_outcomes(passed=10)
 | 
					    result.assert_outcomes(passed=10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_fixture_arg_ordering(testdir):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This test describes how fixtures in the same scope but without explicit dependencies
 | 
				
			||||||
 | 
					    between them are created. While users should make dependencies explicit, often
 | 
				
			||||||
 | 
					    they rely on this order, so this test exists to catch regressions in this regard.
 | 
				
			||||||
 | 
					    See #6540 and #6492.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    p1 = testdir.makepyfile(
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        suffixes = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @pytest.fixture
 | 
				
			||||||
 | 
					        def fix_1(): suffixes.append("fix_1")
 | 
				
			||||||
 | 
					        @pytest.fixture
 | 
				
			||||||
 | 
					        def fix_2(): suffixes.append("fix_2")
 | 
				
			||||||
 | 
					        @pytest.fixture
 | 
				
			||||||
 | 
					        def fix_3(): suffixes.append("fix_3")
 | 
				
			||||||
 | 
					        @pytest.fixture
 | 
				
			||||||
 | 
					        def fix_4(): suffixes.append("fix_4")
 | 
				
			||||||
 | 
					        @pytest.fixture
 | 
				
			||||||
 | 
					        def fix_5(): suffixes.append("fix_5")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @pytest.fixture
 | 
				
			||||||
 | 
					        def fix_combined(fix_1, fix_2, fix_3, fix_4, fix_5): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def test_suffix(fix_combined):
 | 
				
			||||||
 | 
					            assert suffixes == ["fix_1", "fix_2", "fix_3", "fix_4", "fix_5"]
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    result = testdir.runpytest("-vv", str(p1))
 | 
				
			||||||
 | 
					    assert result.ret == 0
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1349,7 +1349,7 @@ def test_assert_indirect_tuple_no_warning(testdir):
 | 
				
			||||||
            assert tpl
 | 
					            assert tpl
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    result = testdir.runpytest("-rw")
 | 
					    result = testdir.runpytest()
 | 
				
			||||||
    output = "\n".join(result.stdout.lines)
 | 
					    output = "\n".join(result.stdout.lines)
 | 
				
			||||||
    assert "WR1" not in output
 | 
					    assert "WR1" not in output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,7 +66,7 @@ class TestNewAPI:
 | 
				
			||||||
        testdir.tmpdir.ensure_dir(".pytest_cache").chmod(0)
 | 
					        testdir.tmpdir.ensure_dir(".pytest_cache").chmod(0)
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            testdir.makepyfile("def test_error(): raise Exception")
 | 
					            testdir.makepyfile("def test_error(): raise Exception")
 | 
				
			||||||
            result = testdir.runpytest("-rw")
 | 
					            result = testdir.runpytest()
 | 
				
			||||||
            assert result.ret == 1
 | 
					            assert result.ret == 1
 | 
				
			||||||
            # warnings from nodeids, lastfailed, and stepwise
 | 
					            # warnings from nodeids, lastfailed, and stepwise
 | 
				
			||||||
            result.stdout.fnmatch_lines(
 | 
					            result.stdout.fnmatch_lines(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1134,13 +1134,14 @@ def test_record_property(testdir, run_and_parse):
 | 
				
			||||||
            record_property("foo", "<1");
 | 
					            record_property("foo", "<1");
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    result, dom = run_and_parse("-rwv")
 | 
					    result, dom = run_and_parse()
 | 
				
			||||||
    node = dom.find_first_by_tag("testsuite")
 | 
					    node = dom.find_first_by_tag("testsuite")
 | 
				
			||||||
    tnode = node.find_first_by_tag("testcase")
 | 
					    tnode = node.find_first_by_tag("testcase")
 | 
				
			||||||
    psnode = tnode.find_first_by_tag("properties")
 | 
					    psnode = tnode.find_first_by_tag("properties")
 | 
				
			||||||
    pnodes = psnode.find_by_tag("property")
 | 
					    pnodes = psnode.find_by_tag("property")
 | 
				
			||||||
    pnodes[0].assert_attr(name="bar", value="1")
 | 
					    pnodes[0].assert_attr(name="bar", value="1")
 | 
				
			||||||
    pnodes[1].assert_attr(name="foo", value="<1")
 | 
					    pnodes[1].assert_attr(name="foo", value="<1")
 | 
				
			||||||
 | 
					    result.stdout.fnmatch_lines(["*= 1 passed in *"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_record_property_same_name(testdir, run_and_parse):
 | 
					def test_record_property_same_name(testdir, run_and_parse):
 | 
				
			||||||
| 
						 | 
					@ -1151,7 +1152,7 @@ def test_record_property_same_name(testdir, run_and_parse):
 | 
				
			||||||
            record_property("foo", "baz")
 | 
					            record_property("foo", "baz")
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    result, dom = run_and_parse("-rw")
 | 
					    result, dom = run_and_parse()
 | 
				
			||||||
    node = dom.find_first_by_tag("testsuite")
 | 
					    node = dom.find_first_by_tag("testsuite")
 | 
				
			||||||
    tnode = node.find_first_by_tag("testcase")
 | 
					    tnode = node.find_first_by_tag("testcase")
 | 
				
			||||||
    psnode = tnode.find_first_by_tag("properties")
 | 
					    psnode = tnode.find_first_by_tag("properties")
 | 
				
			||||||
| 
						 | 
					@ -1193,7 +1194,7 @@ def test_record_attribute(testdir, run_and_parse):
 | 
				
			||||||
            record_xml_attribute("foo", "<1");
 | 
					            record_xml_attribute("foo", "<1");
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    result, dom = run_and_parse("-rw")
 | 
					    result, dom = run_and_parse()
 | 
				
			||||||
    node = dom.find_first_by_tag("testsuite")
 | 
					    node = dom.find_first_by_tag("testsuite")
 | 
				
			||||||
    tnode = node.find_first_by_tag("testcase")
 | 
					    tnode = node.find_first_by_tag("testcase")
 | 
				
			||||||
    tnode.assert_attr(bar="1")
 | 
					    tnode.assert_attr(bar="1")
 | 
				
			||||||
| 
						 | 
					@ -1228,7 +1229,7 @@ def test_record_fixtures_xunit2(testdir, fixture_name, run_and_parse):
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    result, dom = run_and_parse("-rw", family=None)
 | 
					    result, dom = run_and_parse(family=None)
 | 
				
			||||||
    expected_lines = []
 | 
					    expected_lines = []
 | 
				
			||||||
    if fixture_name == "record_xml_attribute":
 | 
					    if fixture_name == "record_xml_attribute":
 | 
				
			||||||
        expected_lines.append(
 | 
					        expected_lines.append(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -377,15 +377,48 @@ def test_skip_test_with_unicode(testdir):
 | 
				
			||||||
    result.stdout.fnmatch_lines(["* 1 skipped *"])
 | 
					    result.stdout.fnmatch_lines(["* 1 skipped *"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_issue_6517(testdir):
 | 
					def test_raises(testdir):
 | 
				
			||||||
    testdir.makepyfile(
 | 
					    testdir.makepyfile(
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        from nose.tools import raises
 | 
					        from nose.tools import raises
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @raises(RuntimeError)
 | 
					        @raises(RuntimeError)
 | 
				
			||||||
        def test_fail_without_tcp():
 | 
					        def test_raises_runtimeerror():
 | 
				
			||||||
            raise RuntimeError
 | 
					            raise RuntimeError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @raises(Exception)
 | 
				
			||||||
 | 
					        def test_raises_baseexception_not_caught():
 | 
				
			||||||
 | 
					            raise BaseException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @raises(BaseException)
 | 
				
			||||||
 | 
					        def test_raises_baseexception_caught():
 | 
				
			||||||
 | 
					            raise BaseException
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    result = testdir.runpytest()
 | 
					    result = testdir.runpytest("-vv")
 | 
				
			||||||
    result.stdout.fnmatch_lines(["* 1 passed *"])
 | 
					    result.stdout.fnmatch_lines(
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "test_raises.py::test_raises_runtimeerror PASSED*",
 | 
				
			||||||
 | 
					            "test_raises.py::test_raises_baseexception_not_caught FAILED*",
 | 
				
			||||||
 | 
					            "test_raises.py::test_raises_baseexception_caught PASSED*",
 | 
				
			||||||
 | 
					            "*= FAILURES =*",
 | 
				
			||||||
 | 
					            "*_ test_raises_baseexception_not_caught _*",
 | 
				
			||||||
 | 
					            "",
 | 
				
			||||||
 | 
					            "arg = (), kw = {}",
 | 
				
			||||||
 | 
					            "",
 | 
				
			||||||
 | 
					            "    def newfunc(*arg, **kw):",
 | 
				
			||||||
 | 
					            "        try:",
 | 
				
			||||||
 | 
					            ">           func(*arg, **kw)",
 | 
				
			||||||
 | 
					            "",
 | 
				
			||||||
 | 
					            "*/nose/*: ",
 | 
				
			||||||
 | 
					            "_ _ *",
 | 
				
			||||||
 | 
					            "",
 | 
				
			||||||
 | 
					            "    @raises(Exception)",
 | 
				
			||||||
 | 
					            "    def test_raises_baseexception_not_caught():",
 | 
				
			||||||
 | 
					            ">       raise BaseException",
 | 
				
			||||||
 | 
					            "E       BaseException",
 | 
				
			||||||
 | 
					            "",
 | 
				
			||||||
 | 
					            "test_raises.py:9: BaseException",
 | 
				
			||||||
 | 
					            "* 1 failed, 2 passed *",
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -256,7 +256,7 @@ class TestPytestPluginManager:
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        p.copy(p.dirpath("skipping2.py"))
 | 
					        p.copy(p.dirpath("skipping2.py"))
 | 
				
			||||||
        monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
 | 
					        monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
 | 
				
			||||||
        result = testdir.runpytest("-rw", "-p", "skipping1", syspathinsert=True)
 | 
					        result = testdir.runpytest("-p", "skipping1", syspathinsert=True)
 | 
				
			||||||
        assert result.ret == ExitCode.NO_TESTS_COLLECTED
 | 
					        assert result.ret == ExitCode.NO_TESTS_COLLECTED
 | 
				
			||||||
        result.stdout.fnmatch_lines(
 | 
					        result.stdout.fnmatch_lines(
 | 
				
			||||||
            ["*skipped plugin*skipping1*hello*", "*skipped plugin*skipping2*hello*"]
 | 
					            ["*skipped plugin*skipping1*hello*", "*skipped plugin*skipping2*hello*"]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue