Merge master into features

Conflicts:
	src/_pytest/_code/code.py
	src/_pytest/main.py
This commit is contained in:
Daniel Hahler 2020-01-24 23:44:50 +01:00
commit 09bdbffbde
41 changed files with 232 additions and 242 deletions

View File

@ -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'

View File

@ -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\.

View File

@ -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:

View File

@ -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

View File

@ -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.

View File

@ -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')

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -1 +0,0 @@
#

View File

@ -1,2 +0,0 @@
def test_quick(setup):
pass

View File

@ -1 +0,0 @@
#

View File

@ -1,6 +0,0 @@
def test_something(setup):
assert setup.timecostly == 1
def test_something_more(setup):
assert setup.timecostly == 1

View File

@ -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:

View File

@ -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

View File

@ -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 "$@"

View File

@ -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%

View File

@ -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:

View File

@ -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).

View File

@ -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

View File

@ -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")

View File

@ -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"):

View File

@ -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":

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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())

View File

@ -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]

View File

@ -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])

View File

@ -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):

View File

@ -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()

View File

@ -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):

View File

@ -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)

View File

@ -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*",

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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(

View File

@ -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 *",
]
)

View File

@ -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*"]