Merge branch 'master' into term-color

Conflicts:
	src/_pytest/terminal.py
	testing/test_debugging.py
	testing/test_terminal.py
This commit is contained in:
Daniel Hahler
2020-02-15 18:46:29 +01:00
147 changed files with 3425 additions and 1446 deletions

View File

@@ -8,7 +8,7 @@ import py
import pytest
from _pytest.compat import importlib_metadata
from _pytest.main import ExitCode
from _pytest.config import ExitCode
def prepend_pythonpath(*dirs):
@@ -104,6 +104,8 @@ class TestGeneralUsage:
@pytest.mark.parametrize("load_cov_early", [True, False])
def test_early_load_setuptools_name(self, testdir, monkeypatch, load_cov_early):
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
testdir.makepyfile(mytestplugin1_module="")
testdir.makepyfile(mytestplugin2_module="")
testdir.makepyfile(mycov_module="")
@@ -188,10 +190,10 @@ class TestGeneralUsage:
)
@pytest.mark.filterwarnings("default")
def test_better_reporting_on_conftest_load_failure(self, testdir, request):
def test_better_reporting_on_conftest_load_failure(self, testdir):
"""Show a user-friendly traceback on conftest import failures (#486, #3332)"""
testdir.makepyfile("")
testdir.makeconftest(
conftest = testdir.makeconftest(
"""
def foo():
import qwerty
@@ -206,22 +208,18 @@ class TestGeneralUsage:
"""
)
result = testdir.runpytest()
dirname = request.node.name + "0"
exc_name = (
"ModuleNotFoundError" if sys.version_info >= (3, 6) else "ImportError"
)
result.stderr.fnmatch_lines(
[
"ImportError while loading conftest '*{sep}{dirname}{sep}conftest.py'.".format(
dirname=dirname, sep=os.sep
),
"conftest.py:3: in <module>",
" foo()",
"conftest.py:2: in foo",
" import qwerty",
"E {}: No module named 'qwerty'".format(exc_name),
]
)
assert result.stdout.lines == []
assert result.stderr.lines == [
"ImportError while loading conftest '{}'.".format(conftest),
"conftest.py:3: in <module>",
" foo()",
"conftest.py:2: in foo",
" import qwerty",
"E {}: No module named 'qwerty'".format(exc_name),
]
def test_early_skip(self, testdir):
testdir.mkdir("xyz")
@@ -412,7 +410,7 @@ class TestGeneralUsage:
def test_report_all_failed_collections_initargs(self, testdir):
testdir.makeconftest(
"""
from _pytest.main import ExitCode
from _pytest.config import ExitCode
def pytest_sessionfinish(exitstatus):
assert exitstatus == ExitCode.USAGE_ERROR
@@ -1277,11 +1275,39 @@ def test_pdb_can_be_rewritten(testdir):
" def check():",
"> assert 1 == 2",
"E assert 1 == 2",
"E -1",
"E +2",
"E +1",
"E -2",
"",
"pdb.py:2: AssertionError",
"*= 1 failed in *",
]
)
assert result.ret == 1
def test_tee_stdio_captures_and_live_prints(testdir):
testpath = testdir.makepyfile(
"""
import sys
def test_simple():
print ("@this is stdout@")
print ("@this is stderr@", file=sys.stderr)
"""
)
result = testdir.runpytest_subprocess(
testpath,
"--capture=tee-sys",
"--junitxml=output.xml",
"-o",
"junit_logging=all",
)
# ensure stdout/stderr were 'live printed'
result.stdout.fnmatch_lines(["*@this is stdout@*"])
result.stderr.fnmatch_lines(["*@this is stderr@*"])
# now ensure the output is in the junitxml
with open(os.path.join(testdir.tmpdir.strpath, "output.xml"), "r") as f:
fullXml = f.read()
assert "@this is stdout@\n" in fullXml
assert "@this is stderr@\n" in fullXml

View File

@@ -2,14 +2,17 @@ import sys
from types import FrameType
from unittest import mock
import _pytest._code
import pytest
from _pytest._code import Code
from _pytest._code import ExceptionInfo
from _pytest._code import Frame
from _pytest._code.code import ReprFuncArgs
def test_ne() -> None:
code1 = _pytest._code.Code(compile('foo = "bar"', "", "exec"))
code1 = Code(compile('foo = "bar"', "", "exec"))
assert code1 == code1
code2 = _pytest._code.Code(compile('foo = "baz"', "", "exec"))
code2 = Code(compile('foo = "baz"', "", "exec"))
assert code2 != code1
@@ -17,7 +20,7 @@ def test_code_gives_back_name_for_not_existing_file() -> None:
name = "abc-123"
co_code = compile("pass\n", name, "exec")
assert co_code.co_filename == name
code = _pytest._code.Code(co_code)
code = Code(co_code)
assert str(code.path) == name
assert code.fullsource is None
@@ -26,7 +29,7 @@ def test_code_with_class() -> None:
class A:
pass
pytest.raises(TypeError, _pytest._code.Code, A)
pytest.raises(TypeError, Code, A)
def x() -> None:
@@ -34,13 +37,13 @@ def x() -> None:
def test_code_fullsource() -> None:
code = _pytest._code.Code(x)
code = Code(x)
full = code.fullsource
assert "test_code_fullsource()" in str(full)
def test_code_source() -> None:
code = _pytest._code.Code(x)
code = Code(x)
src = code.source()
expected = """def x() -> None:
raise NotImplementedError()"""
@@ -51,7 +54,7 @@ def test_frame_getsourcelineno_myself() -> None:
def func() -> FrameType:
return sys._getframe(0)
f = _pytest._code.Frame(func())
f = Frame(func())
source, lineno = f.code.fullsource, f.lineno
assert source is not None
assert source[lineno].startswith(" return sys._getframe(0)")
@@ -61,13 +64,13 @@ def test_getstatement_empty_fullsource() -> None:
def func() -> FrameType:
return sys._getframe(0)
f = _pytest._code.Frame(func())
f = Frame(func())
with mock.patch.object(f.code.__class__, "fullsource", None):
assert f.statement == ""
def test_code_from_func() -> None:
co = _pytest._code.Code(test_frame_getsourcelineno_myself)
co = Code(test_frame_getsourcelineno_myself)
assert co.firstlineno
assert co.path
@@ -86,25 +89,25 @@ def test_code_getargs() -> None:
def f1(x):
raise NotImplementedError()
c1 = _pytest._code.Code(f1)
c1 = Code(f1)
assert c1.getargs(var=True) == ("x",)
def f2(x, *y):
raise NotImplementedError()
c2 = _pytest._code.Code(f2)
c2 = Code(f2)
assert c2.getargs(var=True) == ("x", "y")
def f3(x, **z):
raise NotImplementedError()
c3 = _pytest._code.Code(f3)
c3 = Code(f3)
assert c3.getargs(var=True) == ("x", "z")
def f4(x, *y, **z):
raise NotImplementedError()
c4 = _pytest._code.Code(f4)
c4 = Code(f4)
assert c4.getargs(var=True) == ("x", "y", "z")
@@ -112,25 +115,25 @@ def test_frame_getargs() -> None:
def f1(x) -> FrameType:
return sys._getframe(0)
fr1 = _pytest._code.Frame(f1("a"))
fr1 = Frame(f1("a"))
assert fr1.getargs(var=True) == [("x", "a")]
def f2(x, *y) -> FrameType:
return sys._getframe(0)
fr2 = _pytest._code.Frame(f2("a", "b", "c"))
fr2 = Frame(f2("a", "b", "c"))
assert fr2.getargs(var=True) == [("x", "a"), ("y", ("b", "c"))]
def f3(x, **z) -> FrameType:
return sys._getframe(0)
fr3 = _pytest._code.Frame(f3("a", b="c"))
fr3 = Frame(f3("a", b="c"))
assert fr3.getargs(var=True) == [("x", "a"), ("z", {"b": "c"})]
def f4(x, *y, **z) -> FrameType:
return sys._getframe(0)
fr4 = _pytest._code.Frame(f4("a", "b", c="d"))
fr4 = Frame(f4("a", "b", c="d"))
assert fr4.getargs(var=True) == [("x", "a"), ("y", ("b",)), ("z", {"c": "d"})]
@@ -142,12 +145,12 @@ class TestExceptionInfo:
else:
assert False
except AssertionError:
exci = _pytest._code.ExceptionInfo.from_current()
exci = ExceptionInfo.from_current()
assert exci.getrepr()
def test_from_current_with_missing(self) -> None:
with pytest.raises(AssertionError, match="no current exception"):
_pytest._code.ExceptionInfo.from_current()
ExceptionInfo.from_current()
class TestTracebackEntry:
@@ -158,7 +161,7 @@ class TestTracebackEntry:
else:
assert False
except AssertionError:
exci = _pytest._code.ExceptionInfo.from_current()
exci = ExceptionInfo.from_current()
entry = exci.traceback[0]
source = entry.getsource()
assert source is not None
@@ -168,8 +171,6 @@ class TestTracebackEntry:
class TestReprFuncArgs:
def test_not_raise_exception_with_mixed_encoding(self, tw_mock) -> None:
from _pytest._code.code import ReprFuncArgs
args = [("unicode_string", "São Paulo"), ("utf8_string", b"S\xc3\xa3o Paulo")]
r = ReprFuncArgs(args)

View File

@@ -9,10 +9,11 @@ from typing import Any
from typing import Dict
from typing import Optional
import py
import py.path
import _pytest._code
import pytest
from _pytest._code import getfslineno
from _pytest._code import Source
@@ -496,10 +497,8 @@ def test_findsource() -> None:
def test_getfslineno() -> None:
from _pytest._code import getfslineno
def f(x) -> None:
pass
raise NotImplementedError()
fspath, lineno = getfslineno(f)
@@ -513,6 +512,7 @@ def test_getfslineno() -> None:
fspath, lineno = getfslineno(A)
_, A_lineno = inspect.findsource(A)
assert isinstance(fspath, py.path.local)
assert fspath.basename == "test_source.py"
assert lineno == A_lineno
@@ -524,6 +524,14 @@ def test_getfslineno() -> None:
B.__name__ = "B2"
assert getfslineno(B)[1] == -1
co = compile("...", "", "eval")
assert co.co_filename == ""
if hasattr(sys, "pypy_version_info"):
assert getfslineno(co) == ("", -1)
else:
assert getfslineno(co) == ("", 0)
def test_code_of_object_instance_with_call() -> None:
class A:

View File

@@ -0,0 +1,28 @@
import re
from io import StringIO
import pytest
from _pytest._io import TerminalWriter
@pytest.mark.parametrize(
"has_markup, expected",
[
pytest.param(
True, "{kw}assert{hl-reset} {number}0{hl-reset}\n", id="with markup"
),
pytest.param(False, "assert 0\n", id="no markup"),
],
)
def test_code_highlight(has_markup, expected, color_mapping):
f = StringIO()
tw = TerminalWriter(f)
tw.hasmarkup = has_markup
tw._write_source(["assert 0"])
assert f.getvalue().splitlines(keepends=True) == color_mapping.format([expected])
with pytest.raises(
ValueError,
match=re.escape("indents size (2) should have same size as lines (1)"),
):
tw._write_source(["assert 0"], [" ", " "])

View File

@@ -1,6 +1,10 @@
import re
import sys
from typing import List
import pytest
from _pytest.pytester import RunResult
from _pytest.pytester import Testdir
if sys.gettrace():
@@ -77,6 +81,12 @@ def tw_mock():
def write(self, msg, **kw):
self.lines.append((TWMock.WRITE, msg))
def _write_source(self, lines, indents=()):
if not indents:
indents = [""] * len(lines)
for indent, line in zip(indents, lines):
self.line(indent + line)
def line(self, line, **kw):
self.lines.append(line)
@@ -118,3 +128,70 @@ def dummy_yaml_custom_test(testdir):
"""
)
testdir.makefile(".yaml", test1="")
@pytest.fixture
def testdir(testdir: Testdir) -> Testdir:
testdir.monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1")
return testdir
@pytest.fixture(scope="session")
def color_mapping():
"""Returns a utility class which can replace keys in strings in the form "{NAME}"
by their equivalent ASCII codes in the terminal.
Used by tests which check the actual colors output by pytest.
"""
class ColorMapping:
COLORS = {
"red": "\x1b[31m",
"green": "\x1b[32m",
"yellow": "\x1b[33m",
"bold": "\x1b[1m",
"reset": "\x1b[0m",
"kw": "\x1b[94m",
"hl-reset": "\x1b[39;49;00m",
"function": "\x1b[92m",
"number": "\x1b[94m",
"str": "\x1b[33m",
"print": "\x1b[96m",
}
RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()}
@classmethod
def format(cls, lines: List[str]) -> List[str]:
"""Straightforward replacement of color names to their ASCII codes."""
return [line.format(**cls.COLORS) for line in lines]
@classmethod
def format_for_fnmatch(cls, lines: List[str]) -> List[str]:
"""Replace color names for use with LineMatcher.fnmatch_lines"""
return [line.format(**cls.COLORS).replace("[", "[[]") for line in lines]
@classmethod
def format_for_rematch(cls, lines: List[str]) -> List[str]:
"""Replace color names for use with LineMatcher.re_match_lines"""
return [line.format(**cls.RE_COLORS) for line in lines]
@classmethod
def requires_ordered_markup(cls, result: RunResult):
"""Should be called if a test expects markup to appear in the output
in the order they were passed, for example:
tw.write(line, bold=True, red=True)
In Python 3.5 there's no guarantee that the generated markup will appear
in the order called, so we do some limited color testing and skip the rest of
the test.
"""
if sys.version_info < (3, 6):
# terminal writer.write accepts keyword arguments, so
# py36+ is required so the markup appears in the expected order
output = result.stdout.str()
assert "test session starts" in output
assert "\x1b[1m" in output
pytest.skip("doing limited testing because lacking ordered markup")
return ColorMapping

View File

@@ -1,5 +1,8 @@
import inspect
import pytest
from _pytest import deprecated
from _pytest import nodes
@pytest.mark.filterwarnings("default")
@@ -73,3 +76,58 @@ def test_warn_about_imminent_junit_family_default_change(testdir, junit_family):
result.stdout.no_fnmatch_line(warning_msg)
else:
result.stdout.fnmatch_lines([warning_msg])
def test_node_direct_ctor_warning():
class MockConfig:
pass
ms = MockConfig()
with pytest.warns(
DeprecationWarning,
match="direct construction of .* has been deprecated, please use .*.from_parent",
) as w:
nodes.Node(name="test", config=ms, session=ms, nodeid="None")
assert w[0].lineno == inspect.currentframe().f_lineno - 1
assert w[0].filename == __file__
def assert_no_print_logs(testdir, args):
result = testdir.runpytest(*args)
result.stdout.fnmatch_lines(
[
"*--no-print-logs is deprecated and scheduled for removal in pytest 6.0*",
"*Please use --show-capture instead.*",
]
)
@pytest.mark.filterwarnings("default")
def test_noprintlogs_is_deprecated_cmdline(testdir):
testdir.makepyfile(
"""
def test_foo():
pass
"""
)
assert_no_print_logs(testdir, ("--no-print-logs",))
@pytest.mark.filterwarnings("default")
def test_noprintlogs_is_deprecated_ini(testdir):
testdir.makeini(
"""
[pytest]
log_print=False
"""
)
testdir.makepyfile(
"""
def test_foo():
pass
"""
)
assert_no_print_logs(testdir, ())

View File

@@ -1,4 +1,5 @@
import pytest
from _pytest._io.saferepr import _pformat_dispatch
from _pytest._io.saferepr import saferepr
@@ -147,3 +148,9 @@ def test_unicode():
val = "£€"
reprval = "'£€'"
assert saferepr(val) == reprval
def test_pformat_dispatch():
assert _pformat_dispatch("a") == "'a'"
assert _pformat_dispatch("a" * 10, width=5) == "'aaaaaaaaaa'"
assert _pformat_dispatch("foo bar", width=5) == "('foo '\n 'bar')"

View File

@@ -946,7 +946,7 @@ def test_collection_collect_only_live_logging(testdir, verbose):
expected_lines.extend(
[
"*test_collection_collect_only_live_logging.py::test_simple*",
"no tests ran in 0.[0-9][0-9]s",
"no tests ran in [0-1].[0-9][0-9]s",
]
)
elif verbose == "-qq":

View File

@@ -452,7 +452,7 @@ class TestApprox:
expected = "4.0e-06"
result = testdir.runpytest()
result.stdout.fnmatch_lines(
["*At index 0 diff: 3 != 4 * {}".format(expected), "=* 1 failed in *="]
["*At index 0 diff: 3 != 4 ± {}".format(expected), "=* 1 failed in *="]
)
@pytest.mark.parametrize(

View File

@@ -4,7 +4,7 @@ import textwrap
import _pytest._code
import pytest
from _pytest.main import ExitCode
from _pytest.config import ExitCode
from _pytest.nodes import Collector
@@ -281,10 +281,10 @@ class TestFunction:
from _pytest.fixtures import FixtureManager
config = testdir.parseconfigure()
session = testdir.Session(config)
session = testdir.Session.from_config(config)
session._fixturemanager = FixtureManager(session)
return pytest.Function(config=config, parent=session, **kwargs)
return pytest.Function.from_parent(parent=session, **kwargs)
def test_function_equality(self, testdir):
def func1():
@@ -463,7 +463,7 @@ class TestFunction:
return '3'
@pytest.mark.parametrize('fix2', ['2'])
def test_it(fix1):
def test_it(fix1, fix2):
assert fix1 == '21'
assert not fix3_instantiated
"""
@@ -492,6 +492,19 @@ class TestFunction:
)
assert "foo" in keywords[1] and "bar" in keywords[1] and "baz" in keywords[1]
def test_parametrize_with_empty_string_arguments(self, testdir):
items = testdir.getitems(
"""\
import pytest
@pytest.mark.parametrize('v', ('', ' '))
@pytest.mark.parametrize('w', ('', ' '))
def test(v, w): ...
"""
)
names = {item.name for item in items}
assert names == {"test[-]", "test[ -]", "test[- ]", "test[ - ]"}
def test_function_equality_with_callspec(self, testdir):
items = testdir.getitems(
"""
@@ -1024,7 +1037,7 @@ class TestReportInfo:
return "ABCDE", 42, "custom"
def pytest_pycollect_makeitem(collector, name, obj):
if name == "test_func":
return MyFunction(name, parent=collector)
return MyFunction.from_parent(name=name, parent=collector)
"""
)
item = testdir.getitem("def test_func(): pass")
@@ -1210,6 +1223,28 @@ def test_syntax_error_with_non_ascii_chars(testdir):
result.stdout.fnmatch_lines(["*ERROR collecting*", "*SyntaxError*", "*1 error in*"])
def test_collecterror_with_fulltrace(testdir):
testdir.makepyfile("assert 0")
result = testdir.runpytest("--fulltrace")
result.stdout.fnmatch_lines(
[
"collected 0 items / 1 error",
"",
"*= ERRORS =*",
"*_ ERROR collecting test_collecterror_with_fulltrace.py _*",
"",
"*/_pytest/python.py:*: ",
"_ _ _ _ _ _ _ _ *",
"",
"> assert 0",
"E assert 0",
"",
"test_collecterror_with_fulltrace.py:1: AssertionError",
"*! Interrupted: 1 error during collection !*",
]
)
def test_skip_duplicates_by_default(testdir):
"""Test for issue https://github.com/pytest-dev/pytest/issues/1609 (#1609)

View File

@@ -1102,6 +1102,38 @@ class TestFixtureUsages:
"*Fixture 'badscope' from test_invalid_scope.py got an unexpected scope value 'functions'"
)
@pytest.mark.parametrize("scope", ["function", "session"])
def test_parameters_without_eq_semantics(self, scope, testdir):
testdir.makepyfile(
"""
class NoEq1: # fails on `a == b` statement
def __eq__(self, _):
raise RuntimeError
class NoEq2: # fails on `if a == b:` statement
def __eq__(self, _):
class NoBool:
def __bool__(self):
raise RuntimeError
return NoBool()
import pytest
@pytest.fixture(params=[NoEq1(), NoEq2()], scope={scope!r})
def no_eq(request):
return request.param
def test1(no_eq):
pass
def test2(no_eq):
pass
""".format(
scope=scope
)
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*4 passed*"])
def test_funcarg_parametrized_and_used_twice(self, testdir):
testdir.makepyfile(
"""
@@ -3662,13 +3694,30 @@ class TestParameterizedSubRequest:
" test_foos.py::test_foo",
"",
"Requested fixture 'fix_with_param' defined in:",
"*fix.py:4",
"{}:4".format(fixfile),
"Requested here:",
"test_foos.py:4",
"*1 failed*",
]
)
# With non-overlapping rootdir, passing tests_dir.
rootdir = testdir.mkdir("rootdir")
rootdir.chdir()
result = testdir.runpytest("--rootdir", rootdir, tests_dir)
result.stdout.fnmatch_lines(
[
"The requested fixture has no parameter defined for test:",
" test_foos.py::test_foo",
"",
"Requested fixture 'fix_with_param' defined in:",
"{}:4".format(fixfile),
"Requested here:",
"{}:4".format(testfile),
"*1 failed*",
]
)
def test_pytest_fixture_setup_and_post_finalizer_hook(testdir):
testdir.makeconftest(

View File

@@ -10,7 +10,7 @@ class TestOEJSKITSpecials:
import pytest
def pytest_pycollect_makeitem(collector, name, obj):
if name == "MyClass":
return MyCollector(name, parent=collector)
return MyCollector.from_parent(collector, name=name)
class MyCollector(pytest.Collector):
def reportinfo(self):
return self.fspath, 3, "xyz"
@@ -40,7 +40,7 @@ class TestOEJSKITSpecials:
import pytest
def pytest_pycollect_makeitem(collector, name, obj):
if name == "MyClass":
return MyCollector(name, parent=collector)
return MyCollector.from_parent(collector, name=name)
class MyCollector(pytest.Collector):
def reportinfo(self):
return self.fspath, 3, "xyz"

View File

@@ -9,6 +9,8 @@ from hypothesis import strategies
import pytest
from _pytest import fixtures
from _pytest import python
from _pytest.outcomes import fail
from _pytest.python import _idval
class TestMetafunc:
@@ -26,9 +28,12 @@ class TestMetafunc:
class DefinitionMock(python.FunctionDefinition):
obj = attr.ib()
def listchain(self):
return []
names = fixtures.getfuncargnames(func)
fixtureinfo = FixtureInfo(names)
definition = DefinitionMock(func)
definition = DefinitionMock._create(func)
return python.Metafunc(definition, fixtureinfo, config)
def test_no_funcargs(self):
@@ -61,6 +66,39 @@ class TestMetafunc:
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5, 6]))
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5, 6]))
with pytest.raises(
TypeError, match="^ids must be a callable, sequence or generator$"
):
metafunc.parametrize("y", [5, 6], ids=42)
def test_parametrize_error_iterator(self):
def func(x):
raise NotImplementedError()
class Exc(Exception):
def __repr__(self):
return "Exc(from_gen)"
def gen():
yield 0
yield None
yield Exc()
metafunc = self.Metafunc(func)
metafunc.parametrize("x", [1, 2], ids=gen())
assert [(x.funcargs, x.id) for x in metafunc._calls] == [
({"x": 1}, "0"),
({"x": 2}, "2"),
]
with pytest.raises(
fail.Exception,
match=(
r"In func: ids must be list of string/float/int/bool, found:"
r" Exc\(from_gen\) \(type: <class .*Exc'>\) at index 2"
),
):
metafunc.parametrize("x", [1, 2, 3], ids=gen())
def test_parametrize_bad_scope(self):
def func(x):
pass
@@ -167,6 +205,26 @@ class TestMetafunc:
("x", "y"), [("abc", "def"), ("ghi", "jkl")], ids=["one"]
)
def test_parametrize_ids_iterator_without_mark(self):
import itertools
def func(x, y):
pass
it = itertools.count()
metafunc = self.Metafunc(func)
metafunc.parametrize("x", [1, 2], ids=it)
metafunc.parametrize("y", [3, 4], ids=it)
ids = [x.id for x in metafunc._calls]
assert ids == ["0-2", "0-3", "1-2", "1-3"]
metafunc = self.Metafunc(func)
metafunc.parametrize("x", [1, 2], ids=it)
metafunc.parametrize("y", [3, 4], ids=it)
ids = [x.id for x in metafunc._calls]
assert ids == ["4-6", "4-7", "5-6", "5-7"]
def test_parametrize_empty_list(self):
"""#510"""
@@ -209,8 +267,6 @@ class TestMetafunc:
deadline=400.0
) # very close to std deadline and CI boxes are not reliable in CPU power
def test_idval_hypothesis(self, value):
from _pytest.python import _idval
escaped = _idval(value, "a", 6, None, item=None, config=None)
assert isinstance(escaped, str)
escaped.encode("ascii")
@@ -221,8 +277,6 @@ class TestMetafunc:
escapes if they're not.
"""
from _pytest.python import _idval
values = [
("", ""),
("ascii", "ascii"),
@@ -242,7 +296,6 @@ class TestMetafunc:
disable_test_id_escaping_and_forfeit_all_rights_to_community_support
option. (#5294)
"""
from _pytest.python import _idval
class MockConfig:
def __init__(self, config):
@@ -274,8 +327,6 @@ class TestMetafunc:
"binary escape", where any byte < 127 is escaped into its hex form.
- python3: bytes objects are always escaped using "binary escape".
"""
from _pytest.python import _idval
values = [
(b"", ""),
(b"\xc3\xb4\xff\xe4", "\\xc3\\xb4\\xff\\xe4"),
@@ -289,7 +340,6 @@ class TestMetafunc:
"""unittest for the expected behavior to obtain ids for parametrized
values that are classes or functions: their __name__.
"""
from _pytest.python import _idval
class TestClass:
pass
@@ -534,9 +584,22 @@ class TestMetafunc:
@pytest.mark.parametrize("arg", ({1: 2}, {3, 4}), ids=ids)
def test(arg):
assert arg
@pytest.mark.parametrize("arg", (1, 2.0, True), ids=ids)
def test_int(arg):
assert arg
"""
)
assert testdir.runpytest().ret == 0
result = testdir.runpytest("-vv", "-s")
result.stdout.fnmatch_lines(
[
"test_parametrize_ids_returns_non_string.py::test[arg0] PASSED",
"test_parametrize_ids_returns_non_string.py::test[arg1] PASSED",
"test_parametrize_ids_returns_non_string.py::test_int[1] PASSED",
"test_parametrize_ids_returns_non_string.py::test_int[2.0] PASSED",
"test_parametrize_ids_returns_non_string.py::test_int[True] PASSED",
]
)
def test_idmaker_with_ids(self):
from _pytest.python import idmaker
@@ -1186,12 +1249,12 @@ class TestMetafuncFunctional:
result.stdout.fnmatch_lines(["* 1 skipped *"])
def test_parametrized_ids_invalid_type(self, testdir):
"""Tests parametrized with ids as non-strings (#1857)."""
"""Test error with non-strings/non-ints, without generator (#1857)."""
testdir.makepyfile(
"""
import pytest
@pytest.mark.parametrize("x, expected", [(10, 20), (40, 80)], ids=(None, 2))
@pytest.mark.parametrize("x, expected", [(1, 2), (3, 4), (5, 6)], ids=(None, 2, type))
def test_ids_numbers(x,expected):
assert x * 2 == expected
"""
@@ -1199,7 +1262,8 @@ class TestMetafuncFunctional:
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"*In test_ids_numbers: ids must be list of strings, found: 2 (type: *'int'>)*"
"In test_ids_numbers: ids must be list of string/float/int/bool,"
" found: <class 'type'> (type: <class 'type'>) at index 2"
]
)
@@ -1780,3 +1844,87 @@ class TestMarkersWithParametrization:
)
result = testdir.runpytest()
result.assert_outcomes(passed=1)
def test_parametrize_iterator(self, testdir):
testdir.makepyfile(
"""
import itertools
import pytest
id_parametrize = pytest.mark.parametrize(
ids=("param%d" % i for i in itertools.count())
)
@id_parametrize('y', ['a', 'b'])
def test1(y):
pass
@id_parametrize('y', ['a', 'b'])
def test2(y):
pass
@pytest.mark.parametrize("a, b", [(1, 2), (3, 4)], ids=itertools.count())
def test_converted_to_str(a, b):
pass
"""
)
result = testdir.runpytest("-vv", "-s")
result.stdout.fnmatch_lines(
[
"test_parametrize_iterator.py::test1[param0] PASSED",
"test_parametrize_iterator.py::test1[param1] PASSED",
"test_parametrize_iterator.py::test2[param0] PASSED",
"test_parametrize_iterator.py::test2[param1] PASSED",
"test_parametrize_iterator.py::test_converted_to_str[0] PASSED",
"test_parametrize_iterator.py::test_converted_to_str[1] PASSED",
"*= 6 passed in *",
]
)
def test_parametrize_explicit_parameters_func(self, testdir):
testdir.makepyfile(
"""
import pytest
@pytest.fixture
def fixture(arg):
return arg
@pytest.mark.parametrize("arg", ["baz"])
def test_without_arg(fixture):
assert "baz" == fixture
"""
)
result = testdir.runpytest()
result.assert_outcomes(error=1)
result.stdout.fnmatch_lines(
[
'*In function "test_without_arg"*',
'*Parameter "arg" should be declared explicitly via indirect or in function itself*',
]
)
def test_parametrize_explicit_parameters_method(self, testdir):
testdir.makepyfile(
"""
import pytest
class Test:
@pytest.fixture
def test_fixture(self, argument):
return argument
@pytest.mark.parametrize("argument", ["foobar"])
def test_without_argument(self, test_fixture):
assert "foobar" == test_fixture
"""
)
result = testdir.runpytest()
result.assert_outcomes(error=1)
result.stdout.fnmatch_lines(
[
'*In function "test_without_argument"*',
'*Parameter "argument" should be declared explicitly via indirect or in function itself*',
]
)

View File

@@ -72,10 +72,19 @@ class TestImportHookInstallation:
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines(
[
"E * AssertionError: ([[][]], [[][]], [[]<TestReport *>[]])*",
"E * assert"
" {'failed': 1, 'passed': 0, 'skipped': 0} =="
" {'failed': 0, 'passed': 1, 'skipped': 0}",
"> r.assertoutcome(passed=1)",
"E AssertionError: ([[][]], [[][]], [[]<TestReport *>[]])*",
"E assert {'failed': 1,... 'skipped': 0} == {'failed': 0,... 'skipped': 0}",
"E Omitting 1 identical items, use -vv to show",
"E Differing items:",
"E Use -v to get the full diff",
]
)
# XXX: unstable output.
result.stdout.fnmatch_lines_random(
[
"E {'failed': 1} != {'failed': 0}",
"E {'passed': 0} != {'passed': 1}",
]
)
@@ -316,8 +325,8 @@ class TestAssert_reprcompare:
def test_text_diff(self):
diff = callequal("spam", "eggs")[1:]
assert "- spam" in diff
assert "+ eggs" in diff
assert "- eggs" in diff
assert "+ spam" in diff
def test_text_skipping(self):
lines = callequal("a" * 50 + "spam", "a" * 50 + "eggs")
@@ -327,15 +336,15 @@ class TestAssert_reprcompare:
def test_text_skipping_verbose(self):
lines = callequal("a" * 50 + "spam", "a" * 50 + "eggs", verbose=1)
assert "- " + "a" * 50 + "spam" in lines
assert "+ " + "a" * 50 + "eggs" in lines
assert "- " + "a" * 50 + "eggs" in lines
assert "+ " + "a" * 50 + "spam" in lines
def test_multiline_text_diff(self):
left = "foo\nspam\nbar"
right = "foo\neggs\nbar"
diff = callequal(left, right)
assert "- spam" in diff
assert "+ eggs" in diff
assert "- eggs" in diff
assert "+ spam" in diff
def test_bytes_diff_normal(self):
"""Check special handling for bytes diff (#5260)"""
@@ -354,8 +363,8 @@ class TestAssert_reprcompare:
"b'spam' == b'eggs'",
"At index 0 diff: b's' != b'e'",
"Full diff:",
"- b'spam'",
"+ b'eggs'",
"- b'eggs'",
"+ b'spam'",
]
def test_list(self):
@@ -370,9 +379,9 @@ class TestAssert_reprcompare:
[0, 2],
"""
Full diff:
- [0, 1]
- [0, 2]
? ^
+ [0, 2]
+ [0, 1]
? ^
""",
id="lists",
@@ -382,9 +391,9 @@ class TestAssert_reprcompare:
{0: 2},
"""
Full diff:
- {0: 1}
- {0: 2}
? ^
+ {0: 2}
+ {0: 1}
? ^
""",
id="dicts",
@@ -394,9 +403,9 @@ class TestAssert_reprcompare:
{0, 2},
"""
Full diff:
- {0, 1}
- {0, 2}
? ^
+ {0, 2}
+ {0, 1}
? ^
""",
id="sets",
@@ -433,7 +442,7 @@ class TestAssert_reprcompare:
" 'a',",
" 'b',",
" 'c',",
"+ '" + long_d + "',",
"- '" + long_d + "',",
" ]",
]
@@ -446,7 +455,7 @@ class TestAssert_reprcompare:
" 'a',",
" 'b',",
" 'c',",
"- '" + long_d + "',",
"+ '" + long_d + "',",
" ]",
]
@@ -462,10 +471,10 @@ class TestAssert_reprcompare:
"At index 0 diff: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' != 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'",
"Full diff:",
" [",
"- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',",
"+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',",
" 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',",
" 'cccccccccccccccccccccccccccccc',",
"+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',",
"- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',",
" ]",
]
@@ -480,28 +489,28 @@ class TestAssert_reprcompare:
"Left contains 7 more items, first extra item: 'aaaaaaaaaa'",
"Full diff:",
" [",
"+ 'should not get wrapped',",
"- 'a',",
"- 'aaaaaaaaaa',",
"- 'aaaaaaaaaa',",
"- 'aaaaaaaaaa',",
"- 'aaaaaaaaaa',",
"- 'aaaaaaaaaa',",
"- 'aaaaaaaaaa',",
"- 'aaaaaaaaaa',",
"- 'should not get wrapped',",
"+ 'a',",
"+ 'aaaaaaaaaa',",
"+ 'aaaaaaaaaa',",
"+ 'aaaaaaaaaa',",
"+ 'aaaaaaaaaa',",
"+ 'aaaaaaaaaa',",
"+ 'aaaaaaaaaa',",
"+ 'aaaaaaaaaa',",
" ]",
]
def test_dict_wrap(self):
d1 = {"common": 1, "env": {"env1": 1}}
d2 = {"common": 1, "env": {"env1": 1, "env2": 2}}
d1 = {"common": 1, "env": {"env1": 1, "env2": 2}}
d2 = {"common": 1, "env": {"env1": 1}}
diff = callequal(d1, d2, verbose=True)
assert diff == [
"{'common': 1,...: {'env1': 1}} == {'common': 1,...1, 'env2': 2}}",
"{'common': 1,...1, 'env2': 2}} == {'common': 1,...: {'env1': 1}}",
"Omitting 1 identical items, use -vv to show",
"Differing items:",
"{'env': {'env1': 1}} != {'env': {'env1': 1, 'env2': 2}}",
"{'env': {'env1': 1, 'env2': 2}} != {'env': {'env1': 1}}",
"Full diff:",
"- {'common': 1, 'env': {'env1': 1}}",
"+ {'common': 1, 'env': {'env1': 1, 'env2': 2}}",
@@ -523,7 +532,7 @@ class TestAssert_reprcompare:
" 'env': {'sub': {'long_a': '" + long_a + "',",
" 'sub1': {'long_a': 'substring that gets wrapped substring '",
" 'that gets wrapped '}}},",
"+ 'new': 1,",
"- 'new': 1,",
" }",
]
@@ -561,8 +570,8 @@ class TestAssert_reprcompare:
"Right contains 2 more items:",
"{'b': 1, 'c': 2}",
"Full diff:",
"- {'a': 0}",
"+ {'b': 1, 'c': 2}",
"- {'b': 1, 'c': 2}",
"+ {'a': 0}",
]
lines = callequal({"b": 1, "c": 2}, {"a": 0}, verbose=2)
assert lines == [
@@ -572,8 +581,8 @@ class TestAssert_reprcompare:
"Right contains 1 more item:",
"{'a': 0}",
"Full diff:",
"- {'b': 1, 'c': 2}",
"+ {'a': 0}",
"- {'a': 0}",
"+ {'b': 1, 'c': 2}",
]
def test_sequence_different_items(self):
@@ -583,8 +592,8 @@ class TestAssert_reprcompare:
"At index 0 diff: 1 != 3",
"Right contains one more item: 5",
"Full diff:",
"- (1, 2)",
"+ (3, 4, 5)",
"- (3, 4, 5)",
"+ (1, 2)",
]
lines = callequal((1, 2, 3), (4,), verbose=2)
assert lines == [
@@ -592,8 +601,8 @@ class TestAssert_reprcompare:
"At index 0 diff: 1 != 4",
"Left contains 2 more items, first extra item: 2",
"Full diff:",
"- (1, 2, 3)",
"+ (4,)",
"- (4,)",
"+ (1, 2, 3)",
]
def test_set(self):
@@ -654,12 +663,12 @@ class TestAssert_reprcompare:
assert callequal(nums_x, nums_y) is None
expl = callequal(nums_x, nums_y, verbose=1)
assert "-" + repr(nums_x) in expl
assert "+" + repr(nums_y) in expl
assert "+" + repr(nums_x) in expl
assert "-" + repr(nums_y) in expl
expl = callequal(nums_x, nums_y, verbose=2)
assert "-" + repr(nums_x) in expl
assert "+" + repr(nums_y) in expl
assert "+" + repr(nums_x) in expl
assert "-" + repr(nums_y) in expl
def test_list_bad_repr(self):
class A:
@@ -668,8 +677,16 @@ class TestAssert_reprcompare:
expl = callequal([], [A()])
assert "ValueError" in "".join(expl)
expl = callequal({}, {"1": A()})
assert "faulty" in "".join(expl)
expl = callequal({}, {"1": A()}, verbose=2)
assert expl[0].startswith("{} == <[ValueError")
assert "raised in repr" in expl[0]
assert expl[1:] == [
"(pytest_assertion plugin: representation of details failed:"
" {}:{}: ValueError: 42.".format(
__file__, A.__repr__.__code__.co_firstlineno + 1
),
" Probably an object has a faulty __repr__.)",
]
def test_one_repr_empty(self):
"""
@@ -693,8 +710,8 @@ class TestAssert_reprcompare:
right = "£"
expl = callequal(left, right)
assert expl[0] == "'£€' == '£'"
assert expl[1] == "- £"
assert expl[2] == "+ £"
assert expl[1] == "- £"
assert expl[2] == "+ £"
def test_nonascii_text(self):
"""
@@ -707,7 +724,7 @@ class TestAssert_reprcompare:
return "\xff"
expl = callequal(A(), "1")
assert expl == ["ÿ == '1'", "+ 1"]
assert expl == ["ÿ == '1'", "- 1"]
def test_format_nonascii_explanation(self):
assert util.format_explanation("λ")
@@ -1007,9 +1024,9 @@ class TestTruncateExplanation:
# without -vv, truncate the message showing a few diff lines only
result.stdout.fnmatch_lines(
[
"*- 1*",
"*- 3*",
"*- 5*",
"*+ 1*",
"*+ 3*",
"*+ 5*",
"*truncated (%d lines hidden)*use*-vv*" % expected_truncated_lines,
]
)
@@ -1062,9 +1079,9 @@ def test_reprcompare_whitespaces():
assert detail == [
r"'\r\n' == '\n'",
r"Strings contain only whitespace, escaping them using repr()",
r"- '\r\n'",
r"? --",
r"+ '\n'",
r"- '\n'",
r"+ '\r\n'",
r"? ++",
]
@@ -1312,8 +1329,8 @@ def test_diff_newline_at_end(testdir):
r"""
*assert 'asdf' == 'asdf\n'
* - asdf
* ? -
* + asdf
* ? +
"""
)

View File

@@ -21,7 +21,7 @@ from _pytest.assertion.rewrite import get_cache_dir
from _pytest.assertion.rewrite import PYC_TAIL
from _pytest.assertion.rewrite import PYTEST_TAG
from _pytest.assertion.rewrite import rewrite_asserts
from _pytest.main import ExitCode
from _pytest.config import ExitCode
from _pytest.pathlib import Path
@@ -180,8 +180,8 @@ class TestAssertionRewrite:
if verbose > 0:
assert msg == (
"assert <module 'sys' (built-in)> == 42\n"
" -<module 'sys' (built-in)>\n"
" +42"
" +<module 'sys' (built-in)>\n"
" -42"
)
else:
assert msg == "assert sys == 42"
@@ -194,12 +194,12 @@ class TestAssertionRewrite:
msg = getmsg(f, {"cls": X}).splitlines()
if verbose > 1:
assert msg == ["assert {!r} == 42".format(X), " -{!r}".format(X), " +42"]
assert msg == ["assert {!r} == 42".format(X), " +{!r}".format(X), " -42"]
elif verbose > 0:
assert msg == [
"assert <class 'test_...e.<locals>.X'> == 42",
" -{!r}".format(X),
" +42",
" +{!r}".format(X),
" -42",
]
else:
assert msg == ["assert cls == 42"]
@@ -241,7 +241,7 @@ class TestAssertionRewrite:
# XXX: looks like the "where" should also be there in verbose mode?!
message = getmsg(f, {"cls": Y}).splitlines()
if request.config.getoption("verbose") > 0:
assert message == ["assert 3 == 2", " -3", " +2"]
assert message == ["assert 3 == 2", " +3", " -2"]
else:
assert message == [
"assert 3 == 2",
@@ -625,7 +625,7 @@ class TestAssertionRewrite:
msg = getmsg(f)
if request.config.getoption("verbose") > 0:
assert msg == "assert 10 == 11\n -10\n +11"
assert msg == "assert 10 == 11\n +10\n -11"
else:
assert msg == "assert 10 == 11\n + where 10 = len([0, 1, 2, 3, 4, 5, ...])"
@@ -688,7 +688,7 @@ class TestAssertionRewrite:
lines = util._format_lines([getmsg(f)])
if request.config.getoption("verbose") > 0:
assert lines == ["assert 0 == 1\n -0\n +1"]
assert lines == ["assert 0 == 1\n +0\n -1"]
else:
assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"]

View File

@@ -6,7 +6,7 @@ import sys
import py
import pytest
from _pytest.main import ExitCode
from _pytest.config import ExitCode
pytest_plugins = ("pytester",)
@@ -56,9 +56,7 @@ class TestNewAPI:
testdir.tmpdir.ensure_dir(".pytest_cache").chmod(mode)
@pytest.mark.skipif(sys.platform.startswith("win"), reason="no chmod on windows")
@pytest.mark.filterwarnings(
"ignore:could not create cache path:pytest.PytestWarning"
)
@pytest.mark.filterwarnings("default")
def test_cache_failure_warns(self, testdir, monkeypatch):
monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1")
cache_dir = str(testdir.tmpdir.ensure_dir(".pytest_cache"))
@@ -70,7 +68,15 @@ class TestNewAPI:
assert result.ret == 1
# warnings from nodeids, lastfailed, and stepwise
result.stdout.fnmatch_lines(
["*could not create cache path*", "*3 warnings*"]
[
# Validate location/stacklevel of warning from cacheprovider.
"*= warnings summary =*",
"*/cacheprovider.py:314",
" */cacheprovider.py:314: PytestCacheWarning: could not create cache path "
"{}/v/cache/nodeids".format(cache_dir),
' config.cache.set("cache/nodeids", self.cached_nodeids)',
"*1 failed, 3 warnings in*",
]
)
finally:
testdir.tmpdir.ensure_dir(".pytest_cache").chmod(mode)
@@ -683,11 +689,28 @@ class TestLastFailed:
result.stdout.fnmatch_lines(["*2 passed*"])
result = testdir.runpytest("--lf", "--lfnf", "all")
result.stdout.fnmatch_lines(["*2 passed*"])
# Ensure the list passed to pytest_deselected is a copy,
# and not a reference which is cleared right after.
testdir.makeconftest(
"""
deselected = []
def pytest_deselected(items):
global deselected
deselected = items
def pytest_sessionfinish():
print("\\ndeselected={}".format(len(deselected)))
"""
)
result = testdir.runpytest("--lf", "--lfnf", "none")
result.stdout.fnmatch_lines(
[
"collected 2 items / 2 deselected",
"run-last-failure: no previously failed tests, deselecting all items.",
"deselected=2",
"* 2 deselected in *",
]
)

View File

@@ -7,13 +7,15 @@ import sys
import textwrap
from io import StringIO
from io import UnsupportedOperation
from typing import BinaryIO
from typing import Generator
from typing import List
from typing import TextIO
import pytest
from _pytest import capture
from _pytest.capture import CaptureManager
from _pytest.main import ExitCode
from _pytest.config import ExitCode
# note: py.io capture tests where copied from
# pylib 1.4.20.dev2 (rev 13d9af95547e)
@@ -32,6 +34,10 @@ def StdCapture(out=True, err=True, in_=True):
return capture.MultiCapture(out, err, in_, Capture=capture.SysCapture)
def TeeStdCapture(out=True, err=True, in_=True):
return capture.MultiCapture(out, err, in_, Capture=capture.TeeSysCapture)
class TestCaptureManager:
def test_getmethod_default_no_fd(self, monkeypatch):
from _pytest.capture import pytest_addoption
@@ -471,9 +477,9 @@ class TestCaptureFixture:
result.stdout.fnmatch_lines(
[
"*test_one*",
"*capsys*capfd*same*time*",
"E * cannot use capfd and capsys at the same time",
"*test_two*",
"*capfd*capsys*same*time*",
"E * cannot use capsys and capfd at the same time",
"*2 failed in*",
]
)
@@ -816,6 +822,25 @@ class TestCaptureIO:
assert f.getvalue() == "foo\r\n"
class TestCaptureAndPassthroughIO(TestCaptureIO):
def test_text(self):
sio = io.StringIO()
f = capture.CaptureAndPassthroughIO(sio)
f.write("hello")
s1 = f.getvalue()
assert s1 == "hello"
s2 = sio.getvalue()
assert s2 == s1
f.close()
sio.close()
def test_unicode_and_str_mixture(self):
sio = io.StringIO()
f = capture.CaptureAndPassthroughIO(sio)
f.write("\u00f6")
pytest.raises(TypeError, f.write, b"hello")
def test_dontreadfrominput():
from _pytest.capture import DontReadFromInput
@@ -831,7 +856,7 @@ def test_dontreadfrominput():
@pytest.fixture
def tmpfile(testdir):
def tmpfile(testdir) -> Generator[BinaryIO, None, None]:
f = testdir.makepyfile("").open("wb+")
yield f
if not f.closed:
@@ -1112,6 +1137,23 @@ class TestStdCapture:
pytest.raises(IOError, sys.stdin.read)
class TestTeeStdCapture(TestStdCapture):
captureclass = staticmethod(TeeStdCapture)
def test_capturing_error_recursive(self):
""" for TeeStdCapture since we passthrough stderr/stdout, cap1
should get all output, while cap2 should only get "cap2\n" """
with self.getcapture() as cap1:
print("cap1")
with self.getcapture() as cap2:
print("cap2")
out2, err2 = cap2.readouterr()
out1, err1 = cap1.readouterr()
assert out1 == "cap1\ncap2\n"
assert out2 == "cap2\n"
class TestStdCaptureFD(TestStdCapture):
pytestmark = needsosdup
captureclass = staticmethod(StdCaptureFD)
@@ -1252,7 +1294,7 @@ def test_close_and_capture_again(testdir):
)
@pytest.mark.parametrize("method", ["SysCapture", "FDCapture"])
@pytest.mark.parametrize("method", ["SysCapture", "FDCapture", "TeeSysCapture"])
def test_capturing_and_logging_fundamentals(testdir, method):
if method == "StdCaptureFD" and not hasattr(os, "dup"):
pytest.skip("need os.dup")
@@ -1497,3 +1539,15 @@ def test_typeerror_encodedfile_write(testdir):
def test_stderr_write_returns_len(capsys):
"""Write on Encoded files, namely captured stderr, should return number of characters written."""
assert sys.stderr.write("Foo") == 3
def test_encodedfile_writelines(tmpfile: BinaryIO) -> None:
ef = capture.EncodedFile(tmpfile, "utf-8")
with pytest.raises(AttributeError):
ef.writelines([b"line1", b"line2"]) # type: ignore[list-item] # noqa: F821
assert ef.writelines(["line1", "line2"]) is None # type: ignore[func-returns-value] # noqa: F821
tmpfile.seek(0)
assert tmpfile.read() == b"line1line2"
tmpfile.close()
with pytest.raises(ValueError):
ef.read()

View File

@@ -6,9 +6,10 @@ import textwrap
import py
import pytest
from _pytest.config import ExitCode
from _pytest.main import _in_venv
from _pytest.main import ExitCode
from _pytest.main import Session
from _pytest.pytester import Testdir
class TestCollector:
@@ -18,7 +19,7 @@ class TestCollector:
assert not issubclass(Collector, Item)
assert not issubclass(Item, Collector)
def test_check_equality(self, testdir):
def test_check_equality(self, testdir: Testdir) -> None:
modcol = testdir.getmodulecol(
"""
def test_pass(): pass
@@ -40,12 +41,15 @@ class TestCollector:
assert fn1 != fn3
for fn in fn1, fn2, fn3:
assert fn != 3
assert isinstance(fn, pytest.Function)
assert fn != 3 # type: ignore[comparison-overlap] # noqa: F821
assert fn != modcol
assert fn != [1, 2, 3]
assert [1, 2, 3] != fn
assert fn != [1, 2, 3] # type: ignore[comparison-overlap] # noqa: F821
assert [1, 2, 3] != fn # type: ignore[comparison-overlap] # noqa: F821
assert modcol != fn
assert testdir.collect_by_name(modcol, "doesnotexist") is None
def test_getparent(self, testdir):
modcol = testdir.getmodulecol(
"""
@@ -75,7 +79,7 @@ class TestCollector:
pass
def pytest_collect_file(path, parent):
if path.ext == ".xxx":
return CustomFile(path, parent=parent)
return CustomFile.from_parent(fspath=path, parent=parent)
"""
)
node = testdir.getpathnode(hello)
@@ -438,7 +442,7 @@ class TestCustomConftests:
class TestSession:
def test_parsearg(self, testdir):
def test_parsearg(self, testdir) -> None:
p = testdir.makepyfile("def test_func(): pass")
subdir = testdir.mkdir("sub")
subdir.ensure("__init__.py")
@@ -446,16 +450,16 @@ class TestSession:
p.move(target)
subdir.chdir()
config = testdir.parseconfig(p.basename)
rcol = Session(config=config)
rcol = Session.from_config(config)
assert rcol.fspath == subdir
parts = rcol._parsearg(p.basename)
fspath, parts = rcol._parsearg(p.basename)
assert parts[0] == target
assert fspath == target
assert len(parts) == 0
fspath, parts = rcol._parsearg(p.basename + "::test_func")
assert fspath == target
assert parts[0] == "test_func"
assert len(parts) == 1
parts = rcol._parsearg(p.basename + "::test_func")
assert parts[0] == target
assert parts[1] == "test_func"
assert len(parts) == 2
def test_collect_topdir(self, testdir):
p = testdir.makepyfile("def test_func(): pass")
@@ -463,7 +467,7 @@ class TestSession:
# XXX migrate to collectonly? (see below)
config = testdir.parseconfig(id)
topdir = testdir.tmpdir
rcol = Session(config)
rcol = Session.from_config(config)
assert topdir == rcol.fspath
# rootid = rcol.nodeid
# root2 = rcol.perform_collect([rcol.nodeid], genitems=False)[0]
@@ -809,6 +813,43 @@ class TestNodekeywords:
reprec = testdir.inline_run("-k repr")
reprec.assertoutcome(passed=1, failed=0)
def test_keyword_matching_is_case_insensitive_by_default(self, testdir):
"""Check that selection via -k EXPRESSION is case-insensitive.
Since markers are also added to the node keywords, they too can
be matched without having to think about case sensitivity.
"""
testdir.makepyfile(
"""
import pytest
def test_sPeCiFiCToPiC_1():
assert True
class TestSpecificTopic_2:
def test(self):
assert True
@pytest.mark.sPeCiFiCToPic_3
def test():
assert True
@pytest.mark.sPeCiFiCToPic_4
class Test:
def test(self):
assert True
def test_failing_5():
assert False, "This should not match"
"""
)
num_matching_tests = 4
for expression in ("specifictopic", "SPECIFICTOPIC", "SpecificTopic"):
reprec = testdir.inline_run("-k " + expression)
reprec.assertoutcome(passed=num_matching_tests, failed=0)
COLLECTION_ERROR_PY_FILES = dict(
test_01_failure="""

View File

@@ -7,11 +7,11 @@ import pytest
from _pytest.compat import importlib_metadata
from _pytest.config import _iter_rewritable_modules
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config.exceptions import UsageError
from _pytest.config.findpaths import determine_setup
from _pytest.config.findpaths import get_common_ancestor
from _pytest.config.findpaths import getcfg
from _pytest.main import ExitCode
from _pytest.pathlib import Path
@@ -659,6 +659,13 @@ def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load):
class PseudoPlugin:
x = 42
attrs_used = []
def __getattr__(self, name):
assert name == "__loader__"
self.attrs_used.append(name)
return object()
def distributions():
return (Distribution(),)
@@ -668,6 +675,36 @@ def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load):
config = testdir.parseconfig(*parse_args)
has_loaded = config.pluginmanager.get_plugin("mytestplugin") is not None
assert has_loaded == should_load
if should_load:
assert PseudoPlugin.attrs_used == ["__loader__"]
else:
assert PseudoPlugin.attrs_used == []
def test_plugin_loading_order(testdir):
"""Test order of plugin loading with `-p`."""
p1 = testdir.makepyfile(
"""
def test_terminal_plugin(request):
import myplugin
assert myplugin.terminal_plugin == [False, True]
""",
**{
"myplugin": """
terminal_plugin = []
def pytest_configure(config):
terminal_plugin.append(bool(config.pluginmanager.get_plugin("terminalreporter")))
def pytest_sessionstart(session):
config = session.config
terminal_plugin.append(bool(config.pluginmanager.get_plugin("terminalreporter")))
"""
}
)
testdir.syspathinsert()
result = testdir.runpytest("-p", "myplugin", str(p1))
assert result.ret == 0
def test_cmdline_processargs_simple(testdir):
@@ -802,7 +839,7 @@ def test_load_initial_conftest_last_ordering(_config_for_test):
pm.register(m)
hc = pm.hook.pytest_load_initial_conftests
values = hc._nonwrappers + hc._wrappers
expected = ["_pytest.config", "test_config", "_pytest.capture"]
expected = ["_pytest.config", m.__module__, "_pytest.capture"]
assert [x.function.__module__ for x in values] == expected
@@ -1097,7 +1134,7 @@ class TestOverrideIniArgs:
% (testdir.request.config._parser.optparser.prog,)
]
)
assert result.ret == _pytest.main.ExitCode.USAGE_ERROR
assert result.ret == _pytest.config.ExitCode.USAGE_ERROR
def test_override_ini_does_not_contain_paths(self, _config_for_test, _sys_snapshot):
"""Check that -o no longer swallows all options after it (#3103)"""

View File

@@ -4,8 +4,8 @@ import textwrap
import py
import pytest
from _pytest.config import ExitCode
from _pytest.config import PytestPluginManager
from _pytest.main import ExitCode
from _pytest.pathlib import Path

View File

@@ -22,7 +22,7 @@ def pdb_env(request):
if "testdir" in request.fixturenames:
# Disable pdb++ with inner tests.
testdir = request.getfixturevalue("testdir")
testdir._env_run_update["PDBPP_HIJACK_PDB"] = "0"
testdir.monkeypatch.setenv("PDBPP_HIJACK_PDB", "0")
def runpdb_and_get_report(testdir, source):
@@ -193,7 +193,7 @@ class TestPDB:
)
child = testdir.spawn_pytest("-rs --pdb %s" % p1)
child.expect("Skipping also with pdb active")
child.expect_exact("= \x1b[33m\x1b[1m1 skipped\x1b[0m\x1b[33m in")
child.expect_exact("= 1 skipped in")
child.sendeof()
self.flush(child)
@@ -221,7 +221,7 @@ class TestPDB:
child.sendeof()
rest = child.read().decode("utf8")
assert "Exit: Quitting debugger" in rest
assert "= \x1b[31m\x1b[1m1 failed\x1b[0m\x1b[31m in" in rest
assert "= 1 failed in" in rest
assert "def test_1" not in rest
assert "get rekt" not in rest
self.flush(child)
@@ -506,7 +506,7 @@ class TestPDB:
rest = child.read().decode("utf8")
assert "! _pytest.outcomes.Exit: Quitting debugger !" in rest
assert "= \x1b[33mno tests ran\x1b[0m\x1b[32m in" in rest
assert "= no tests ran in" in rest
assert "BdbQuit" not in rest
assert "UNEXPECTED EXCEPTION" not in rest
@@ -725,7 +725,7 @@ class TestPDB:
assert "> PDB continue (IO-capturing resumed) >" in rest
else:
assert "> PDB continue >" in rest
assert "= \x1b[32m\x1b[1m1 passed\x1b[0m\x1b[32m in" in rest
assert "= 1 passed in" in rest
def test_pdb_used_outside_test(self, testdir):
p1 = testdir.makepyfile(
@@ -1041,7 +1041,7 @@ class TestTraceOption:
child.sendline("q")
child.expect_exact("Exit: Quitting debugger")
rest = child.read().decode("utf8")
assert "= \x1b[32m\x1b[1m2 passed\x1b[0m\x1b[32m in" in rest
assert "= 2 passed in" in rest
assert "reading from stdin while output" not in rest
# Only printed once - not on stderr.
assert "Exit: Quitting debugger" not in child.before.decode("utf8")
@@ -1086,7 +1086,7 @@ class TestTraceOption:
child.sendline("c")
child.expect_exact("> PDB continue (IO-capturing resumed) >")
rest = child.read().decode("utf8")
assert "= \x1b[32m\x1b[1m6 passed\x1b[0m\x1b[32m in" in rest
assert "= 6 passed in" in rest
assert "reading from stdin while output" not in rest
# Only printed once - not on stderr.
assert "Exit: Quitting debugger" not in child.before.decode("utf8")
@@ -1197,7 +1197,7 @@ def test_pdb_suspends_fixture_capturing(testdir, fixture):
TestPDB.flush(child)
assert child.exitstatus == 0
assert "= \x1b[32m\x1b[1m1 passed\x1b[0m\x1b[32m in" in rest
assert "= 1 passed in" in rest
assert "> PDB continue (IO-capturing resumed for fixture %s) >" % (fixture) in rest

274
testing/test_error_diffs.py Normal file
View File

@@ -0,0 +1,274 @@
"""
Tests and examples for correct "+/-" usage in error diffs.
See https://github.com/pytest-dev/pytest/issues/3333 for details.
"""
import sys
import pytest
TESTCASES = [
pytest.param(
"""
def test_this():
result = [1, 4, 3]
expected = [1, 2, 3]
assert result == expected
""",
"""
> assert result == expected
E assert [1, 4, 3] == [1, 2, 3]
E At index 1 diff: 4 != 2
E Full diff:
E - [1, 2, 3]
E ? ^
E + [1, 4, 3]
E ? ^
""",
id="Compare lists, one item differs",
),
pytest.param(
"""
def test_this():
result = [1, 2, 3]
expected = [1, 2]
assert result == expected
""",
"""
> assert result == expected
E assert [1, 2, 3] == [1, 2]
E Left contains one more item: 3
E Full diff:
E - [1, 2]
E + [1, 2, 3]
E ? +++
""",
id="Compare lists, one extra item",
),
pytest.param(
"""
def test_this():
result = [1, 3]
expected = [1, 2, 3]
assert result == expected
""",
"""
> assert result == expected
E assert [1, 3] == [1, 2, 3]
E At index 1 diff: 3 != 2
E Right contains one more item: 3
E Full diff:
E - [1, 2, 3]
E ? ---
E + [1, 3]
""",
id="Compare lists, one item missing",
),
pytest.param(
"""
def test_this():
result = (1, 4, 3)
expected = (1, 2, 3)
assert result == expected
""",
"""
> assert result == expected
E assert (1, 4, 3) == (1, 2, 3)
E At index 1 diff: 4 != 2
E Full diff:
E - (1, 2, 3)
E ? ^
E + (1, 4, 3)
E ? ^
""",
id="Compare tuples",
),
pytest.param(
"""
def test_this():
result = {1, 3, 4}
expected = {1, 2, 3}
assert result == expected
""",
"""
> assert result == expected
E assert {1, 3, 4} == {1, 2, 3}
E Extra items in the left set:
E 4
E Extra items in the right set:
E 2
E Full diff:
E - {1, 2, 3}
E ? ^ ^
E + {1, 3, 4}
E ? ^ ^
""",
id="Compare sets",
),
pytest.param(
"""
def test_this():
result = {1: 'spam', 3: 'eggs'}
expected = {1: 'spam', 2: 'eggs'}
assert result == expected
""",
"""
> assert result == expected
E AssertionError: assert {1: 'spam', 3: 'eggs'} == {1: 'spam', 2: 'eggs'}
E Common items:
E {1: 'spam'}
E Left contains 1 more item:
E {3: 'eggs'}
E Right contains 1 more item:
E {2: 'eggs'}
E Full diff:
E - {1: 'spam', 2: 'eggs'}
E ? ^
E + {1: 'spam', 3: 'eggs'}
E ? ^
""",
id="Compare dicts with differing keys",
),
pytest.param(
"""
def test_this():
result = {1: 'spam', 2: 'eggs'}
expected = {1: 'spam', 2: 'bacon'}
assert result == expected
""",
"""
> assert result == expected
E AssertionError: assert {1: 'spam', 2: 'eggs'} == {1: 'spam', 2: 'bacon'}
E Common items:
E {1: 'spam'}
E Differing items:
E {2: 'eggs'} != {2: 'bacon'}
E Full diff:
E - {1: 'spam', 2: 'bacon'}
E ? ^^^^^
E + {1: 'spam', 2: 'eggs'}
E ? ^^^^
""",
id="Compare dicts with differing values",
),
pytest.param(
"""
def test_this():
result = {1: 'spam', 2: 'eggs'}
expected = {1: 'spam', 3: 'bacon'}
assert result == expected
""",
"""
> assert result == expected
E AssertionError: assert {1: 'spam', 2: 'eggs'} == {1: 'spam', 3: 'bacon'}
E Common items:
E {1: 'spam'}
E Left contains 1 more item:
E {2: 'eggs'}
E Right contains 1 more item:
E {3: 'bacon'}
E Full diff:
E - {1: 'spam', 3: 'bacon'}
E ? ^ ^^^^^
E + {1: 'spam', 2: 'eggs'}
E ? ^ ^^^^
""",
id="Compare dicts with differing items",
),
pytest.param(
"""
def test_this():
result = "spmaeggs"
expected = "spameggs"
assert result == expected
""",
"""
> assert result == expected
E AssertionError: assert 'spmaeggs' == 'spameggs'
E - spameggs
E ? -
E + spmaeggs
E ? +
""",
id="Compare strings",
),
pytest.param(
"""
def test_this():
result = "spam bacon eggs"
assert "bacon" not in result
""",
"""
> assert "bacon" not in result
E AssertionError: assert 'bacon' not in 'spam bacon eggs'
E 'bacon' is contained here:
E spam bacon eggs
E ? +++++
""",
id='Test "not in" string',
),
]
if sys.version_info[:2] >= (3, 7):
TESTCASES.extend(
[
pytest.param(
"""
from dataclasses import dataclass
@dataclass
class A:
a: int
b: str
def test_this():
result = A(1, 'spam')
expected = A(2, 'spam')
assert result == expected
""",
"""
> assert result == expected
E AssertionError: assert A(a=1, b='spam') == A(a=2, b='spam')
E Matching attributes:
E ['b']
E Differing attributes:
E a: 1 != 2
""",
id="Compare data classes",
),
pytest.param(
"""
import attr
@attr.s(auto_attribs=True)
class A:
a: int
b: str
def test_this():
result = A(1, 'spam')
expected = A(1, 'eggs')
assert result == expected
""",
"""
> assert result == expected
E AssertionError: assert A(a=1, b='spam') == A(a=1, b='eggs')
E Matching attributes:
E ['a']
E Differing attributes:
E b: 'spam' != 'eggs'
""",
id="Compare attrs classes",
),
]
)
@pytest.mark.parametrize("code, expected", TESTCASES)
def test_error_diff(code, expected, testdir):
expected = [l.lstrip() for l in expected.splitlines()]
p = testdir.makepyfile(code)
result = testdir.runpytest(p, "-vv")
result.stdout.fnmatch_lines(expected)
assert result.ret == 1

View File

@@ -88,7 +88,7 @@ def test_cancel_timeout_on_hook(monkeypatch, hook_name):
exception (pytest-dev/pytest-faulthandler#14).
"""
import faulthandler
from _pytest import faulthandler as plugin_module
from _pytest.faulthandler import FaultHandlerHooks
called = []
@@ -98,6 +98,35 @@ def test_cancel_timeout_on_hook(monkeypatch, hook_name):
# call our hook explicitly, we can trust that pytest will call the hook
# for us at the appropriate moment
hook_func = getattr(plugin_module, hook_name)
hook_func()
hook_func = getattr(FaultHandlerHooks, hook_name)
hook_func(self=None)
assert called == [1]
@pytest.mark.parametrize("faulthandler_timeout", [0, 2])
def test_already_initialized(faulthandler_timeout, testdir):
"""Test for faulthandler being initialized earlier than pytest (#6575)"""
testdir.makepyfile(
"""
def test():
import faulthandler
assert faulthandler.is_enabled()
"""
)
result = testdir.run(
sys.executable,
"-X",
"faulthandler",
"-mpytest",
testdir.tmpdir,
"-o",
"faulthandler_timeout={}".format(faulthandler_timeout),
)
# ensure warning is emitted if faulthandler_timeout is configured
warning_line = "*faulthandler.py*faulthandler module enabled before*"
if faulthandler_timeout > 0:
result.stdout.fnmatch_lines(warning_line)
else:
result.stdout.no_fnmatch_line(warning_line)
result.stdout.fnmatch_lines("*1 passed*")
assert result.ret == 0

View File

@@ -1,8 +1,9 @@
import pytest
from _pytest.main import ExitCode
from _pytest.config import ExitCode
def test_version(testdir, pytestconfig):
testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
result = testdir.runpytest("--version")
assert result.ret == 0
# p = py.path.local(py.__file__).dirpath()

View File

@@ -445,7 +445,9 @@ class TestPython:
fnode.assert_attr(message="internal error")
assert "Division" in fnode.toxml()
@pytest.mark.parametrize("junit_logging", ["no", "system-out", "system-err"])
@pytest.mark.parametrize(
"junit_logging", ["no", "log", "system-out", "system-err", "out-err", "all"]
)
@parametrize_families
def test_failure_function(
self, testdir, junit_logging, run_and_parse, xunit_family
@@ -467,35 +469,48 @@ class TestPython:
result, dom = run_and_parse(
"-o", "junit_logging=%s" % junit_logging, family=xunit_family
)
assert result.ret
assert result.ret, "Expected ret > 0"
node = dom.find_first_by_tag("testsuite")
node.assert_attr(failures=1, tests=1)
tnode = node.find_first_by_tag("testcase")
tnode.assert_attr(classname="test_failure_function", name="test_fail")
fnode = tnode.find_first_by_tag("failure")
fnode.assert_attr(message="ValueError: 42")
assert "ValueError" in fnode.toxml()
systemout = fnode.next_sibling
assert systemout.tag == "system-out"
systemout_xml = systemout.toxml()
assert "hello-stdout" in systemout_xml
assert "info msg" not in systemout_xml
systemerr = systemout.next_sibling
assert systemerr.tag == "system-err"
systemerr_xml = systemerr.toxml()
assert "hello-stderr" in systemerr_xml
assert "info msg" not in systemerr_xml
assert "ValueError" in fnode.toxml(), "ValueError not included"
if junit_logging == "system-out":
assert "warning msg" in systemout_xml
assert "warning msg" not in systemerr_xml
elif junit_logging == "system-err":
assert "warning msg" not in systemout_xml
assert "warning msg" in systemerr_xml
else:
assert junit_logging == "no"
assert "warning msg" not in systemout_xml
assert "warning msg" not in systemerr_xml
if junit_logging in ["log", "all"]:
logdata = tnode.find_first_by_tag("system-out")
log_xml = logdata.toxml()
assert logdata.tag == "system-out", "Expected tag: system-out"
assert "info msg" not in log_xml, "Unexpected INFO message"
assert "warning msg" in log_xml, "Missing WARN message"
if junit_logging in ["system-out", "out-err", "all"]:
systemout = tnode.find_first_by_tag("system-out")
systemout_xml = systemout.toxml()
assert systemout.tag == "system-out", "Expected tag: system-out"
assert "info msg" not in systemout_xml, "INFO message found in system-out"
assert (
"hello-stdout" in systemout_xml
), "Missing 'hello-stdout' in system-out"
if junit_logging in ["system-err", "out-err", "all"]:
systemerr = tnode.find_first_by_tag("system-err")
systemerr_xml = systemerr.toxml()
assert systemerr.tag == "system-err", "Expected tag: system-err"
assert "info msg" not in systemerr_xml, "INFO message found in system-err"
assert (
"hello-stderr" in systemerr_xml
), "Missing 'hello-stderr' in system-err"
assert (
"warning msg" not in systemerr_xml
), "WARN message found in system-err"
if junit_logging == "no":
assert not tnode.find_by_tag("log"), "Found unexpected content: log"
assert not tnode.find_by_tag(
"system-out"
), "Found unexpected content: system-out"
assert not tnode.find_by_tag(
"system-err"
), "Found unexpected content: system-err"
@parametrize_families
def test_failure_verbose_message(self, testdir, run_and_parse, xunit_family):
@@ -523,7 +538,9 @@ class TestPython:
assert 0
"""
)
result, dom = run_and_parse(family=xunit_family)
result, dom = run_and_parse(
"-o", "junit_logging=system-out", family=xunit_family
)
assert result.ret
node = dom.find_first_by_tag("testsuite")
node.assert_attr(failures=3, tests=3)
@@ -536,7 +553,7 @@ class TestPython:
)
sysout = tnode.find_first_by_tag("system-out")
text = sysout.text
assert text == "%s\n" % char
assert "%s\n" % char in text
@parametrize_families
def test_junit_prefixing(self, testdir, run_and_parse, xunit_family):
@@ -597,7 +614,10 @@ class TestPython:
fnode = tnode.find_first_by_tag("skipped")
fnode.assert_attr(type="pytest.xfail", message="42")
def test_xfail_captures_output_once(self, testdir, run_and_parse):
@pytest.mark.parametrize(
"junit_logging", ["no", "log", "system-out", "system-err", "out-err", "all"]
)
def test_xfail_captures_output_once(self, testdir, junit_logging, run_and_parse):
testdir.makepyfile(
"""
import sys
@@ -610,11 +630,18 @@ class TestPython:
assert 0
"""
)
result, dom = run_and_parse()
result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)
node = dom.find_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase")
assert len(tnode.find_by_tag("system-err")) == 1
assert len(tnode.find_by_tag("system-out")) == 1
if junit_logging in ["system-err", "out-err", "all"]:
assert len(tnode.find_by_tag("system-err")) == 1
else:
assert len(tnode.find_by_tag("system-err")) == 0
if junit_logging in ["log", "system-out", "out-err", "all"]:
assert len(tnode.find_by_tag("system-out")) == 1
else:
assert len(tnode.find_by_tag("system-out")) == 0
@parametrize_families
def test_xfailure_xpass(self, testdir, run_and_parse, xunit_family):
@@ -696,20 +723,29 @@ class TestPython:
result, dom = run_and_parse()
print(dom.toxml())
def test_pass_captures_stdout(self, testdir, run_and_parse):
@pytest.mark.parametrize("junit_logging", ["no", "system-out"])
def test_pass_captures_stdout(self, testdir, run_and_parse, junit_logging):
testdir.makepyfile(
"""
def test_pass():
print('hello-stdout')
"""
)
result, dom = run_and_parse()
result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)
node = dom.find_first_by_tag("testsuite")
pnode = node.find_first_by_tag("testcase")
systemout = pnode.find_first_by_tag("system-out")
assert "hello-stdout" in systemout.toxml()
if junit_logging == "no":
assert not node.find_by_tag(
"system-out"
), "system-out should not be generated"
if junit_logging == "system-out":
systemout = pnode.find_first_by_tag("system-out")
assert (
"hello-stdout" in systemout.toxml()
), "'hello-stdout' should be in system-out"
def test_pass_captures_stderr(self, testdir, run_and_parse):
@pytest.mark.parametrize("junit_logging", ["no", "system-err"])
def test_pass_captures_stderr(self, testdir, run_and_parse, junit_logging):
testdir.makepyfile(
"""
import sys
@@ -717,13 +753,21 @@ class TestPython:
sys.stderr.write('hello-stderr')
"""
)
result, dom = run_and_parse()
result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)
node = dom.find_first_by_tag("testsuite")
pnode = node.find_first_by_tag("testcase")
systemout = pnode.find_first_by_tag("system-err")
assert "hello-stderr" in systemout.toxml()
if junit_logging == "no":
assert not node.find_by_tag(
"system-err"
), "system-err should not be generated"
if junit_logging == "system-err":
systemerr = pnode.find_first_by_tag("system-err")
assert (
"hello-stderr" in systemerr.toxml()
), "'hello-stderr' should be in system-err"
def test_setup_error_captures_stdout(self, testdir, run_and_parse):
@pytest.mark.parametrize("junit_logging", ["no", "system-out"])
def test_setup_error_captures_stdout(self, testdir, run_and_parse, junit_logging):
testdir.makepyfile(
"""
import pytest
@@ -736,13 +780,21 @@ class TestPython:
pass
"""
)
result, dom = run_and_parse()
result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)
node = dom.find_first_by_tag("testsuite")
pnode = node.find_first_by_tag("testcase")
systemout = pnode.find_first_by_tag("system-out")
assert "hello-stdout" in systemout.toxml()
if junit_logging == "no":
assert not node.find_by_tag(
"system-out"
), "system-out should not be generated"
if junit_logging == "system-out":
systemout = pnode.find_first_by_tag("system-out")
assert (
"hello-stdout" in systemout.toxml()
), "'hello-stdout' should be in system-out"
def test_setup_error_captures_stderr(self, testdir, run_and_parse):
@pytest.mark.parametrize("junit_logging", ["no", "system-err"])
def test_setup_error_captures_stderr(self, testdir, run_and_parse, junit_logging):
testdir.makepyfile(
"""
import sys
@@ -756,13 +808,21 @@ class TestPython:
pass
"""
)
result, dom = run_and_parse()
result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)
node = dom.find_first_by_tag("testsuite")
pnode = node.find_first_by_tag("testcase")
systemout = pnode.find_first_by_tag("system-err")
assert "hello-stderr" in systemout.toxml()
if junit_logging == "no":
assert not node.find_by_tag(
"system-err"
), "system-err should not be generated"
if junit_logging == "system-err":
systemerr = pnode.find_first_by_tag("system-err")
assert (
"hello-stderr" in systemerr.toxml()
), "'hello-stderr' should be in system-err"
def test_avoid_double_stdout(self, testdir, run_and_parse):
@pytest.mark.parametrize("junit_logging", ["no", "system-out"])
def test_avoid_double_stdout(self, testdir, run_and_parse, junit_logging):
testdir.makepyfile(
"""
import sys
@@ -777,12 +837,17 @@ class TestPython:
sys.stdout.write('hello-stdout call')
"""
)
result, dom = run_and_parse()
result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)
node = dom.find_first_by_tag("testsuite")
pnode = node.find_first_by_tag("testcase")
systemout = pnode.find_first_by_tag("system-out")
assert "hello-stdout call" in systemout.toxml()
assert "hello-stdout teardown" in systemout.toxml()
if junit_logging == "no":
assert not node.find_by_tag(
"system-out"
), "system-out should not be generated"
if junit_logging == "system-out":
systemout = pnode.find_first_by_tag("system-out")
assert "hello-stdout call" in systemout.toxml()
assert "hello-stdout teardown" in systemout.toxml()
def test_mangle_test_address():
@@ -850,7 +915,8 @@ class TestNonPython:
assert "custom item runtest failed" in fnode.toxml()
def test_nullbyte(testdir):
@pytest.mark.parametrize("junit_logging", ["no", "system-out"])
def test_nullbyte(testdir, junit_logging):
# A null byte can not occur in XML (see section 2.2 of the spec)
testdir.makepyfile(
"""
@@ -862,13 +928,17 @@ def test_nullbyte(testdir):
"""
)
xmlf = testdir.tmpdir.join("junit.xml")
testdir.runpytest("--junitxml=%s" % xmlf)
testdir.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging)
text = xmlf.read()
assert "\x00" not in text
assert "#x00" in text
if junit_logging == "system-out":
assert "#x00" in text
if junit_logging == "no":
assert "#x00" not in text
def test_nullbyte_replace(testdir):
@pytest.mark.parametrize("junit_logging", ["no", "system-out"])
def test_nullbyte_replace(testdir, junit_logging):
# Check if the null byte gets replaced
testdir.makepyfile(
"""
@@ -880,9 +950,12 @@ def test_nullbyte_replace(testdir):
"""
)
xmlf = testdir.tmpdir.join("junit.xml")
testdir.runpytest("--junitxml=%s" % xmlf)
testdir.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging)
text = xmlf.read()
assert "#x0" in text
if junit_logging == "system-out":
assert "#x0" in text
if junit_logging == "no":
assert "#x0" not in text
def test_invalid_xml_escape():
@@ -1227,6 +1300,7 @@ def test_runs_twice(testdir, run_and_parse):
def test_runs_twice_xdist(testdir, run_and_parse):
pytest.importorskip("xdist")
testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
f = testdir.makepyfile(
"""
def test_pass():

View File

@@ -1,5 +1,8 @@
from typing import Optional
import pytest
from _pytest.main import ExitCode
from _pytest.config import ExitCode
from _pytest.pytester import Testdir
@pytest.mark.parametrize(
@@ -50,3 +53,25 @@ def test_wrap_session_notify_exception(ret_exc, testdir):
assert result.stderr.lines == ["mainloop: caught unexpected SystemExit!"]
else:
assert result.stderr.lines == ["Exit: exiting after {}...".format(exc.__name__)]
@pytest.mark.parametrize("returncode", (None, 42))
def test_wrap_session_exit_sessionfinish(
returncode: Optional[int], testdir: Testdir
) -> None:
testdir.makeconftest(
"""
import pytest
def pytest_sessionfinish():
pytest.exit(msg="exit_pytest_sessionfinish", returncode={returncode})
""".format(
returncode=returncode
)
)
result = testdir.runpytest()
if returncode:
assert result.ret == returncode
else:
assert result.ret == ExitCode.NO_TESTS_COLLECTED
assert result.stdout.lines[-1] == "collected 0 items"
assert result.stderr.lines == ["Exit: exit_pytest_sessionfinish"]

View File

@@ -3,7 +3,7 @@ import sys
from unittest import mock
import pytest
from _pytest.main import ExitCode
from _pytest.config import ExitCode
from _pytest.mark import EMPTY_PARAMETERSET_OPTION
from _pytest.mark import MarkGenerator as Mark
from _pytest.nodes import Collector
@@ -962,7 +962,11 @@ def test_mark_expressions_no_smear(testdir):
def test_addmarker_order():
node = Node("Test", config=mock.Mock(), session=mock.Mock(), nodeid="Test")
session = mock.Mock()
session.own_markers = []
session.parent = None
session.nodeid = ""
node = Node.from_parent(session, name="Test")
node.add_marker("foo")
node.add_marker("bar")
node.add_marker("baz", append=False)

View File

@@ -1,3 +1,9 @@
"""
Test importing of all internal packages and modules.
This ensures all internal packages can be imported without needing the pytest
namespace being set, which is critical for the initialization of xdist.
"""
import pkgutil
import subprocess
import sys

View File

@@ -1,40 +0,0 @@
import subprocess
import sys
import py
import _pytest
import pytest
pytestmark = pytest.mark.slow
MODSET = [
x
for x in py.path.local(_pytest.__file__).dirpath().visit("*.py")
if x.purebasename != "__init__"
]
@pytest.mark.parametrize("modfile", MODSET, ids=lambda x: x.purebasename)
def test_fileimport(modfile):
# this test ensures all internal packages can import
# without needing the pytest namespace being set
# this is critical for the initialization of xdist
p = subprocess.Popen(
[
sys.executable,
"-c",
"import sys, py; py.path.local(sys.argv[1]).pyimport()",
modfile.strpath,
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
(out, err) = p.communicate()
assert p.returncode == 0, "importing %s failed (exitcode %d): out=%r, err=%r" % (
modfile,
p.returncode,
out,
err,
)

View File

@@ -22,6 +22,13 @@ def test_ischildnode(baseid, nodeid, expected):
assert result is expected
def test_node_from_parent_disallowed_arguments():
with pytest.raises(TypeError, match="session is"):
nodes.Node.from_parent(None, session=None)
with pytest.raises(TypeError, match="config is"):
nodes.Node.from_parent(None, config=None)
def test_std_warn_not_pytestwarning(testdir):
items = testdir.getitems(
"""

View File

@@ -1,7 +1,7 @@
import argparse
import distutils.spawn
import os
import shlex
import shutil
import sys
import py
@@ -12,22 +12,22 @@ from _pytest.config.exceptions import UsageError
@pytest.fixture
def parser():
def parser() -> parseopt.Parser:
return parseopt.Parser()
class TestParser:
def test_no_help_by_default(self):
def test_no_help_by_default(self) -> None:
parser = parseopt.Parser(usage="xyz")
pytest.raises(UsageError, lambda: parser.parse(["-h"]))
def test_custom_prog(self, parser):
def test_custom_prog(self, parser: parseopt.Parser) -> None:
"""Custom prog can be set for `argparse.ArgumentParser`."""
assert parser._getparser().prog == os.path.basename(sys.argv[0])
parser.prog = "custom-prog"
assert parser._getparser().prog == "custom-prog"
def test_argument(self):
def test_argument(self) -> None:
with pytest.raises(parseopt.ArgumentError):
# need a short or long option
argument = parseopt.Argument()
@@ -45,7 +45,7 @@ class TestParser:
"Argument(_short_opts: ['-t'], _long_opts: ['--test'], dest: 'abc')"
)
def test_argument_type(self):
def test_argument_type(self) -> None:
argument = parseopt.Argument("-t", dest="abc", type=int)
assert argument.type is int
argument = parseopt.Argument("-t", dest="abc", type=str)
@@ -60,7 +60,7 @@ class TestParser:
)
assert argument.type is str
def test_argument_processopt(self):
def test_argument_processopt(self) -> None:
argument = parseopt.Argument("-t", type=int)
argument.default = 42
argument.dest = "abc"
@@ -68,19 +68,19 @@ class TestParser:
assert res["default"] == 42
assert res["dest"] == "abc"
def test_group_add_and_get(self, parser):
def test_group_add_and_get(self, parser: parseopt.Parser) -> None:
group = parser.getgroup("hello", description="desc")
assert group.name == "hello"
assert group.description == "desc"
def test_getgroup_simple(self, parser):
def test_getgroup_simple(self, parser: parseopt.Parser) -> None:
group = parser.getgroup("hello", description="desc")
assert group.name == "hello"
assert group.description == "desc"
group2 = parser.getgroup("hello")
assert group2 is group
def test_group_ordering(self, parser):
def test_group_ordering(self, parser: parseopt.Parser) -> None:
parser.getgroup("1")
parser.getgroup("2")
parser.getgroup("3", after="1")
@@ -88,20 +88,20 @@ class TestParser:
groups_names = [x.name for x in groups]
assert groups_names == list("132")
def test_group_addoption(self):
def test_group_addoption(self) -> None:
group = parseopt.OptionGroup("hello")
group.addoption("--option1", action="store_true")
assert len(group.options) == 1
assert isinstance(group.options[0], parseopt.Argument)
def test_group_addoption_conflict(self):
def test_group_addoption_conflict(self) -> None:
group = parseopt.OptionGroup("hello again")
group.addoption("--option1", "--option-1", action="store_true")
with pytest.raises(ValueError) as err:
group.addoption("--option1", "--option-one", action="store_true")
assert str({"--option1"}) in str(err.value)
def test_group_shortopt_lowercase(self, parser):
def test_group_shortopt_lowercase(self, parser: parseopt.Parser) -> None:
group = parser.getgroup("hello")
with pytest.raises(ValueError):
group.addoption("-x", action="store_true")
@@ -109,30 +109,30 @@ class TestParser:
group._addoption("-x", action="store_true")
assert len(group.options) == 1
def test_parser_addoption(self, parser):
def test_parser_addoption(self, parser: parseopt.Parser) -> None:
group = parser.getgroup("custom options")
assert len(group.options) == 0
group.addoption("--option1", action="store_true")
assert len(group.options) == 1
def test_parse(self, parser):
def test_parse(self, parser: parseopt.Parser) -> None:
parser.addoption("--hello", dest="hello", action="store")
args = parser.parse(["--hello", "world"])
assert args.hello == "world"
assert not getattr(args, parseopt.FILE_OR_DIR)
def test_parse2(self, parser):
def test_parse2(self, parser: parseopt.Parser) -> None:
args = parser.parse([py.path.local()])
assert getattr(args, parseopt.FILE_OR_DIR)[0] == py.path.local()
def test_parse_known_args(self, parser):
def test_parse_known_args(self, parser: parseopt.Parser) -> None:
parser.parse_known_args([py.path.local()])
parser.addoption("--hello", action="store_true")
ns = parser.parse_known_args(["x", "--y", "--hello", "this"])
assert ns.hello
assert ns.file_or_dir == ["x"]
def test_parse_known_and_unknown_args(self, parser):
def test_parse_known_and_unknown_args(self, parser: parseopt.Parser) -> None:
parser.addoption("--hello", action="store_true")
ns, unknown = parser.parse_known_and_unknown_args(
["x", "--y", "--hello", "this"]
@@ -141,7 +141,7 @@ class TestParser:
assert ns.file_or_dir == ["x"]
assert unknown == ["--y", "this"]
def test_parse_will_set_default(self, parser):
def test_parse_will_set_default(self, parser: parseopt.Parser) -> None:
parser.addoption("--hello", dest="hello", default="x", action="store")
option = parser.parse([])
assert option.hello == "x"
@@ -149,25 +149,22 @@ class TestParser:
parser.parse_setoption([], option)
assert option.hello == "x"
def test_parse_setoption(self, parser):
def test_parse_setoption(self, parser: parseopt.Parser) -> None:
parser.addoption("--hello", dest="hello", action="store")
parser.addoption("--world", dest="world", default=42)
class A:
pass
option = A()
option = argparse.Namespace()
args = parser.parse_setoption(["--hello", "world"], option)
assert option.hello == "world"
assert option.world == 42
assert not args
def test_parse_special_destination(self, parser):
def test_parse_special_destination(self, parser: parseopt.Parser) -> None:
parser.addoption("--ultimate-answer", type=int)
args = parser.parse(["--ultimate-answer", "42"])
assert args.ultimate_answer == 42
def test_parse_split_positional_arguments(self, parser):
def test_parse_split_positional_arguments(self, parser: parseopt.Parser) -> None:
parser.addoption("-R", action="store_true")
parser.addoption("-S", action="store_false")
args = parser.parse(["-R", "4", "2", "-S"])
@@ -181,7 +178,7 @@ class TestParser:
assert args.R is True
assert args.S is False
def test_parse_defaultgetter(self):
def test_parse_defaultgetter(self) -> None:
def defaultget(option):
if not hasattr(option, "type"):
return
@@ -199,17 +196,17 @@ class TestParser:
assert option.this == 42
assert option.no is False
def test_drop_short_helper(self):
def test_drop_short_helper(self) -> None:
parser = argparse.ArgumentParser(
formatter_class=parseopt.DropShorterLongHelpFormatter, allow_abbrev=False
)
parser.add_argument(
"-t", "--twoword", "--duo", "--two-word", "--two", help="foo"
).map_long_option = {"two": "two-word"}
)
# throws error on --deux only!
parser.add_argument(
"-d", "--deuxmots", "--deux-mots", action="store_true", help="foo"
).map_long_option = {"deux": "deux-mots"}
)
parser.add_argument("-s", action="store_true", help="single short")
parser.add_argument("--abc", "-a", action="store_true", help="bar")
parser.add_argument("--klm", "-k", "--kl-m", action="store_true", help="bar")
@@ -221,7 +218,7 @@ class TestParser:
)
parser.add_argument(
"-x", "--exit-on-first", "--exitfirst", action="store_true", help="spam"
).map_long_option = {"exitfirst": "exit-on-first"}
)
parser.add_argument("files_and_dirs", nargs="*")
args = parser.parse_args(["-k", "--duo", "hallo", "--exitfirst"])
assert args.twoword == "hallo"
@@ -236,32 +233,32 @@ class TestParser:
args = parser.parse_args(["file", "dir"])
assert "|".join(args.files_and_dirs) == "file|dir"
def test_drop_short_0(self, parser):
def test_drop_short_0(self, parser: parseopt.Parser) -> None:
parser.addoption("--funcarg", "--func-arg", action="store_true")
parser.addoption("--abc-def", "--abc-def", action="store_true")
parser.addoption("--klm-hij", action="store_true")
with pytest.raises(UsageError):
parser.parse(["--funcarg", "--k"])
def test_drop_short_2(self, parser):
def test_drop_short_2(self, parser: parseopt.Parser) -> None:
parser.addoption("--func-arg", "--doit", action="store_true")
args = parser.parse(["--doit"])
assert args.func_arg is True
def test_drop_short_3(self, parser):
def test_drop_short_3(self, parser: parseopt.Parser) -> None:
parser.addoption("--func-arg", "--funcarg", "--doit", action="store_true")
args = parser.parse(["abcd"])
assert args.func_arg is False
assert args.file_or_dir == ["abcd"]
def test_drop_short_help0(self, parser):
def test_drop_short_help0(self, parser: parseopt.Parser) -> None:
parser.addoption("--func-args", "--doit", help="foo", action="store_true")
parser.parse([])
help = parser.optparser.format_help()
assert "--func-args, --doit foo" in help
# testing would be more helpful with all help generated
def test_drop_short_help1(self, parser):
def test_drop_short_help1(self, parser: parseopt.Parser) -> None:
group = parser.getgroup("general")
group.addoption("--doit", "--func-args", action="store_true", help="foo")
group._addoption(
@@ -275,7 +272,7 @@ class TestParser:
help = parser.optparser.format_help()
assert "-doit, --func-args foo" in help
def test_multiple_metavar_help(self, parser):
def test_multiple_metavar_help(self, parser: parseopt.Parser) -> None:
"""
Help text for options with a metavar tuple should display help
in the form "--preferences=value1 value2 value3" (#2004).
@@ -290,8 +287,8 @@ class TestParser:
assert "--preferences=value1 value2 value3" in help
def test_argcomplete(testdir, monkeypatch):
if not distutils.spawn.find_executable("bash"):
def test_argcomplete(testdir, monkeypatch) -> None:
if not shutil.which("bash"):
pytest.skip("bash not available")
script = str(testdir.tmpdir.join("test_argcomplete"))

View File

@@ -3,9 +3,9 @@ import sys
import types
import pytest
from _pytest.config import ExitCode
from _pytest.config import PytestPluginManager
from _pytest.config.exceptions import UsageError
from _pytest.main import ExitCode
from _pytest.main import Session
@@ -122,7 +122,7 @@ class TestPytestPluginInteractions:
def test_hook_proxy(self, testdir):
"""Test the gethookproxy function(#2016)"""
config = testdir.parseconfig()
session = Session(config)
session = Session.from_config(config)
testdir.makepyfile(**{"tests/conftest.py": "", "tests/subdir/conftest.py": ""})
conftest1 = testdir.tmpdir.join("tests/conftest.py")

View File

@@ -8,8 +8,8 @@ import py.path
import _pytest.pytester as pytester
import pytest
from _pytest.config import ExitCode
from _pytest.config import PytestPluginManager
from _pytest.main import ExitCode
from _pytest.outcomes import Failed
from _pytest.pytester import CwdSnapshot
from _pytest.pytester import HookRecorder
@@ -458,17 +458,26 @@ def test_testdir_run_timeout_expires(testdir) -> None:
def test_linematcher_with_nonlist() -> None:
"""Test LineMatcher with regard to passing in a set (accidentally)."""
lm = LineMatcher([])
from _pytest._code.source import Source
with pytest.raises(AssertionError):
lm.fnmatch_lines(set())
with pytest.raises(AssertionError):
lm.fnmatch_lines({})
lm = LineMatcher([])
with pytest.raises(TypeError, match="invalid type for lines2: set"):
lm.fnmatch_lines(set()) # type: ignore[arg-type] # noqa: F821
with pytest.raises(TypeError, match="invalid type for lines2: dict"):
lm.fnmatch_lines({}) # type: ignore[arg-type] # noqa: F821
with pytest.raises(TypeError, match="invalid type for lines2: set"):
lm.re_match_lines(set()) # type: ignore[arg-type] # noqa: F821
with pytest.raises(TypeError, match="invalid type for lines2: dict"):
lm.re_match_lines({}) # type: ignore[arg-type] # noqa: F821
with pytest.raises(TypeError, match="invalid type for lines2: Source"):
lm.fnmatch_lines(Source()) # type: ignore[arg-type] # noqa: F821
lm.fnmatch_lines([])
lm.fnmatch_lines(())
assert lm._getlines({}) == {}
assert lm._getlines(set()) == set()
lm.fnmatch_lines("")
assert lm._getlines({}) == {} # type: ignore[arg-type,comparison-overlap] # noqa: F821
assert lm._getlines(set()) == set() # type: ignore[arg-type,comparison-overlap] # noqa: F821
assert lm._getlines(Source()) == []
assert lm._getlines(Source("pass\npass")) == ["pass", "pass"]
def test_linematcher_match_failure() -> None:
@@ -499,8 +508,28 @@ def test_linematcher_match_failure() -> None:
]
def test_linematcher_consecutive():
lm = LineMatcher(["1", "", "2"])
with pytest.raises(pytest.fail.Exception) as excinfo:
lm.fnmatch_lines(["1", "2"], consecutive=True)
assert str(excinfo.value).splitlines() == [
"exact match: '1'",
"no consecutive match: '2'",
" with: ''",
]
lm.re_match_lines(["1", r"\d?", "2"], consecutive=True)
with pytest.raises(pytest.fail.Exception) as excinfo:
lm.re_match_lines(["1", r"\d", "2"], consecutive=True)
assert str(excinfo.value).splitlines() == [
"exact match: '1'",
r"no consecutive match: '\\d'",
" with: ''",
]
@pytest.mark.parametrize("function", ["no_fnmatch_line", "no_re_match_line"])
def test_no_matching(function) -> None:
def test_linematcher_no_matching(function) -> None:
if function == "no_fnmatch_line":
good_pattern = "*.py OK*"
bad_pattern = "*X.py OK*"
@@ -548,7 +577,7 @@ def test_no_matching(function) -> None:
func(bad_pattern) # bad pattern does not match any line: passes
def test_no_matching_after_match() -> None:
def test_linematcher_no_matching_after_match() -> None:
lm = LineMatcher(["1", "2", "3"])
lm.fnmatch_lines(["1", "3"])
with pytest.raises(Failed) as e:
@@ -556,17 +585,15 @@ def test_no_matching_after_match() -> None:
assert str(e.value).splitlines() == ["fnmatch: '*'", " with: '1'"]
def test_pytester_addopts(request, monkeypatch) -> None:
def test_pytester_addopts_before_testdir(request, monkeypatch) -> None:
orig = os.environ.get("PYTEST_ADDOPTS", None)
monkeypatch.setenv("PYTEST_ADDOPTS", "--orig-unused")
testdir = request.getfixturevalue("testdir")
try:
assert "PYTEST_ADDOPTS" not in os.environ
finally:
testdir.finalize()
assert os.environ["PYTEST_ADDOPTS"] == "--orig-unused"
assert "PYTEST_ADDOPTS" not in os.environ
testdir.finalize()
assert os.environ.get("PYTEST_ADDOPTS") == "--orig-unused"
monkeypatch.undo()
assert os.environ.get("PYTEST_ADDOPTS") == orig
def test_run_stdin(testdir) -> None:
@@ -646,14 +673,10 @@ def test_popen_default_stdin_stderr_and_stdin_None(testdir) -> None:
def test_spawn_uses_tmphome(testdir) -> None:
import os
tmphome = str(testdir.tmpdir)
assert os.environ.get("HOME") == tmphome
# Does use HOME only during run.
assert os.environ.get("HOME") != tmphome
testdir._env_run_update["CUSTOMENV"] = "42"
testdir.monkeypatch.setenv("CUSTOMENV", "42")
p1 = testdir.makepyfile(
"""
@@ -710,3 +733,13 @@ def test_testdir_outcomes_with_multiple_errors(testdir):
result.assert_outcomes(error=2)
assert result.parseoutcomes() == {"error": 2}
def test_makefile_joins_absolute_path(testdir: Testdir) -> None:
absfile = testdir.tmpdir / "absfile"
if sys.platform == "win32":
with pytest.raises(OSError):
testdir.makepyfile(**{str(absfile): ""})
else:
p1 = testdir.makepyfile(**{str(absfile): ""})
assert str(p1) == (testdir.tmpdir / absfile) + ".py"

View File

@@ -10,10 +10,10 @@ import py
import _pytest._code
import pytest
from _pytest import main
from _pytest import outcomes
from _pytest import reports
from _pytest import runner
from _pytest.config import ExitCode
from _pytest.outcomes import Exit
from _pytest.outcomes import Failed
from _pytest.outcomes import OutcomeException
@@ -681,7 +681,7 @@ def test_pytest_fail_notrace_non_ascii(testdir) -> None:
def test_pytest_no_tests_collected_exit_status(testdir) -> None:
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*collected 0 items*"])
assert result.ret == main.ExitCode.NO_TESTS_COLLECTED
assert result.ret == ExitCode.NO_TESTS_COLLECTED
testdir.makepyfile(
test_foo="""
@@ -692,12 +692,12 @@ def test_pytest_no_tests_collected_exit_status(testdir) -> None:
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*collected 1 item*"])
result.stdout.fnmatch_lines(["*1 passed*"])
assert result.ret == main.ExitCode.OK
assert result.ret == ExitCode.OK
result = testdir.runpytest("-k nonmatch")
result.stdout.fnmatch_lines(["*collected 1 item*"])
result.stdout.fnmatch_lines(["*1 deselected*"])
assert result.ret == main.ExitCode.NO_TESTS_COLLECTED
assert result.ret == ExitCode.NO_TESTS_COLLECTED
def test_exception_printing_skip() -> None:

View File

@@ -1,5 +1,5 @@
import pytest
from _pytest.main import ExitCode
from _pytest.config import ExitCode
class SessionTests:

View File

@@ -1,5 +1,5 @@
import pytest
from _pytest.main import ExitCode
from _pytest.config import ExitCode
@pytest.fixture(params=["--setup-only", "--setup-plan", "--setup-show"], scope="module")

View File

@@ -3,7 +3,6 @@ terminal reporting of the full testing process.
"""
import collections
import os
import re
import sys
import textwrap
from io import StringIO
@@ -16,7 +15,8 @@ import py
import _pytest.config
import pytest
from _pytest.main import ExitCode
from _pytest.config import ExitCode
from _pytest.pytester import Testdir
from _pytest.reports import BaseReport
from _pytest.terminal import _folded_skips
from _pytest.terminal import _get_line_with_reprcrash_message
@@ -26,14 +26,8 @@ from _pytest.terminal import TerminalReporter
DistInfo = collections.namedtuple("DistInfo", ["project_name", "version"])
COLORS = {
"red": "\x1b[31m",
"green": "\x1b[32m",
"yellow": "\x1b[33m",
"bold": "\x1b[1m",
"reset": "\x1b[0m",
}
RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()}
TRANS_FNMATCH = str.maketrans({"[": "[[]", "]": "[]]"})
class Option:
@@ -163,6 +157,8 @@ class TestTerminal:
"test2.py": "def test_2(): pass",
}
)
# Explicitly test colored output.
testdir.monkeypatch.setenv("PY_COLORS", "1")
child = testdir.spawn_pytest("-v test1.py test2.py")
child.expect(r"collecting \.\.\.")
@@ -604,6 +600,7 @@ class TestTerminalFunctional:
assert result.ret == 0
def test_header_trailer_info(self, testdir, request):
testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
testdir.makepyfile(
"""
def test_passes():
@@ -677,6 +674,26 @@ class TestTerminalFunctional:
]
)
def test_showlocals_short(self, testdir):
p1 = testdir.makepyfile(
"""
def test_showlocals_short():
x = 3
y = "xxxx"
assert 0
"""
)
result = testdir.runpytest(p1, "-l", "--tb=short")
result.stdout.fnmatch_lines(
[
"test_showlocals_short.py:*",
" assert 0",
"E assert 0",
" x = 3",
" y = 'xxxx'",
]
)
@pytest.fixture
def verbose_testfile(self, testdir):
return testdir.makepyfile(
@@ -714,6 +731,7 @@ class TestTerminalFunctional:
if not pytestconfig.pluginmanager.get_plugin("xdist"):
pytest.skip("xdist plugin not installed")
testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
result = testdir.runpytest(
verbose_testfile, "-v", "-n 1", "-Walways::pytest.PytestWarning"
)
@@ -761,13 +779,42 @@ class TestTerminalFunctional:
result = testdir.runpytest(*params)
result.stdout.fnmatch_lines(["collected 3 items", "hello from hook: 3 items"])
def test_summary_f_alias(self, testdir):
"""Test that 'f' and 'F' report chars are aliases and don't show up twice in the summary (#6334)"""
testdir.makepyfile(
"""
def test():
assert False
"""
)
result = testdir.runpytest("-rfF")
expected = "FAILED test_summary_f_alias.py::test - assert False"
result.stdout.fnmatch_lines([expected])
assert result.stdout.lines.count(expected) == 1
def test_summary_s_alias(self, testdir):
"""Test that 's' and 'S' report chars are aliases and don't show up twice in the summary"""
testdir.makepyfile(
"""
import pytest
@pytest.mark.skip
def test():
pass
"""
)
result = testdir.runpytest("-rsS")
expected = "SKIPPED [1] test_summary_s_alias.py:3: unconditional skip"
result.stdout.fnmatch_lines([expected])
assert result.stdout.lines.count(expected) == 1
def test_fail_extra_reporting(testdir, monkeypatch):
monkeypatch.setenv("COLUMNS", "80")
testdir.makepyfile("def test_this(): assert 0, 'this_failed' * 100")
result = testdir.runpytest()
result = testdir.runpytest("-rN")
result.stdout.no_fnmatch_line("*short test summary*")
result = testdir.runpytest("-rf")
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"*test summary*",
@@ -836,7 +883,7 @@ def test_pass_output_reporting(testdir):
)
def test_color_yes(testdir):
def test_color_yes(testdir, color_mapping):
p1 = testdir.makepyfile(
"""
def fail():
@@ -847,16 +894,10 @@ def test_color_yes(testdir):
"""
)
result = testdir.runpytest("--color=yes", str(p1))
if sys.version_info < (3, 6):
# py36 required for ordered markup
output = result.stdout.str()
assert "test session starts" in output
assert "\x1b[1m" in output
return
color_mapping.requires_ordered_markup(result)
result.stdout.fnmatch_lines(
[
line.format(**COLORS).replace("[", "[[]")
for line in [
color_mapping.format_for_fnmatch(
[
"{bold}=*= test session starts =*={reset}",
"collected 1 item",
"",
@@ -865,26 +906,25 @@ def test_color_yes(testdir):
"=*= FAILURES =*=",
"{red}{bold}_*_ test_this _*_{reset}",
"",
"{bold} def test_this():{reset}",
"{bold}> fail(){reset}",
" {kw}def{hl-reset} {function}test_this{hl-reset}():",
"> fail()",
"",
"{bold}{red}test_color_yes.py{reset}:5: ",
"_ _ * _ _*",
"",
"{bold} def fail():{reset}",
"{bold}> assert 0{reset}",
" {kw}def{hl-reset} {function}fail{hl-reset}():",
"> {kw}assert{hl-reset} {number}0{hl-reset}",
"{bold}{red}E assert 0{reset}",
"",
"{bold}{red}test_color_yes.py{reset}:2: AssertionError",
"{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}",
]
]
)
)
result = testdir.runpytest("--color=yes", "--tb=short", str(p1))
result.stdout.fnmatch_lines(
[
line.format(**COLORS).replace("[", "[[]")
for line in [
color_mapping.format_for_fnmatch(
[
"{bold}=*= test session starts =*={reset}",
"collected 1 item",
"",
@@ -893,13 +933,13 @@ def test_color_yes(testdir):
"=*= FAILURES =*=",
"{red}{bold}_*_ test_this _*_{reset}",
"{bold}{red}test_color_yes.py{reset}:5: in test_this",
"{bold} fail(){reset}",
" fail()",
"{bold}{red}test_color_yes.py{reset}:2: in fail",
"{bold} assert 0{reset}",
" {kw}assert{hl-reset} {number}0{hl-reset}",
"{bold}{red}E assert 0{reset}",
"{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}",
]
]
)
)
@@ -936,37 +976,62 @@ def test_color_yes_collection_on_non_atty(testdir, verbose):
def test_getreportopt():
from _pytest.terminal import _REPORTCHARS_DEFAULT
class Config:
class Option:
reportchars = ""
disable_warnings = True
reportchars = _REPORTCHARS_DEFAULT
disable_warnings = False
option = Option()
config = Config()
assert _REPORTCHARS_DEFAULT == "fE"
# Default.
assert getreportopt(config) == "wfE"
config.option.reportchars = "sf"
assert getreportopt(config) == "sf"
assert getreportopt(config) == "wsf"
config.option.reportchars = "sfxw"
assert getreportopt(config) == "sfxw"
config.option.reportchars = "a"
assert getreportopt(config) == "wsxXEf"
config.option.reportchars = "N"
assert getreportopt(config) == "w"
config.option.reportchars = "NwfE"
assert getreportopt(config) == "wfE"
config.option.reportchars = "NfENx"
assert getreportopt(config) == "wx"
# Now with --disable-warnings.
config.option.disable_warnings = True
config.option.reportchars = "a"
assert getreportopt(config) == "sxXEf"
config.option.reportchars = "sfx"
assert getreportopt(config) == "sfx"
config.option.reportchars = "sfxw"
assert getreportopt(config) == "sfx"
# Now with --disable-warnings.
config.option.disable_warnings = False
config.option.reportchars = "a"
assert getreportopt(config) == "sxXwEf" # NOTE: "w" included!
config.option.reportchars = "sfx"
assert getreportopt(config) == "sfxw"
config.option.reportchars = "sfxw"
assert getreportopt(config) == "sfxw"
config.option.reportchars = "a"
assert getreportopt(config) == "sxXwEf" # NOTE: "w" included!
assert getreportopt(config) == "sxXEf"
config.option.reportchars = "A"
assert getreportopt(config) == "PpsxXwEf"
assert getreportopt(config) == "PpsxXEf"
config.option.reportchars = "AN"
assert getreportopt(config) == ""
config.option.reportchars = "NwfE"
assert getreportopt(config) == "fE"
def test_terminalreporter_reportopt_addopts(testdir):
@@ -1083,7 +1148,7 @@ class TestGenericReporting:
)
for tbopt in ["long", "short", "no"]:
print("testing --tb=%s..." % tbopt)
result = testdir.runpytest("--tb=%s" % tbopt)
result = testdir.runpytest("-rN", "--tb=%s" % tbopt)
s = result.stdout.str()
if tbopt == "long":
assert "print(6*7)" in s
@@ -1622,7 +1687,7 @@ class TestProgressOutputStyle:
]
)
def test_colored_progress(self, testdir, monkeypatch):
def test_colored_progress(self, testdir, monkeypatch, color_mapping):
monkeypatch.setenv("PY_COLORS", "1")
testdir.makepyfile(
test_axfail="""
@@ -1651,27 +1716,25 @@ class TestProgressOutputStyle:
)
result = testdir.runpytest()
result.stdout.re_match_lines(
[
line.format(**RE_COLORS)
for line in [
color_mapping.format_for_rematch(
[
r"test_axfail.py {yellow}x{reset}{green} \s+ \[ 4%\]{reset}",
r"test_bar.py ({green}\.{reset}){{10}}{green} \s+ \[ 52%\]{reset}",
r"test_foo.py ({green}\.{reset}){{5}}{yellow} \s+ \[ 76%\]{reset}",
r"test_foobar.py ({red}F{reset}){{5}}{red} \s+ \[100%\]{reset}",
]
]
)
)
# Only xfail should have yellow progress indicator.
result = testdir.runpytest("test_axfail.py")
result.stdout.re_match_lines(
[
line.format(**RE_COLORS)
for line in [
color_mapping.format_for_rematch(
[
r"test_axfail.py {yellow}x{reset}{yellow} \s+ \[100%\]{reset}",
r"^{yellow}=+ ({yellow}{bold}|{bold}{yellow})1 xfailed{reset}{yellow} in ",
]
]
)
)
def test_count(self, many_tests_files, testdir):
@@ -1745,6 +1808,22 @@ class TestProgressOutputStyle:
r"\[gw\d\] \[\s*\d+%\] PASSED test_foobar.py::test_foobar\[1\]",
]
)
output.stdout.fnmatch_lines_random(
[
line.translate(TRANS_FNMATCH)
for line in [
"test_bar.py::test_bar[0] ",
"test_foo.py::test_foo[0] ",
"test_foobar.py::test_foobar[0] ",
"[gw?] [ 5%] PASSED test_*[?] ",
"[gw?] [ 10%] PASSED test_*[?] ",
"[gw?] [ 55%] PASSED test_*[?] ",
"[gw?] [ 60%] PASSED test_*[?] ",
"[gw?] [ 95%] PASSED test_*[?] ",
"[gw?] [100%] PASSED test_*[?] ",
]
]
)
def test_capture_no(self, many_tests_files, testdir):
output = testdir.runpytest("-s")
@@ -1805,12 +1884,16 @@ class TestProgressWithTeardown:
testdir.makepyfile(
"""
def test_foo(fail_teardown):
assert False
assert 0
"""
)
output = testdir.runpytest()
output = testdir.runpytest("-rfE")
output.stdout.re_match_lines(
[r"test_teardown_with_test_also_failing.py FE\s+\[100%\]"]
[
r"test_teardown_with_test_also_failing.py FE\s+\[100%\]",
"FAILED test_teardown_with_test_also_failing.py::test_foo - assert 0",
"ERROR test_teardown_with_test_also_failing.py::test_foo - assert False",
]
)
def test_teardown_many(self, testdir, many_files):
@@ -1819,15 +1902,21 @@ class TestProgressWithTeardown:
[r"test_bar.py (\.E){5}\s+\[ 25%\]", r"test_foo.py (\.E){15}\s+\[100%\]"]
)
def test_teardown_many_verbose(self, testdir, many_files):
output = testdir.runpytest("-v")
output.stdout.re_match_lines(
[
r"test_bar.py::test_bar\[0\] PASSED\s+\[ 5%\]",
r"test_bar.py::test_bar\[0\] ERROR\s+\[ 5%\]",
r"test_bar.py::test_bar\[4\] PASSED\s+\[ 25%\]",
r"test_bar.py::test_bar\[4\] ERROR\s+\[ 25%\]",
]
def test_teardown_many_verbose(
self, testdir: Testdir, many_files, color_mapping
) -> None:
result = testdir.runpytest("-v")
result.stdout.fnmatch_lines(
color_mapping.format_for_fnmatch(
[
"test_bar.py::test_bar[0] PASSED * [ 5%]",
"test_bar.py::test_bar[0] ERROR * [ 5%]",
"test_bar.py::test_bar[4] PASSED * [ 25%]",
"test_foo.py::test_foo[14] PASSED * [100%]",
"test_foo.py::test_foo[14] ERROR * [100%]",
"=* 20 passed, 20 errors in *",
]
)
)
def test_xdist_normal(self, many_files, testdir, monkeypatch):
@@ -1971,3 +2060,54 @@ def test_collecterror(testdir):
"*= 1 error in *",
]
)
def test_via_exec(testdir: Testdir) -> None:
p1 = testdir.makepyfile("exec('def test_via_exec(): pass')")
result = testdir.runpytest(str(p1), "-vv")
result.stdout.fnmatch_lines(
["test_via_exec.py::test_via_exec <- <string> PASSED*", "*= 1 passed in *"]
)
class TestCodeHighlight:
def test_code_highlight_simple(self, testdir: Testdir, color_mapping) -> None:
testdir.makepyfile(
"""
def test_foo():
assert 1 == 10
"""
)
result = testdir.runpytest("--color=yes")
color_mapping.requires_ordered_markup(result)
result.stdout.fnmatch_lines(
color_mapping.format_for_fnmatch(
[
" {kw}def{hl-reset} {function}test_foo{hl-reset}():",
"> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}",
"{bold}{red}E assert 1 == 10{reset}",
]
)
)
def test_code_highlight_continuation(self, testdir: Testdir, color_mapping) -> None:
testdir.makepyfile(
"""
def test_foo():
print('''
'''); assert 0
"""
)
result = testdir.runpytest("--color=yes")
color_mapping.requires_ordered_markup(result)
result.stdout.fnmatch_lines(
color_mapping.format_for_fnmatch(
[
" {kw}def{hl-reset} {function}test_foo{hl-reset}():",
" {print}print{hl-reset}({str}'''{hl-reset}{str}{hl-reset}",
"> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}",
"{bold}{red}E assert 0{reset}",
]
)
)

View File

@@ -74,19 +74,38 @@ class TestConfigTmpdir:
assert not mytemp.join("hello").check()
def test_basetemp(testdir):
testdata = [
("mypath", True),
("/mypath1", False),
("./mypath1", True),
("../mypath3", False),
("../../mypath4", False),
("mypath5/..", False),
("mypath6/../mypath6", True),
("mypath7/../mypath7/..", False),
]
@pytest.mark.parametrize("basename, is_ok", testdata)
def test_mktemp(testdir, basename, is_ok):
mytemp = testdir.tmpdir.mkdir("mytemp")
p = testdir.makepyfile(
"""
import pytest
def test_1(tmpdir_factory):
tmpdir_factory.mktemp('hello', numbered=False)
"""
def test_abs_path(tmpdir_factory):
tmpdir_factory.mktemp('{}', numbered=False)
""".format(
basename
)
)
result = testdir.runpytest(p, "--basetemp=%s" % mytemp)
assert result.ret == 0
print(mytemp)
assert mytemp.join("hello").check()
if is_ok:
assert result.ret == 0
assert mytemp.join(basename).check()
else:
assert result.ret == 1
result.stdout.fnmatch_lines("*ValueError*")
def test_tmpdir_always_is_realpath(testdir):

View File

@@ -1,7 +1,7 @@
import gc
import pytest
from _pytest.main import ExitCode
from _pytest.config import ExitCode
def test_simple_unittest(testdir):
@@ -537,24 +537,28 @@ class TestTrialUnittest:
)
result.stdout.fnmatch_lines(
[
"test_trial_error.py::TC::test_four FAILED",
"test_trial_error.py::TC::test_four SKIPPED",
"test_trial_error.py::TC::test_four ERROR",
"test_trial_error.py::TC::test_one FAILED",
"test_trial_error.py::TC::test_three FAILED",
"test_trial_error.py::TC::test_two FAILED",
"test_trial_error.py::TC::test_two SKIPPED",
"test_trial_error.py::TC::test_two ERROR",
"*ERRORS*",
"*_ ERROR at teardown of TC.test_four _*",
"NOTE: Incompatible Exception Representation, displaying natively:",
"*DelayedCalls*",
"*_ ERROR at teardown of TC.test_two _*",
"NOTE: Incompatible Exception Representation, displaying natively:",
"*DelayedCalls*",
"*= FAILURES =*",
"*_ TC.test_four _*",
"*NameError*crash*",
# "*_ TC.test_four _*",
# "*NameError*crash*",
"*_ TC.test_one _*",
"*NameError*crash*",
"*_ TC.test_three _*",
"NOTE: Incompatible Exception Representation, displaying natively:",
"*DelayedCalls*",
"*_ TC.test_two _*",
"*NameError*crash*",
"*= 4 failed, 1 error in *",
"*= 2 failed, 2 skipped, 2 errors in *",
]
)
@@ -1096,3 +1100,32 @@ def test_exit_outcome(testdir):
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*Exit: pytest_exit called*", "*= no tests ran in *"])
def test_trace(testdir, monkeypatch):
calls = []
def check_call(*args, **kwargs):
calls.append((args, kwargs))
assert args == ("runcall",)
class _pdb:
def runcall(*args, **kwargs):
calls.append((args, kwargs))
return _pdb
monkeypatch.setattr("_pytest.debugging.pytestPDB._init_pdb", check_call)
p1 = testdir.makepyfile(
"""
import unittest
class MyTestCase(unittest.TestCase):
def test(self):
self.assertEqual('foo', 'foo')
"""
)
result = testdir.runpytest("--trace", str(p1))
assert len(calls) == 2
assert result.ret == 0

View File

@@ -1,3 +1,4 @@
import os
import warnings
import pytest
@@ -599,3 +600,136 @@ def test_pytest_configure_warning(testdir, recwarn):
assert "INTERNALERROR" not in result.stderr.str()
warning = recwarn.pop()
assert str(warning.message) == "from pytest_configure"
class TestStackLevel:
@pytest.fixture
def capwarn(self, testdir):
class CapturedWarnings:
captured = []
@classmethod
def pytest_warning_captured(cls, warning_message, when, item, location):
cls.captured.append((warning_message, location))
testdir.plugins = [CapturedWarnings()]
return CapturedWarnings
def test_issue4445_rewrite(self, testdir, capwarn):
"""#4445: Make sure the warning points to a reasonable location
See origin of _issue_warning_captured at: _pytest.assertion.rewrite.py:241
"""
testdir.makepyfile(some_mod="")
conftest = testdir.makeconftest(
"""
import some_mod
import pytest
pytest.register_assert_rewrite("some_mod")
"""
)
testdir.parseconfig()
# with stacklevel=5 the warning originates from register_assert_rewrite
# function in the created conftest.py
assert len(capwarn.captured) == 1
warning, location = capwarn.captured.pop()
file, lineno, func = location
assert "Module already imported" in str(warning.message)
assert file == str(conftest)
assert func == "<module>" # the above conftest.py
assert lineno == 4
def test_issue4445_preparse(self, testdir, capwarn):
"""#4445: Make sure the warning points to a reasonable location
See origin of _issue_warning_captured at: _pytest.config.__init__.py:910
"""
testdir.makeconftest(
"""
import nothing
"""
)
testdir.parseconfig("--help")
# with stacklevel=2 the warning should originate from config._preparse and is
# thrown by an errorneous conftest.py
assert len(capwarn.captured) == 1
warning, location = capwarn.captured.pop()
file, _, func = location
assert "could not load initial conftests" in str(warning.message)
assert "config{sep}__init__.py".format(sep=os.sep) in file
assert func == "_preparse"
def test_issue4445_import_plugin(self, testdir, capwarn):
"""#4445: Make sure the warning points to a reasonable location
See origin of _issue_warning_captured at: _pytest.config.__init__.py:585
"""
testdir.makepyfile(
some_plugin="""
import pytest
pytest.skip("thing", allow_module_level=True)
"""
)
testdir.syspathinsert()
testdir.parseconfig("-p", "some_plugin")
# with stacklevel=2 the warning should originate from
# config.PytestPluginManager.import_plugin is thrown by a skipped plugin
assert len(capwarn.captured) == 1
warning, location = capwarn.captured.pop()
file, _, func = location
assert "skipped plugin 'some_plugin': thing" in str(warning.message)
assert "config{sep}__init__.py".format(sep=os.sep) in file
assert func == "import_plugin"
def test_issue4445_resultlog(self, testdir, capwarn):
"""#4445: Make sure the warning points to a reasonable location
See origin of _issue_warning_captured at: _pytest.resultlog.py:35
"""
testdir.makepyfile(
"""
def test_dummy():
pass
"""
)
# Use parseconfigure() because the warning in resultlog.py is triggered in
# the pytest_configure hook
testdir.parseconfigure(
"--result-log={dir}".format(dir=testdir.tmpdir.join("result.log"))
)
# with stacklevel=2 the warning originates from resultlog.pytest_configure
# and is thrown when --result-log is used
warning, location = capwarn.captured.pop()
file, _, func = location
assert "--result-log is deprecated" in str(warning.message)
assert "resultlog.py" in file
assert func == "pytest_configure"
def test_issue4445_issue5928_mark_generator(self, testdir):
"""#4445 and #5928: Make sure the warning from an unknown mark points to
the test file where this mark is used.
"""
testfile = testdir.makepyfile(
"""
import pytest
@pytest.mark.unknown
def test_it():
pass
"""
)
result = testdir.runpytest_subprocess()
# with stacklevel=2 the warning should originate from the above created test file
result.stdout.fnmatch_lines_random(
[
"*{testfile}:3*".format(testfile=str(testfile)),
"*Unknown pytest.mark.unknown*",
]
)