From cbad31973641af61da06b9a2efc10a1eb6faf197 Mon Sep 17 00:00:00 2001 From: ParetoLife Date: Mon, 27 Jan 2020 10:50:05 +0100 Subject: [PATCH 1/9] Update getting-started.rst From the description it seemed to me as if just prefixing your methods with ``test_`` was enough, but you also need to prefix your class with ``Test``. Of course, in the reference material this is clearly stated, but I think it makes sense to mention it here as well, since you also mention the part about the methods' prefix. --- doc/en/getting-started.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 59197d0d7..83b4677e9 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -127,7 +127,7 @@ Once you develop multiple tests, you may want to group them into a class. pytest x = "hello" assert hasattr(x, "check") -``pytest`` discovers all tests following its :ref:`Conventions for Python test discovery `, so it finds both ``test_`` prefixed functions. There is no need to subclass anything. We can simply run the module by passing its filename: +``pytest`` discovers all tests following its :ref:`Conventions for Python test discovery `, so it finds both ``test_`` prefixed functions. There is no need to subclass anything, but make sure to prefix your class with ``Test`` otherwise the class will be skipped. We can simply run the module by passing its filename: .. code-block:: pytest From b01e3794281468ff9f80f7e445d525d675e70dab Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 28 Jan 2020 15:52:46 +0100 Subject: [PATCH 2/9] tests: harden test_teardown_many_verbose --- testing/test_terminal.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index f284ad577..c3a0c17e1 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -33,6 +33,8 @@ COLORS = { } RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()} +TRANS_FNMATCH = str.maketrans({"[": "[[]", "]": "[]]"}) + class Option: def __init__(self, verbosity=0): @@ -1772,14 +1774,19 @@ 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( + def test_teardown_many_verbose(self, testdir: Testdir, many_files) -> None: + result = testdir.runpytest("-v") + result.stdout.fnmatch_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%\]", + line.translate(TRANS_FNMATCH) + for line in [ + "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 *", + ] ] ) From a3f482cebab103bed8e8b10abb343753865905ee Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 29 Jan 2020 01:06:23 +0100 Subject: [PATCH 3/9] tests: move test_getfslineno It should be in `test_code` when testing `_pytest._code.getfslineno`, not to be confused with `_pytest._code.source.getfslineno`. Adds an extra assert (via https://github.com/pytest-dev/pytest/pull/6590). --- testing/code/test_code.py | 33 +++++++++++++++++++++++++++++++++ testing/code/test_source.py | 30 ------------------------------ 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/testing/code/test_code.py b/testing/code/test_code.py index f8e1ce17f..31977c719 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -1,9 +1,13 @@ +import inspect import sys from types import FrameType from unittest import mock +import py.path + import _pytest._code import pytest +from _pytest._code import getfslineno def test_ne() -> None: @@ -179,3 +183,32 @@ class TestReprFuncArgs: tw_mock.lines[0] == r"unicode_string = São Paulo, utf8_string = b'S\xc3\xa3o Paulo'" ) + + +def test_getfslineno() -> None: + def f(x) -> None: + pass + + fspath, lineno = getfslineno(f) + + assert isinstance(fspath, py.path.local) + assert fspath.basename == "test_code.py" + assert lineno == f.__code__.co_firstlineno - 1 # see findsource + + class A: + pass + + fspath, lineno = getfslineno(A) + + _, A_lineno = inspect.findsource(A) + assert isinstance(fspath, py.path.local) + assert fspath.basename == "test_code.py" + assert lineno == A_lineno + + assert getfslineno(3) == ("", -1) + + class B: + pass + + B.__name__ = "B2" + assert getfslineno(B)[1] == -1 diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 030e60676..d769d4d77 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -495,36 +495,6 @@ def test_findsource() -> None: assert src[lineno] == " def x():" -def test_getfslineno() -> None: - from _pytest._code import getfslineno - - def f(x) -> None: - pass - - fspath, lineno = getfslineno(f) - - assert isinstance(fspath, py.path.local) - assert fspath.basename == "test_source.py" - assert lineno == f.__code__.co_firstlineno - 1 # see findsource - - class A: - pass - - fspath, lineno = getfslineno(A) - - _, A_lineno = inspect.findsource(A) - assert fspath.basename == "test_source.py" - assert lineno == A_lineno - - assert getfslineno(3) == ("", -1) - - class B: - pass - - B.__name__ = "B2" - assert getfslineno(B)[1] == -1 - - def test_code_of_object_instance_with_call() -> None: class A: pass From 3f4b8d3aec5b3605e48ac2b895ddb9b2cbea7163 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 29 Jan 2020 02:54:12 +0100 Subject: [PATCH 4/9] test_code: improve coverage --- testing/code/test_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/code/test_code.py b/testing/code/test_code.py index 31977c719..69b53d340 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -187,7 +187,7 @@ class TestReprFuncArgs: def test_getfslineno() -> None: def f(x) -> None: - pass + raise NotImplementedError() fspath, lineno = getfslineno(f) From d848a205631d1c44d8b6f6d79fd25341abe8bcd6 Mon Sep 17 00:00:00 2001 From: sdementen Date: Mon, 27 Jan 2020 10:45:17 +0100 Subject: [PATCH 5/9] Extend the incremental marker for parametrize The incremental marker is adapted to handle properly test classes with parametrize defined at class level. Fix #3125 --- doc/en/example/simple.rst | 40 +++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 1570850fc..1a5c5b444 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -461,21 +461,49 @@ an ``incremental`` marker which is to be used on classes: # content of conftest.py - import pytest + # store history of failures per test class name and per index in parametrize (if parametrize used) + _test_failed_incremental: Dict[str, Dict[Tuple[int, ...], str]] = {} def pytest_runtest_makereport(item, call): if "incremental" in item.keywords: + # incremental marker is used if call.excinfo is not None: - parent = item.parent - parent._previousfailed = item + # the test has failed + # retrieve the class name of the test + cls_name = str(item.cls) + # retrieve the index of the test (if parametrize is used in combination with incremental) + parametrize_index = ( + tuple(item.callspec.indices.values()) + if hasattr(item, "callspec") + else () + ) + # retrieve the name of the test function + test_name = item.originalname or item.name + # store in _test_failed_incremental the original name of the failed test + _test_failed_incremental.setdefault(cls_name, {}).setdefault( + parametrize_index, test_name + ) def pytest_runtest_setup(item): if "incremental" in item.keywords: - previousfailed = getattr(item.parent, "_previousfailed", None) - if previousfailed is not None: - pytest.xfail("previous test failed ({})".format(previousfailed.name)) + # retrieve the class name of the test + cls_name = str(item.cls) + # check if a previous test has failed for this class + if cls_name in _test_failed_incremental: + # retrieve the index of the test (if parametrize is used in combination with incremental) + parametrize_index = ( + tuple(item.callspec.indices.values()) + if hasattr(item, "callspec") + else () + ) + # retrieve the name of the first test function to fail for this class name and index + test_name = _test_failed_incremental[cls_name].get(parametrize_index, None) + # if name found, test has failed for the combination of class name & test name + if test_name is not None: + pytest.xfail("previous test failed ({})".format(test_name)) + These two hook implementations work together to abort incremental-marked tests in a class. Here is a test module example: From d478e2bbcaefa0f5b193a201dca29bae8c552a91 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 29 Jan 2020 18:28:11 +0100 Subject: [PATCH 6/9] doc: release-5.3.4 (cherry picked from commit fd1a51a23fa687cf344f3506dff6cde0166faf2c) --- doc/en/announce/index.rst | 1 + doc/en/announce/release-5.3.5.rst | 19 +++++++++++++++++++ doc/en/changelog.rst | 9 +++++++++ 3 files changed, 29 insertions(+) create mode 100644 doc/en/announce/release-5.3.5.rst diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index fb17b8e93..a35039587 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-5.3.5 release-5.3.4 release-5.3.3 release-5.3.2 diff --git a/doc/en/announce/release-5.3.5.rst b/doc/en/announce/release-5.3.5.rst new file mode 100644 index 000000000..46095339f --- /dev/null +++ b/doc/en/announce/release-5.3.5.rst @@ -0,0 +1,19 @@ +pytest-5.3.5 +======================================= + +pytest 5.3.5 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Daniel Hahler +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index e0a2495cc..f0ad2b8ec 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,15 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 5.3.5 (2020-01-29) +========================= + +Bug Fixes +--------- + +- `#6517 `_: Fix regression in pytest 5.3.4 causing an INTERNALERROR due to a wrong assertion. + + pytest 5.3.4 (2020-01-20) ========================= From 3dbc61dd80bb22a0e52aacd7be3d988cfd72263b Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 29 Jan 2020 01:20:45 +0100 Subject: [PATCH 7/9] tests: test_code: improve/clarify imports --- testing/code/test_code.py | 47 ++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/testing/code/test_code.py b/testing/code/test_code.py index 69b53d340..5d35c19ab 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -5,15 +5,18 @@ from unittest import mock import py.path -import _pytest._code import pytest +from _pytest._code import Code +from _pytest._code import ExceptionInfo +from _pytest._code import Frame from _pytest._code import getfslineno +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 @@ -21,7 +24,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 @@ -30,7 +33,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: @@ -38,13 +41,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()""" @@ -55,7 +58,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)") @@ -65,13 +68,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 @@ -90,25 +93,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") @@ -116,25 +119,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"})] @@ -146,12 +149,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: @@ -162,7 +165,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 @@ -172,8 +175,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) From 97f16459932b9337c101dc61f64bce9d62faaa75 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 29 Jan 2020 21:56:01 +0200 Subject: [PATCH 8/9] Don't wrap the markdown for GitHub releases --- scripts/publish-gh-release-notes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/publish-gh-release-notes.py b/scripts/publish-gh-release-notes.py index f8d8b3986..583b5bfc7 100644 --- a/scripts/publish-gh-release-notes.py +++ b/scripts/publish-gh-release-notes.py @@ -61,7 +61,7 @@ def parse_changelog(tag_name): def convert_rst_to_md(text): - return pypandoc.convert_text(text, "md", format="rst") + return pypandoc.convert_text(text, "md", format="rst", extra_args=["--wrap=none"]) def main(argv): From 78eddcb5b10d652d757c9a80cba642d89aa56f11 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 29 Jan 2020 22:43:22 +0100 Subject: [PATCH 9/9] tests: move test_getfslineno back Reverts https://github.com/pytest-dev/pytest/pull/6610. The tested `getfslineno` is `src/_pytest/_code/source.py` actually, exported via `src/_pytest/_code/__init__.py`. I've confused it with the one in `src/_pytest/compat.py` apparently. --- testing/code/test_code.py | 33 --------------------------------- testing/code/test_source.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/testing/code/test_code.py b/testing/code/test_code.py index 5d35c19ab..826a37708 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -1,15 +1,11 @@ -import inspect import sys from types import FrameType from unittest import mock -import py.path - import pytest from _pytest._code import Code from _pytest._code import ExceptionInfo from _pytest._code import Frame -from _pytest._code import getfslineno from _pytest._code.code import ReprFuncArgs @@ -184,32 +180,3 @@ class TestReprFuncArgs: tw_mock.lines[0] == r"unicode_string = São Paulo, utf8_string = b'S\xc3\xa3o Paulo'" ) - - -def test_getfslineno() -> None: - def f(x) -> None: - raise NotImplementedError() - - fspath, lineno = getfslineno(f) - - assert isinstance(fspath, py.path.local) - assert fspath.basename == "test_code.py" - assert lineno == f.__code__.co_firstlineno - 1 # see findsource - - class A: - pass - - fspath, lineno = getfslineno(A) - - _, A_lineno = inspect.findsource(A) - assert isinstance(fspath, py.path.local) - assert fspath.basename == "test_code.py" - assert lineno == A_lineno - - assert getfslineno(3) == ("", -1) - - class B: - pass - - B.__name__ = "B2" - assert getfslineno(B)[1] == -1 diff --git a/testing/code/test_source.py b/testing/code/test_source.py index d769d4d77..b5efdb317 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -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 @@ -495,6 +496,35 @@ def test_findsource() -> None: assert src[lineno] == " def x():" +def test_getfslineno() -> None: + def f(x) -> None: + raise NotImplementedError() + + fspath, lineno = getfslineno(f) + + assert isinstance(fspath, py.path.local) + assert fspath.basename == "test_source.py" + assert lineno == f.__code__.co_firstlineno - 1 # see findsource + + class A: + pass + + 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 + + assert getfslineno(3) == ("", -1) + + class B: + pass + + B.__name__ = "B2" + assert getfslineno(B)[1] == -1 + + def test_code_of_object_instance_with_call() -> None: class A: pass