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",
"linting",
"docs",
"doctesting",
]
include:
@ -114,7 +116,17 @@ jobs:
- name: "linting"
python: "3.7"
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:
- uses: actions/checkout@v1
@ -144,21 +156,11 @@ jobs:
run: |
python scripts/append_codecov_token.py
- name: Combine coverage
- name: Report coverage
if: (!matrix.skip_coverage)
run: |
python -m coverage combine
python -m coverage xml
- 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 }}
env:
CODECOV_NAME: ${{ matrix.name }}
run: bash scripts/report-coverage.sh -F GHA,${{ runner.os }}
deploy:
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest'

View File

@ -64,7 +64,7 @@ repos:
_code\.|
builtin\.|
code\.|
io\.(BytesIO|saferepr)|
io\.(BytesIO|saferepr|TerminalWriter)|
path\.local\.sysfind|
process\.|
std\.

View File

@ -47,11 +47,6 @@ jobs:
python: '3.5.1'
dist: trusty
- env: TOXENV=linting,docs,doctesting PYTEST_COVERAGE=1
cache:
directories:
- $HOME/.cache/pre-commit
before_script:
- |
# Do not (re-)upload coverage with cron runs.
@ -71,7 +66,7 @@ script: tox
after_success:
- |
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
notifications:

View File

@ -166,7 +166,7 @@ Short version
#. Fork the repository.
#. 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.
#. Follow **PEP-8** for naming and `black <https://github.com/psf/black>`_ for formatting.
#. 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
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
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
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

View File

@ -1,14 +1,14 @@
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
taking a lot of time to make a new one.
.. important::
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.

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.
* ``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.
* ``deprecation``: feature deprecation.
* ``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
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
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.
- (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
with minor or major pytest releases.

View File

@ -19,7 +19,7 @@ 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.
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
@pytest.fixture()
@pytest.fixture
def cleandir():
old_cwd = os.getcwd()
newpath = tempfile.mkdtemp()
os.chdir(newpath)
yield
os.chdir(old_cwd)
shutil.rmtree(newpath)
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
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:
https://docs.pytest.org/en/latest/changelog.html
@ -15,7 +15,7 @@ For complete documentation, please visit:
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
@ -24,4 +24,4 @@ Thanks to all who contributed to this release, among them:
{contributors}
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
# 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
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 _pytest
from _pytest._io import TerminalWriter
from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr
from _pytest.compat import overload
@ -913,14 +914,14 @@ class TerminalRepr:
# FYI this is called from pytest-xdist's serialization of exception
# information.
io = StringIO()
tw = py.io.TerminalWriter(file=io)
tw = TerminalWriter(file=io)
self.toterminal(tw)
return io.getvalue().strip()
def __repr__(self) -> str:
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()
@ -931,7 +932,7 @@ class ExceptionRepr(TerminalRepr):
def addsection(self, name: str, content: str, sep: str = "-") -> None:
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:
tw.sep(sep, name)
tw.line(content)
@ -951,7 +952,7 @@ class ExceptionChainRepr(ExceptionRepr):
self.reprtraceback = chain[-1][0]
self.reprcrash = chain[-1][1]
def toterminal(self, tw: py.io.TerminalWriter) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
for element in self.chain:
element[0].toterminal(tw)
if element[2] is not None:
@ -968,7 +969,7 @@ class ReprExceptionInfo(ExceptionRepr):
self.reprtraceback = reprtraceback
self.reprcrash = reprcrash
def toterminal(self, tw: py.io.TerminalWriter) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
self.reprtraceback.toterminal(tw)
super().toterminal(tw)
@ -986,7 +987,7 @@ class ReprTraceback(TerminalRepr):
self.extraline = extraline
self.style = style
def toterminal(self, tw: py.io.TerminalWriter) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
# the entries might have different styles
for i, entry in enumerate(self.reprentries):
if entry.style == "long":
@ -1018,7 +1019,7 @@ class ReprEntryNative(TerminalRepr):
def __init__(self, tblines: Sequence[str]) -> None:
self.lines = tblines
def toterminal(self, tw: py.io.TerminalWriter) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
tw.write("".join(self.lines))
@ -1037,7 +1038,7 @@ class ReprEntry(TerminalRepr):
self.reprfileloc = filelocrepr
self.style = style
def toterminal(self, tw: py.io.TerminalWriter) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
if self.style == "short":
assert self.reprfileloc is not None
self.reprfileloc.toterminal(tw)
@ -1072,7 +1073,7 @@ class ReprFileLocation(TerminalRepr):
self.lineno = lineno
self.message = message
def toterminal(self, tw: py.io.TerminalWriter) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
# filename and lineno output for each entry,
# using an output format that most editors understand
msg = self.message
@ -1087,7 +1088,7 @@ class ReprLocals(TerminalRepr):
def __init__(self, lines: Sequence[str]) -> None:
self.lines = lines
def toterminal(self, tw: py.io.TerminalWriter, indent="") -> None:
def toterminal(self, tw: TerminalWriter, indent="") -> None:
for line in self.lines:
tw.line(indent + line)
@ -1096,7 +1097,7 @@ class ReprFuncArgs(TerminalRepr):
def __init__(self, args: Sequence[Tuple[str, object]]) -> None:
self.args = args
def toterminal(self, tw: py.io.TerminalWriter) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
if self.args:
linesofar = ""
for name, value in self.args:

View File

@ -277,7 +277,7 @@ def compile_( # noqa: F811
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.
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 rm_rf
from _pytest import nodes
from _pytest._io import TerminalWriter
from _pytest.config import Config
from _pytest.main import Session
@ -418,7 +419,7 @@ def pytest_report_header(config):
def cacheshow(config, session):
from pprint import pformat
tw = py.io.TerminalWriter()
tw = TerminalWriter()
tw.line("cachedir: " + str(config.cache._cachedir))
if not config.cache._cachedir.is_dir():
tw.line("cache is empty")

View File

@ -308,7 +308,7 @@ def get_real_method(obj, holder):
return obj
def getfslineno(obj):
def getfslineno(obj) -> Tuple[Union[str, py.path.local], int]:
# xxx let decorators etc specify a sane ordering
obj = get_real_func(obj)
if hasattr(obj, "place_as"):

View File

@ -36,6 +36,7 @@ from .findpaths import determine_setup
from .findpaths import exists
from _pytest._code import ExceptionInfo
from _pytest._code import filter_traceback
from _pytest._io import TerminalWriter
from _pytest.compat import importlib_metadata
from _pytest.compat import TYPE_CHECKING
from _pytest.outcomes import fail
@ -75,7 +76,7 @@ def main(args=None, plugins=None) -> "Union[int, _pytest.main.ExitCode]":
config = _prepareconfig(args, plugins)
except ConftestImportFailure as e:
exc_info = ExceptionInfo(e.excinfo)
tw = py.io.TerminalWriter(sys.stderr)
tw = TerminalWriter(sys.stderr)
tw.line(
"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:
config._ensure_unconfigure()
except UsageError as e:
tw = py.io.TerminalWriter(sys.stderr)
tw = TerminalWriter(sys.stderr)
for msg in e.args:
tw.line("ERROR: {}\n".format(msg), red=True)
return ExitCode.USAGE_ERROR
@ -1177,12 +1178,12 @@ def setns(obj, dic):
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
in the config object. Every code which requires a TerminalWriter object
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":
tw.hasmarkup = True
if config.option.color == "no":

View File

@ -102,8 +102,8 @@ class Parser:
self.optparser = self._getparser()
try_argcomplete(self.optparser)
args = [str(x) if isinstance(x, py.path.local) else x for x in args]
return self.optparser.parse_args(args, namespace=namespace)
strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
return self.optparser.parse_args(strargs, namespace=namespace)
def _getparser(self) -> "MyOptionParser":
from _pytest._argcomplete import filescompleter
@ -154,8 +154,8 @@ class Parser:
the remaining arguments unknown at this point.
"""
optparser = self._getparser()
args = [str(x) if isinstance(x, py.path.local) else x for x in args]
return optparser.parse_known_args(args, namespace=namespace)
strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
return optparser.parse_known_args(strargs, namespace=namespace)
def addini(
self,

View File

@ -1,6 +1,9 @@
import os
from typing import Any
from typing import Iterable
from typing import List
from typing import Optional
from typing import Tuple
import py
@ -60,7 +63,7 @@ def getcfg(args, config=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
for path in paths:
if not path.exists():
@ -113,7 +116,7 @@ def determine_setup(
args: List[str],
rootdir_cmd_arg: Optional[str] = None,
config: Optional["Config"] = None,
):
) -> Tuple[py.path.local, Optional[str], Any]:
dirs = get_dirs_from_args(args)
if inifile:
iniconfig = py.iniconfig.IniConfig(inifile)

View File

@ -13,13 +13,14 @@ from typing import Sequence
from typing import Tuple
from typing import Union
import py
import py.path
import pytest
from _pytest import outcomes
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ReprFileLocation
from _pytest._code.code import TerminalRepr
from _pytest._io import TerminalWriter
from _pytest.compat import safe_getattr
from _pytest.compat import TYPE_CHECKING
from _pytest.fixtures import FixtureRequest
@ -139,7 +140,7 @@ class ReprFailDoctest(TerminalRepr):
):
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 line in lines:
tw.line(line)
@ -312,7 +313,7 @@ class DoctestItem(pytest.Item):
else:
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

View File

@ -16,6 +16,7 @@ import py
import _pytest
from _pytest._code.code import FormattedExcinfo
from _pytest._code.code import TerminalRepr
from _pytest._io import TerminalWriter
from _pytest.compat import _format_args
from _pytest.compat import _PytestWrapper
from _pytest.compat import get_real_func
@ -352,7 +353,7 @@ class FixtureRequest:
self.fixturename = None
#: Scope string, one of "function", "class", "module", "session"
self.scope = "function"
self._fixture_defs = {} # argname -> FixtureDef
self._fixture_defs = {} # type: Dict[str, FixtureDef]
fixtureinfo = pyfuncitem._fixtureinfo
self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
self._arg2index = {}
@ -427,7 +428,8 @@ class FixtureRequest:
@scopeproperty()
def fspath(self) -> py.path.local:
""" 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
def keywords(self):
@ -547,7 +549,9 @@ class FixtureRequest:
source_path = py.path.local(frameinfo.filename)
source_lineno = frameinfo.lineno
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 = (
"The requested fixture has no parameter defined for test:\n"
" {}\n\n"
@ -556,7 +560,7 @@ class FixtureRequest:
funcitem.nodeid,
fixturedef.argname,
getlocation(fixturedef.func, funcitem.config.rootdir),
source_path,
source_path_str,
source_lineno,
)
)
@ -749,7 +753,7 @@ class FixtureLookupErrorRepr(TerminalRepr):
self.firstlineno = firstlineno
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)
for tbline in self.tblines:
tw.line(tbline.rstrip())

View File

@ -15,6 +15,7 @@ import py
import _pytest._code
from _pytest import nodes
from _pytest.compat import TYPE_CHECKING
from _pytest.config import Config
from _pytest.config import directory_arg
from _pytest.config import hookimpl
from _pytest.config import UsageError
@ -375,9 +376,9 @@ class Failed(Exception):
@attr.s
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
self[path] = r
return r
@ -391,7 +392,7 @@ class Session(nodes.FSCollector):
# Set on the session by fixtures.pytest_sessionstart.
_fixturemanager = None # type: FixtureManager
def __init__(self, config) -> None:
def __init__(self, config: Config) -> None:
nodes.FSCollector.__init__(
self, config.rootdir, parent=None, config=config, session=self, nodeid=""
)
@ -411,7 +412,7 @@ class Session(nodes.FSCollector):
self._bestrelpathcache = _bestrelpath_cache(
config.rootdir
) # type: Dict[str, str]
) # type: Dict[py.path.local, str]
self.config.pluginmanager.register(self, name="session")
@ -428,7 +429,7 @@ class Session(nodes.FSCollector):
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
return self._bestrelpathcache[node_path]

View File

@ -482,6 +482,10 @@ class Item(Node):
@cached_property
def location(self) -> Tuple[str, Optional[int], str]:
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
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 Optional
from typing import Tuple
from typing import Union
import py
@ -282,15 +283,16 @@ class PyobjMixin(PyobjContext):
parts.reverse()
return ".".join(parts)
def reportinfo(self) -> Tuple[str, int, str]:
def reportinfo(self) -> Tuple[Union[py.path.local, str], int, str]:
# XXX caching?
obj = self.obj
compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None)
if isinstance(compat_co_firstlineno, int):
# nose compatibility
fspath = sys.modules[obj.__module__].__file__
if fspath.endswith(".pyc"):
fspath = fspath[:-1]
file_path = sys.modules[obj.__module__].__file__
if file_path.endswith(".pyc"):
file_path = file_path[:-1]
fspath = file_path # type: Union[py.path.local, str]
lineno = compat_co_firstlineno
else:
fspath, lineno = getfslineno(obj)
@ -369,7 +371,12 @@ class PyCollector(PyobjMixin, nodes.Collector):
if not isinstance(res, list):
res = [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
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 ReprTraceback
from _pytest._code.code import TerminalRepr
from _pytest._io import TerminalWriter
from _pytest.compat import TYPE_CHECKING
from _pytest.nodes import Node
from _pytest.outcomes import skip
@ -80,7 +81,7 @@ class BaseReport:
.. versionadded:: 3.0
"""
tw = py.io.TerminalWriter(stringio=True)
tw = TerminalWriter(stringio=True)
tw.hasmarkup = False
self.toterminal(tw)
exc = tw.stringio.getvalue()

View File

@ -12,7 +12,8 @@ import pytest
from _pytest._code.code import ExceptionChainRepr
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import FormattedExcinfo
from _pytest._io import TerminalWriter
from _pytest.pytester import LineMatcher
try:
import importlib
@ -775,14 +776,43 @@ raise ValueError()
)
excinfo = pytest.raises(ValueError, mod.entry)
p = FormattedExcinfo()
p = FormattedExcinfo(abspath=False)
raised = 0
orig_getcwd = os.getcwd
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)
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):
mod = importasmod(
@ -855,7 +885,7 @@ raise ValueError()
from _pytest._code.code import TerminalRepr
class MyRepr(TerminalRepr):
def toterminal(self, tw: py.io.TerminalWriter) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
tw.line("я")
x = str(MyRepr())
@ -1005,7 +1035,7 @@ raise ValueError()
"""
)
excinfo = pytest.raises(ValueError, mod.f)
tw = py.io.TerminalWriter(stringio=True)
tw = TerminalWriter(stringio=True)
repr = excinfo.getrepr(**reproptions)
repr.toterminal(tw)
assert tw.stringio.getvalue()
@ -1200,8 +1230,6 @@ raise ValueError()
real traceback, such as those raised in a subprocess submitted by the multiprocessing
module (#1984).
"""
from _pytest.pytester import LineMatcher
exc_handling_code = " from e" if reason == "cause" else ""
mod = importasmod(
"""
@ -1225,7 +1253,7 @@ raise ValueError()
getattr(excinfo.value, attr).__traceback__ = None
r = excinfo.getrepr()
tw = py.io.TerminalWriter(stringio=True)
tw = TerminalWriter(stringio=True)
tw.hasmarkup = False
r.toterminal(tw)
@ -1320,7 +1348,6 @@ def test_exception_repr_extraction_error_on_recursion():
Ensure we can properly detect a recursion error even
if some locals raise error on comparison (#2459).
"""
from _pytest.pytester import LineMatcher
class numpy_like:
def __eq__(self, other):

View File

@ -1,7 +1,6 @@
import logging
import py.io
from _pytest._io import TerminalWriter
from _pytest.logging import ColoredLevelFormatter
@ -22,7 +21,7 @@ def test_coloredlogformatter():
class option:
pass
tw = py.io.TerminalWriter()
tw = TerminalWriter()
tw.hasmarkup = True
formatter = ColoredLevelFormatter(tw, logfmt)
output = formatter.format(record)
@ -142,7 +141,7 @@ def test_colored_short_level():
class option:
pass
tw = py.io.TerminalWriter()
tw = TerminalWriter()
tw.hasmarkup = True
formatter = ColoredLevelFormatter(tw, logfmt)
output = formatter.format(record)

View File

@ -68,7 +68,7 @@ class TestModule:
def test_invalid_test_module_name(self, testdir):
a = testdir.mkdir("a")
a.ensure("test_one.part1.py")
result = testdir.runpytest("-rw")
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"ImportError while importing test module*test_one.part1*",
@ -137,7 +137,7 @@ class TestClass:
pass
"""
)
result = testdir.runpytest("-rw")
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"*cannot collect test class 'TestClass1' because it has "
@ -153,7 +153,7 @@ class TestClass:
pass
"""
)
result = testdir.runpytest("-rw")
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"*cannot collect test class 'TestClass1' because it has "
@ -230,7 +230,7 @@ class TestClass:
TestCase = collections.namedtuple('TestCase', ['a'])
"""
)
result = testdir.runpytest("-rw")
result = testdir.runpytest()
result.stdout.fnmatch_lines(
"*cannot collect test class 'TestCase' "
"because it has a __new__ constructor*"
@ -1162,7 +1162,7 @@ def test_dont_collect_non_function_callable(testdir):
pass
"""
)
result = testdir.runpytest("-rw")
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"*collected 1 item*",

View File

@ -4207,3 +4207,38 @@ def test_fixture_parametrization_nparray(testdir):
)
result = testdir.runpytest()
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
"""
)
result = testdir.runpytest("-rw")
result = testdir.runpytest()
output = "\n".join(result.stdout.lines)
assert "WR1" not in output

View File

@ -66,7 +66,7 @@ class TestNewAPI:
testdir.tmpdir.ensure_dir(".pytest_cache").chmod(0)
try:
testdir.makepyfile("def test_error(): raise Exception")
result = testdir.runpytest("-rw")
result = testdir.runpytest()
assert result.ret == 1
# warnings from nodeids, lastfailed, and stepwise
result.stdout.fnmatch_lines(

View File

@ -1134,13 +1134,14 @@ def test_record_property(testdir, run_and_parse):
record_property("foo", "<1");
"""
)
result, dom = run_and_parse("-rwv")
result, dom = run_and_parse()
node = dom.find_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase")
psnode = tnode.find_first_by_tag("properties")
pnodes = psnode.find_by_tag("property")
pnodes[0].assert_attr(name="bar", 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):
@ -1151,7 +1152,7 @@ def test_record_property_same_name(testdir, run_and_parse):
record_property("foo", "baz")
"""
)
result, dom = run_and_parse("-rw")
result, dom = run_and_parse()
node = dom.find_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase")
psnode = tnode.find_first_by_tag("properties")
@ -1193,7 +1194,7 @@ def test_record_attribute(testdir, run_and_parse):
record_xml_attribute("foo", "<1");
"""
)
result, dom = run_and_parse("-rw")
result, dom = run_and_parse()
node = dom.find_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase")
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 = []
if fixture_name == "record_xml_attribute":
expected_lines.append(

View File

@ -377,15 +377,48 @@ def test_skip_test_with_unicode(testdir):
result.stdout.fnmatch_lines(["* 1 skipped *"])
def test_issue_6517(testdir):
def test_raises(testdir):
testdir.makepyfile(
"""
from nose.tools import raises
@raises(RuntimeError)
def test_fail_without_tcp():
def test_raises_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.stdout.fnmatch_lines(["* 1 passed *"])
result = testdir.runpytest("-vv")
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"))
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
result.stdout.fnmatch_lines(
["*skipped plugin*skipping1*hello*", "*skipped plugin*skipping2*hello*"]