From 9769bc05c626dd0401e691c2c9c68f774fb990c3 Mon Sep 17 00:00:00 2001 From: Aly Sivji Date: Thu, 2 Aug 2018 02:54:15 -0500 Subject: [PATCH 01/44] moving plugin inside pytest first pass --- .gitignore | 2 ++ src/_pytest/assertion/util.py | 46 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/.gitignore b/.gitignore index f5cd0145c..907876f1b 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,5 @@ coverage.xml .pydevproject .project .settings +.vscode +.envrc diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 451e45495..1f5a857b8 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -122,6 +122,12 @@ def assertrepr_compare(config, op, left, right): def isset(x): return isinstance(x, (set, frozenset)) + def isdatacls(obj): + return hasattr(obj, "__dataclass_fields__") + + def isattrs(obj): + return hasattr(obj, "__attrs_attrs__") + def isiterable(obj): try: iter(obj) @@ -142,6 +148,10 @@ def assertrepr_compare(config, op, left, right): explanation = _compare_eq_set(left, right, verbose) elif isdict(left) and isdict(right): explanation = _compare_eq_dict(left, right, verbose) + elif type(left) == type(right) and isdatacls(left) and isdatacls(right): + explanation = _compare_eq_class(left, right, verbose, type="data") + elif type(left) == type(right) and isattrs(left) and isattrs(right): + explanation = _compare_eq_class(left, right, verbose, type="attrs") if isiterable(left) and isiterable(right): expl = _compare_eq_iterable(left, right, verbose) if explanation is not None: @@ -315,6 +325,42 @@ def _compare_eq_dict(left, right, verbose=False): return explanation +def _compare_eq_class(left, right, verbose, type=None): + # TODO account for verbose + # TODO write tests + + if type == "data": + all_fields = left.__dataclass_fields__ + fields_to_check = [field for field, info in all_fields.items() if info.compare] + elif type == "attrs": + all_fields = left.__attrs_attrs__ + fields_to_check = [field.name for field in all_fields if field.cmp] + else: + raise RuntimeError # TODO figure out what to raise + + same = [] + diff = [] + for field in fields_to_check: + if getattr(left, field) == getattr(right, field): + same.append(field) + else: + diff.append(field) + + explanation = [] + if same: + explanation += [("Common attributes:")] + explanation += pprint.pformat(same).splitlines() + if diff: + explanation += [("Differing attributes:")] + for k in diff: + class_name = left.__class__.__name__ + explanation += [ + u("%s(%s=%r) != %s(%s=%r)") + % (class_name, k, getattr(left, k), class_name, k, getattr(right, k)) + ] + return explanation + + def _notin_text(term, text, verbose=False): index = text.find(term) head = text[:index] From d42f1e87c3c4b55ef5faa1a11a4f3083860c864d Mon Sep 17 00:00:00 2001 From: Aly Sivji Date: Thu, 2 Aug 2018 17:16:14 -0500 Subject: [PATCH 02/44] Add tests for attrs and dataclasses --- src/_pytest/assertion/util.py | 11 ++- testing/test_assertion.py | 139 ++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 6 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 1f5a857b8..da5d5fe97 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -326,9 +326,6 @@ def _compare_eq_dict(left, right, verbose=False): def _compare_eq_class(left, right, verbose, type=None): - # TODO account for verbose - # TODO write tests - if type == "data": all_fields = left.__dataclass_fields__ fields_to_check = [field for field, info in all_fields.items() if info.compare] @@ -336,7 +333,7 @@ def _compare_eq_class(left, right, verbose, type=None): all_fields = left.__attrs_attrs__ fields_to_check = [field.name for field in all_fields if field.cmp] else: - raise RuntimeError # TODO figure out what to raise + raise RuntimeError same = [] diff = [] @@ -347,8 +344,10 @@ def _compare_eq_class(left, right, verbose, type=None): diff.append(field) explanation = [] - if same: - explanation += [("Common attributes:")] + if same and verbose < 2: + explanation += [u("Omitting %s identical items, use -vv to show") % len(same)] + elif same: + explanation += [u("Common items:")] explanation += pprint.pformat(same).splitlines() if diff: explanation += [("Differing attributes:")] diff --git a/testing/test_assertion.py b/testing/test_assertion.py index b6c31aba2..87f7de2b5 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -6,6 +6,7 @@ from __future__ import print_function import sys import textwrap +import attr import py import six @@ -548,6 +549,144 @@ class TestAssert_reprcompare(object): assert msg +class TestAssert_reprcompare_dataclass(object): + @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") + def test_dataclasses(self): + from dataclasses import dataclass + + @dataclass + class SimpleDataObject: + field_a: int + field_b: str + + left = SimpleDataObject(1, "b") + right = SimpleDataObject(1, "c") + + lines = callequal(left, right) + assert lines[1].startswith("Omitting 1 identical item") + assert "Common items" not in lines + for line in lines[1:]: + assert "field_a" not in line + + @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") + def test_dataclasses_verbose(self): + from dataclasses import dataclass + + @dataclass + class SimpleDataObject: + field_a: int + field_b: str + + left = SimpleDataObject(1, "b") + right = SimpleDataObject(1, "c") + + lines = callequal(left, right, verbose=2) + assert lines[1].startswith("Common items:") + assert "Omitting" not in lines[1] + assert lines[2] == "['field_a']" + + def test_dataclasses_with_attribute_comparison_off(self): + from dataclasses import dataclass, field + + @dataclass + class SimpleDataObject: + field_a: int + field_b: str = field(compare=False) + + left = SimpleDataObject(1, "b") + right = SimpleDataObject(1, "b") + + lines = callequal(left, right, verbose=2) + assert lines[1].startswith("Common items:") + assert "Omitting" not in lines[1] + assert lines[2] == "['field_a']" + for line in lines[2:]: + assert "field_b" not in line + + def test_comparing_different_data_classes(self): + from dataclasses import dataclass + + @dataclass + class SimpleDataObjectOne: + field_a: int + field_b: str + + @dataclass + class SimpleDataObjectTwo: + field_a: int + field_b: str + + left = SimpleDataObjectOne(1, "b") + right = SimpleDataObjectTwo(1, "c") + + lines = callequal(left, right) + assert lines is None + + +class TestAssert_reprcompare_attrsclass(object): + def test_attrs(self): + @attr.s + class SimpleDataObject: + field_a = attr.ib() + field_b = attr.ib() + + left = SimpleDataObject(1, "b") + right = SimpleDataObject(1, "c") + + lines = callequal(left, right) + assert lines[1].startswith("Omitting 1 identical item") + assert "Common items" not in lines + for line in lines[1:]: + assert "field_a" not in line + + def test_attrs_verbose(self): + @attr.s + class SimpleDataObject: + field_a = attr.ib() + field_b = attr.ib() + + left = SimpleDataObject(1, "b") + right = SimpleDataObject(1, "c") + + lines = callequal(left, right, verbose=2) + assert lines[1].startswith("Common items:") + assert "Omitting" not in lines[1] + assert lines[2] == "['field_a']" + + def test_attrs_with_attribute_comparison_off(self): + @attr.s + class SimpleDataObject: + field_a = attr.ib() + field_b = attr.ib(cmp=False) + + left = SimpleDataObject(1, "b") + right = SimpleDataObject(1, "b") + + lines = callequal(left, right, verbose=2) + assert lines[1].startswith("Common items:") + assert "Omitting" not in lines[1] + assert lines[2] == "['field_a']" + for line in lines[2:]: + assert "field_b" not in line + + def test_comparing_different_attrs(self): + @attr.s + class SimpleDataObjectOne: + field_a = attr.ib() + field_b = attr.ib() + + @attr.s + class SimpleDataObjectTwo: + field_a = attr.ib() + field_b = attr.ib() + + left = SimpleDataObjectOne(1, "b") + right = SimpleDataObjectTwo(1, "c") + + lines = callequal(left, right) + assert lines is None + + class TestFormatExplanation(object): def test_special_chars_full(self, testdir): # Issue 453, for the bug this would raise IndexError From a0ba881c2202b4b7d49505fb5d53088ea387db30 Mon Sep 17 00:00:00 2001 From: Aly Sivji Date: Thu, 2 Aug 2018 18:08:07 -0500 Subject: [PATCH 03/44] Add change to log; name to AUTHORS --- AUTHORS | 1 + changelog/3632.feature.rst | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog/3632.feature.rst diff --git a/AUTHORS b/AUTHORS index 777eda324..f5ba603c2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,6 +11,7 @@ Alan Velasco Alexander Johnson Alexei Kozlenok Allan Feldman +Aly Sivji Anatoly Bubenkoff Anders Hovmöller Andras Tim diff --git a/changelog/3632.feature.rst b/changelog/3632.feature.rst new file mode 100644 index 000000000..bb7918ab7 --- /dev/null +++ b/changelog/3632.feature.rst @@ -0,0 +1 @@ +Provide richer comparison on ``AssertionError`` for objects created using `dataclasses `_ (Python 3.7+) or `attrs package `_. From 1184db827373822863fe936b3027b225cb77ed02 Mon Sep 17 00:00:00 2001 From: Aly Sivji Date: Thu, 2 Aug 2018 18:22:15 -0500 Subject: [PATCH 04/44] cleaning up --- changelog/3632.feature.rst | 2 +- testing/test_assertion.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog/3632.feature.rst b/changelog/3632.feature.rst index bb7918ab7..a08a6dd61 100644 --- a/changelog/3632.feature.rst +++ b/changelog/3632.feature.rst @@ -1 +1 @@ -Provide richer comparison on ``AssertionError`` for objects created using `dataclasses `_ (Python 3.7+) or `attrs package `_. +Richer comparison information on ``AssertionError`` for objects created using `attrs `_ or `dataclasses `_ (Python 3.7+). diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 87f7de2b5..e7f3dbaf3 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -585,6 +585,7 @@ class TestAssert_reprcompare_dataclass(object): assert "Omitting" not in lines[1] assert lines[2] == "['field_a']" + @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_dataclasses_with_attribute_comparison_off(self): from dataclasses import dataclass, field @@ -603,6 +604,7 @@ class TestAssert_reprcompare_dataclass(object): for line in lines[2:]: assert "field_b" not in line + @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_comparing_different_data_classes(self): from dataclasses import dataclass From 87b019d5f91f7fb74c8a80218b4d4cf1647e61e5 Mon Sep 17 00:00:00 2001 From: Aly Sivji Date: Thu, 2 Aug 2018 18:24:46 -0500 Subject: [PATCH 05/44] fix gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 907876f1b..e2d59502c 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,3 @@ coverage.xml .project .settings .vscode -.envrc From 1847cc74208a58efbfd1d8154e34b2bf18987b8c Mon Sep 17 00:00:00 2001 From: Aly Sivji Date: Fri, 3 Aug 2018 09:23:50 -0500 Subject: [PATCH 06/44] adding docs and cleaning up --- changelog/3632.feature.rst | 2 +- doc/en/example/assertion/failure_demo.py | 24 +++++++++++++++++++++++ doc/en/example/assertion/test_failures.py | 2 +- src/_pytest/assertion/util.py | 4 ++-- testing/test_assertion.py | 16 +++++++-------- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/changelog/3632.feature.rst b/changelog/3632.feature.rst index a08a6dd61..a715288e1 100644 --- a/changelog/3632.feature.rst +++ b/changelog/3632.feature.rst @@ -1 +1 @@ -Richer comparison information on ``AssertionError`` for objects created using `attrs `_ or `dataclasses `_ (Python 3.7+). +Richer comparison introspection on ``AssertionError`` for objects created using `attrs `_ or `dataclasses `_ (Python 3.7+). diff --git a/doc/en/example/assertion/failure_demo.py b/doc/en/example/assertion/failure_demo.py index 115fd3e22..10f8798f2 100644 --- a/doc/en/example/assertion/failure_demo.py +++ b/doc/en/example/assertion/failure_demo.py @@ -101,6 +101,30 @@ class TestSpecialisedExplanations(object): text = "head " * 50 + "f" * 70 + "tail " * 20 assert "f" * 70 not in text + def test_eq_dataclass(self): + from dataclasses import dataclass + + @dataclass + class Foo(object): + a: int + b: str + + left = Foo(1, "b") + right = Foo(1, "c") + assert left == right + + def test_eq_attrs(self): + import attr + + @attr.s + class Foo(object): + a = attr.ib() + b = attr.ib() + + left = Foo(1, "b") + right = Foo(1, "c") + assert left == right + def test_attribute(): class Foo(object): diff --git a/doc/en/example/assertion/test_failures.py b/doc/en/example/assertion/test_failures.py index 9ffe31664..30ebc72dc 100644 --- a/doc/en/example/assertion/test_failures.py +++ b/doc/en/example/assertion/test_failures.py @@ -9,5 +9,5 @@ def test_failure_demo_fails_properly(testdir): failure_demo.copy(target) failure_demo.copy(testdir.tmpdir.join(failure_demo.basename)) result = testdir.runpytest(target, syspathinsert=True) - result.stdout.fnmatch_lines(["*42 failed*"]) + result.stdout.fnmatch_lines(["*44 failed*"]) assert result.ret != 0 diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index da5d5fe97..ac83f6000 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -347,12 +347,12 @@ def _compare_eq_class(left, right, verbose, type=None): if same and verbose < 2: explanation += [u("Omitting %s identical items, use -vv to show") % len(same)] elif same: - explanation += [u("Common items:")] + explanation += [u("Common attributes:")] explanation += pprint.pformat(same).splitlines() if diff: + class_name = left.__class__.__name__ explanation += [("Differing attributes:")] for k in diff: - class_name = left.__class__.__name__ explanation += [ u("%s(%s=%r) != %s(%s=%r)") % (class_name, k, getattr(left, k), class_name, k, getattr(right, k)) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index e7f3dbaf3..8ddd96b94 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -564,7 +564,7 @@ class TestAssert_reprcompare_dataclass(object): lines = callequal(left, right) assert lines[1].startswith("Omitting 1 identical item") - assert "Common items" not in lines + assert "Common attributes" not in lines for line in lines[1:]: assert "field_a" not in line @@ -581,7 +581,7 @@ class TestAssert_reprcompare_dataclass(object): right = SimpleDataObject(1, "c") lines = callequal(left, right, verbose=2) - assert lines[1].startswith("Common items:") + assert lines[1].startswith("Common attributes:") assert "Omitting" not in lines[1] assert lines[2] == "['field_a']" @@ -598,14 +598,14 @@ class TestAssert_reprcompare_dataclass(object): right = SimpleDataObject(1, "b") lines = callequal(left, right, verbose=2) - assert lines[1].startswith("Common items:") + assert lines[1].startswith("Common attributes:") assert "Omitting" not in lines[1] assert lines[2] == "['field_a']" for line in lines[2:]: assert "field_b" not in line @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") - def test_comparing_different_data_classes(self): + def test_comparing_two_different_data_classes(self): from dataclasses import dataclass @dataclass @@ -637,7 +637,7 @@ class TestAssert_reprcompare_attrsclass(object): lines = callequal(left, right) assert lines[1].startswith("Omitting 1 identical item") - assert "Common items" not in lines + assert "Common attributes" not in lines for line in lines[1:]: assert "field_a" not in line @@ -651,7 +651,7 @@ class TestAssert_reprcompare_attrsclass(object): right = SimpleDataObject(1, "c") lines = callequal(left, right, verbose=2) - assert lines[1].startswith("Common items:") + assert lines[1].startswith("Common attributes:") assert "Omitting" not in lines[1] assert lines[2] == "['field_a']" @@ -665,13 +665,13 @@ class TestAssert_reprcompare_attrsclass(object): right = SimpleDataObject(1, "b") lines = callequal(left, right, verbose=2) - assert lines[1].startswith("Common items:") + assert lines[1].startswith("Common attributes:") assert "Omitting" not in lines[1] assert lines[2] == "['field_a']" for line in lines[2:]: assert "field_b" not in line - def test_comparing_different_attrs(self): + def test_comparing_two_different_attrs_classes(self): @attr.s class SimpleDataObjectOne: field_a = attr.ib() From a3e388a73a4ef6b712c442c4147633322790b80d Mon Sep 17 00:00:00 2001 From: Aly Sivji Date: Fri, 3 Aug 2018 10:28:46 -0500 Subject: [PATCH 07/44] Improve changelog --- changelog/3632.feature.rst | 2 +- src/_pytest/assertion/util.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog/3632.feature.rst b/changelog/3632.feature.rst index a715288e1..023fa3607 100644 --- a/changelog/3632.feature.rst +++ b/changelog/3632.feature.rst @@ -1 +1 @@ -Richer comparison introspection on ``AssertionError`` for objects created using `attrs `_ or `dataclasses `_ (Python 3.7+). +Richer equality comparison introspection on ``AssertionError`` for objects created using `attrs `_ or `dataclasses `_ (Python 3.7+, `backported to Python 3.6 `_). diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index ac83f6000..4536cd0dd 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -333,7 +333,7 @@ def _compare_eq_class(left, right, verbose, type=None): all_fields = left.__attrs_attrs__ fields_to_check = [field.name for field in all_fields if field.cmp] else: - raise RuntimeError + raise RuntimeError("Unexpected value for `type` paramater") same = [] diff = [] From 025d160dfc0ce87482606b27762c47d47e4a2d5a Mon Sep 17 00:00:00 2001 From: Aly Sivji Date: Fri, 3 Aug 2018 11:29:45 -0500 Subject: [PATCH 08/44] Update tests to pass in py27 --- changelog/3632.feature.rst | 2 +- testing/test_assertion.py | 46 +++++++++++++++++++------------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/changelog/3632.feature.rst b/changelog/3632.feature.rst index 023fa3607..cb1d93750 100644 --- a/changelog/3632.feature.rst +++ b/changelog/3632.feature.rst @@ -1 +1 @@ -Richer equality comparison introspection on ``AssertionError`` for objects created using `attrs `_ or `dataclasses `_ (Python 3.7+, `backported to Python 3.6 `_). +Richer equality comparison introspection on ``AssertionError`` for objects created using `attrs `_ or `dataclasses `_ (Python 3.7+, `backported to 3.6 `_). diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 8ddd96b94..fdcfccfed 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -552,12 +552,12 @@ class TestAssert_reprcompare(object): class TestAssert_reprcompare_dataclass(object): @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_dataclasses(self): - from dataclasses import dataclass + from dataclasses import dataclass, field @dataclass - class SimpleDataObject: - field_a: int - field_b: str + class SimpleDataObject(object): + field_a = field() + field_b = field() left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "c") @@ -570,12 +570,12 @@ class TestAssert_reprcompare_dataclass(object): @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_dataclasses_verbose(self): - from dataclasses import dataclass + from dataclasses import dataclass, field @dataclass - class SimpleDataObject: - field_a: int - field_b: str + class SimpleDataObject(object): + field_a = field() + field_b = field() left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "c") @@ -590,9 +590,9 @@ class TestAssert_reprcompare_dataclass(object): from dataclasses import dataclass, field @dataclass - class SimpleDataObject: - field_a: int - field_b: str = field(compare=False) + class SimpleDataObject(object): + field_a = field() + field_b = field(compare=False) left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "b") @@ -606,17 +606,17 @@ class TestAssert_reprcompare_dataclass(object): @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_comparing_two_different_data_classes(self): - from dataclasses import dataclass + from dataclasses import dataclass, field @dataclass - class SimpleDataObjectOne: - field_a: int - field_b: str + class SimpleDataObjectOne(object): + field_a = field() + field_b = field() @dataclass - class SimpleDataObjectTwo: - field_a: int - field_b: str + class SimpleDataObjectTwo(object): + field_a = field() + field_b = field() left = SimpleDataObjectOne(1, "b") right = SimpleDataObjectTwo(1, "c") @@ -628,7 +628,7 @@ class TestAssert_reprcompare_dataclass(object): class TestAssert_reprcompare_attrsclass(object): def test_attrs(self): @attr.s - class SimpleDataObject: + class SimpleDataObject(object): field_a = attr.ib() field_b = attr.ib() @@ -643,7 +643,7 @@ class TestAssert_reprcompare_attrsclass(object): def test_attrs_verbose(self): @attr.s - class SimpleDataObject: + class SimpleDataObject(object): field_a = attr.ib() field_b = attr.ib() @@ -657,7 +657,7 @@ class TestAssert_reprcompare_attrsclass(object): def test_attrs_with_attribute_comparison_off(self): @attr.s - class SimpleDataObject: + class SimpleDataObject(object): field_a = attr.ib() field_b = attr.ib(cmp=False) @@ -673,12 +673,12 @@ class TestAssert_reprcompare_attrsclass(object): def test_comparing_two_different_attrs_classes(self): @attr.s - class SimpleDataObjectOne: + class SimpleDataObjectOne(object): field_a = attr.ib() field_b = attr.ib() @attr.s - class SimpleDataObjectTwo: + class SimpleDataObjectTwo(object): field_a = attr.ib() field_b = attr.ib() From e1e81e315e41fd4674b9e10c2bd4074734d3692b Mon Sep 17 00:00:00 2001 From: Aly Sivji Date: Sat, 4 Aug 2018 08:29:55 -0500 Subject: [PATCH 09/44] code review 1/n -- change hasattr to getattr --- src/_pytest/assertion/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 4536cd0dd..b6867436e 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -123,10 +123,10 @@ def assertrepr_compare(config, op, left, right): return isinstance(x, (set, frozenset)) def isdatacls(obj): - return hasattr(obj, "__dataclass_fields__") + return getattr(obj, "__dataclass_fields__", None) is not None def isattrs(obj): - return hasattr(obj, "__attrs_attrs__") + return getattr(obj, "__attrs_attrs__", None) is not None def isiterable(obj): try: From a663f60b054fa791c1cb9729b68dbddedfdff26f Mon Sep 17 00:00:00 2001 From: Aly Sivji Date: Mon, 10 Sep 2018 15:33:37 -0500 Subject: [PATCH 10/44] cr 2/n -- refactor compare eq class --- src/_pytest/assertion/util.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index b6867436e..9984b5120 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -148,10 +148,9 @@ def assertrepr_compare(config, op, left, right): explanation = _compare_eq_set(left, right, verbose) elif isdict(left) and isdict(right): explanation = _compare_eq_dict(left, right, verbose) - elif type(left) == type(right) and isdatacls(left) and isdatacls(right): - explanation = _compare_eq_class(left, right, verbose, type="data") - elif type(left) == type(right) and isattrs(left) and isattrs(right): - explanation = _compare_eq_class(left, right, verbose, type="attrs") + elif type(left) == type(right) and (isdatacls(left) or isattrs(left)): + type_fn = (isdatacls, isattrs) + explanation = _compare_eq_cls(left, right, verbose, type_fn) if isiterable(left) and isiterable(right): expl = _compare_eq_iterable(left, right, verbose) if explanation is not None: @@ -325,15 +324,14 @@ def _compare_eq_dict(left, right, verbose=False): return explanation -def _compare_eq_class(left, right, verbose, type=None): - if type == "data": +def _compare_eq_cls(left, right, verbose, type_fns): + isdatacls, isattrs = type_fns + if isdatacls(left): all_fields = left.__dataclass_fields__ fields_to_check = [field for field, info in all_fields.items() if info.compare] - elif type == "attrs": + elif isattrs(left): all_fields = left.__attrs_attrs__ fields_to_check = [field.name for field in all_fields if field.cmp] - else: - raise RuntimeError("Unexpected value for `type` paramater") same = [] diff = [] From 4e99c80425024dfcf733e060c5ce9c53ff49c475 Mon Sep 17 00:00:00 2001 From: Aly Sivji Date: Mon, 12 Nov 2018 11:24:15 -0600 Subject: [PATCH 11/44] have tests pass in python37; move to separate file --- src/_pytest/assertion/util.py | 8 ++++---- testing/test_assertion.py | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 9984b5120..4e10a3fbb 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -343,16 +343,16 @@ def _compare_eq_cls(left, right, verbose, type_fns): explanation = [] if same and verbose < 2: - explanation += [u("Omitting %s identical items, use -vv to show") % len(same)] + explanation.append(u"Omitting %s identical items, use -vv to show" % len(same)) elif same: - explanation += [u("Common attributes:")] + explanation += [u"Common attributes:"] explanation += pprint.pformat(same).splitlines() if diff: class_name = left.__class__.__name__ - explanation += [("Differing attributes:")] + explanation += [u"Differing attributes:"] for k in diff: explanation += [ - u("%s(%s=%r) != %s(%s=%r)") + (u"%s(%s=%r) != %s(%s=%r)") % (class_name, k, getattr(left, k), class_name, k, getattr(right, k)) ] return explanation diff --git a/testing/test_assertion.py b/testing/test_assertion.py index fdcfccfed..4a09187b7 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -556,8 +556,8 @@ class TestAssert_reprcompare_dataclass(object): @dataclass class SimpleDataObject(object): - field_a = field() - field_b = field() + field_a: int = field() + field_b: int = field() left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "c") @@ -574,8 +574,8 @@ class TestAssert_reprcompare_dataclass(object): @dataclass class SimpleDataObject(object): - field_a = field() - field_b = field() + field_a: int = field() + field_b: int = field() left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "c") @@ -591,8 +591,8 @@ class TestAssert_reprcompare_dataclass(object): @dataclass class SimpleDataObject(object): - field_a = field() - field_b = field(compare=False) + field_a: int = field() + field_b: int = field(compare=False) left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "b") @@ -610,13 +610,13 @@ class TestAssert_reprcompare_dataclass(object): @dataclass class SimpleDataObjectOne(object): - field_a = field() - field_b = field() + field_a: int = field() + field_b: int = field() @dataclass class SimpleDataObjectTwo(object): - field_a = field() - field_b = field() + field_a: int = field() + field_b: int = field() left = SimpleDataObjectOne(1, "b") right = SimpleDataObjectTwo(1, "c") From 2bffd6829eb38a44f9e7523d044dad7339830691 Mon Sep 17 00:00:00 2001 From: Aly Sivji Date: Mon, 12 Nov 2018 15:36:16 -0600 Subject: [PATCH 12/44] Move dataclass tests for 3.7 to separate file --- .../dataclasses/test_compare_dataclasses.py | 14 +++ ...ompare_dataclasses_field_comparison_off.py | 14 +++ .../test_compare_dataclasses_verbose.py | 14 +++ .../test_compare_two_different_dataclasses.py | 19 ++++ testing/test_assertion.py | 101 ++++++------------ 5 files changed, 96 insertions(+), 66 deletions(-) create mode 100644 testing/example_scripts/dataclasses/test_compare_dataclasses.py create mode 100644 testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py create mode 100644 testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py create mode 100644 testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_dataclasses.py new file mode 100644 index 000000000..3bbebe2aa --- /dev/null +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass +from dataclasses import field + + +def test_dataclasses(): + @dataclass + class SimpleDataObject(object): + field_a: int = field() + field_b: int = field() + + left = SimpleDataObject(1, "b") + right = SimpleDataObject(1, "c") + + assert left == right diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py new file mode 100644 index 000000000..63b9f534e --- /dev/null +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass +from dataclasses import field + + +def test_dataclasses_with_attribute_comparison_off(): + @dataclass + class SimpleDataObject(object): + field_a: int = field() + field_b: int = field(compare=False) + + left = SimpleDataObject(1, "b") + right = SimpleDataObject(1, "c") + + assert left == right diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py new file mode 100644 index 000000000..17835c0c3 --- /dev/null +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass +from dataclasses import field + + +def test_dataclasses_verbose(): + @dataclass + class SimpleDataObject(object): + field_a: int = field() + field_b: int = field() + + left = SimpleDataObject(1, "b") + right = SimpleDataObject(1, "c") + + assert left == right diff --git a/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py new file mode 100644 index 000000000..24f185d8a --- /dev/null +++ b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py @@ -0,0 +1,19 @@ +from dataclasses import dataclass +from dataclasses import field + + +def test_comparing_two_different_data_classes(): + @dataclass + class SimpleDataObjectOne(object): + field_a: int = field() + field_b: int = field() + + @dataclass + class SimpleDataObjectTwo(object): + field_a: int = field() + field_b: int = field() + + left = SimpleDataObjectOne(1, "b") + right = SimpleDataObjectTwo(1, "c") + + assert left != right diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 4a09187b7..2a55f70bc 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -551,78 +551,47 @@ class TestAssert_reprcompare(object): class TestAssert_reprcompare_dataclass(object): @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") - def test_dataclasses(self): - from dataclasses import dataclass, field - - @dataclass - class SimpleDataObject(object): - field_a: int = field() - field_b: int = field() - - left = SimpleDataObject(1, "b") - right = SimpleDataObject(1, "c") - - lines = callequal(left, right) - assert lines[1].startswith("Omitting 1 identical item") - assert "Common attributes" not in lines - for line in lines[1:]: - assert "field_a" not in line + def test_dataclasses(self, testdir): + p = testdir.copy_example("dataclasses/test_compare_dataclasses.py") + result = testdir.runpytest(p) + result.assert_outcomes(failed=1, passed=0) + result.stdout.fnmatch_lines( + [ + "*Omitting 1 identical items, use -vv to show*", + "*Differing attributes:*", + "*SimpleDataObject(field_b='b') != SimpleDataObject(field_b='c')*", + ] + ) @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") - def test_dataclasses_verbose(self): - from dataclasses import dataclass, field - - @dataclass - class SimpleDataObject(object): - field_a: int = field() - field_b: int = field() - - left = SimpleDataObject(1, "b") - right = SimpleDataObject(1, "c") - - lines = callequal(left, right, verbose=2) - assert lines[1].startswith("Common attributes:") - assert "Omitting" not in lines[1] - assert lines[2] == "['field_a']" + def test_dataclasses_verbose(self, testdir): + p = testdir.copy_example("dataclasses/test_compare_dataclasses_verbose.py") + result = testdir.runpytest(p, "-vv") + result.assert_outcomes(failed=1, passed=0) + result.stdout.fnmatch_lines( + [ + "*Common attributes:*", + "*['field_a']*", + "*Differing attributes:*", + "*SimpleDataObject(field_b='b') != SimpleDataObject(field_b='c')*", + ] + ) @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") - def test_dataclasses_with_attribute_comparison_off(self): - from dataclasses import dataclass, field - - @dataclass - class SimpleDataObject(object): - field_a: int = field() - field_b: int = field(compare=False) - - left = SimpleDataObject(1, "b") - right = SimpleDataObject(1, "b") - - lines = callequal(left, right, verbose=2) - assert lines[1].startswith("Common attributes:") - assert "Omitting" not in lines[1] - assert lines[2] == "['field_a']" - for line in lines[2:]: - assert "field_b" not in line + def test_dataclasses_with_attribute_comparison_off(self, testdir): + p = testdir.copy_example( + "dataclasses/test_compare_dataclasses_field_comparison_off.py" + ) + result = testdir.runpytest(p, "-vv") + result.assert_outcomes(failed=0, passed=1) @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") - def test_comparing_two_different_data_classes(self): - from dataclasses import dataclass, field - - @dataclass - class SimpleDataObjectOne(object): - field_a: int = field() - field_b: int = field() - - @dataclass - class SimpleDataObjectTwo(object): - field_a: int = field() - field_b: int = field() - - left = SimpleDataObjectOne(1, "b") - right = SimpleDataObjectTwo(1, "c") - - lines = callequal(left, right) - assert lines is None + def test_comparing_two_different_data_classes(self, testdir): + p = testdir.copy_example( + "dataclasses/test_compare_two_different_dataclasses.py" + ) + result = testdir.runpytest(p, "-vv") + result.assert_outcomes(failed=0, passed=1) class TestAssert_reprcompare_attrsclass(object): From b83e97802e91ff74c6a437bb076de65bf2423fa1 Mon Sep 17 00:00:00 2001 From: Aly Sivji Date: Tue, 13 Nov 2018 09:37:02 -0600 Subject: [PATCH 13/44] improve failure output --- src/_pytest/assertion/util.py | 8 +++----- testing/test_assertion.py | 12 ++++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 4e10a3fbb..3ec9a365a 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -345,15 +345,13 @@ def _compare_eq_cls(left, right, verbose, type_fns): if same and verbose < 2: explanation.append(u"Omitting %s identical items, use -vv to show" % len(same)) elif same: - explanation += [u"Common attributes:"] + explanation += [u"Matching attributes:"] explanation += pprint.pformat(same).splitlines() if diff: - class_name = left.__class__.__name__ explanation += [u"Differing attributes:"] - for k in diff: + for field in diff: explanation += [ - (u"%s(%s=%r) != %s(%s=%r)") - % (class_name, k, getattr(left, k), class_name, k, getattr(right, k)) + (u"%s: %r != %r") % (field, getattr(left, field), getattr(right, field)) ] return explanation diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 2a55f70bc..bb54e394f 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -559,7 +559,7 @@ class TestAssert_reprcompare_dataclass(object): [ "*Omitting 1 identical items, use -vv to show*", "*Differing attributes:*", - "*SimpleDataObject(field_b='b') != SimpleDataObject(field_b='c')*", + "*field_b: 'b' != 'c'*", ] ) @@ -570,10 +570,10 @@ class TestAssert_reprcompare_dataclass(object): result.assert_outcomes(failed=1, passed=0) result.stdout.fnmatch_lines( [ - "*Common attributes:*", + "*Matching attributes:*", "*['field_a']*", "*Differing attributes:*", - "*SimpleDataObject(field_b='b') != SimpleDataObject(field_b='c')*", + "*field_b: 'b' != 'c'*", ] ) @@ -606,7 +606,7 @@ class TestAssert_reprcompare_attrsclass(object): lines = callequal(left, right) assert lines[1].startswith("Omitting 1 identical item") - assert "Common attributes" not in lines + assert "Matching attributes" not in lines for line in lines[1:]: assert "field_a" not in line @@ -620,7 +620,7 @@ class TestAssert_reprcompare_attrsclass(object): right = SimpleDataObject(1, "c") lines = callequal(left, right, verbose=2) - assert lines[1].startswith("Common attributes:") + assert lines[1].startswith("Matching attributes:") assert "Omitting" not in lines[1] assert lines[2] == "['field_a']" @@ -634,7 +634,7 @@ class TestAssert_reprcompare_attrsclass(object): right = SimpleDataObject(1, "b") lines = callequal(left, right, verbose=2) - assert lines[1].startswith("Common attributes:") + assert lines[1].startswith("Matching attributes:") assert "Omitting" not in lines[1] assert lines[2] == "['field_a']" for line in lines[2:]: From 0385c273439d105e3cc5131759cd44e66702b5ee Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 14 Nov 2018 21:55:37 +0100 Subject: [PATCH 14/44] cacheprovider: do not write README/.gitignore to existing dir Fixes https://github.com/pytest-dev/pytest/issues/4393. --- changelog/4393.bugfix.rst | 1 + src/_pytest/cacheprovider.py | 11 +++++++++-- testing/test_cacheprovider.py | 27 +++++++++++++++++++++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 changelog/4393.bugfix.rst diff --git a/changelog/4393.bugfix.rst b/changelog/4393.bugfix.rst new file mode 100644 index 000000000..4bde5aa43 --- /dev/null +++ b/changelog/4393.bugfix.rst @@ -0,0 +1 @@ +Do not create ``.gitignore``/``README.md`` files in existing cache directories. diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index d762d867d..5b0b32641 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -108,6 +108,10 @@ class Cache(object): """ path = self._getvaluepath(key) try: + if path.parent.is_dir(): + cache_dir_exists_already = True + else: + cache_dir_exists_already = self._cachedir.exists() path.parent.mkdir(exist_ok=True, parents=True) except (IOError, OSError): self.warn("could not create cache path {path}", path=path) @@ -119,6 +123,7 @@ class Cache(object): else: with f: json.dump(value, f, indent=2, sort_keys=True) + if not cache_dir_exists_already: self._ensure_supporting_files() def _ensure_supporting_files(self): @@ -128,8 +133,10 @@ class Cache(object): if not readme_path.is_file(): readme_path.write_text(README_CONTENT) - msg = u"# created by pytest automatically, do not change\n*" - self._cachedir.joinpath(".gitignore").write_text(msg, encoding="UTF-8") + gitignore_path = self._cachedir.joinpath(".gitignore") + if not gitignore_path.is_file(): + msg = u"# Created by pytest automatically.\n*" + gitignore_path.write_text(msg, encoding="UTF-8") class LFPlugin(object): diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 2b8ca2e18..3de53b59f 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -899,5 +899,28 @@ def test_gitignore(testdir): config = testdir.parseconfig() cache = Cache.for_config(config) cache.set("foo", "bar") - msg = "# created by pytest automatically, do not change\n*" - assert cache._cachedir.joinpath(".gitignore").read_text(encoding="UTF-8") == msg + msg = "# Created by pytest automatically.\n*" + gitignore_path = cache._cachedir.joinpath(".gitignore") + assert gitignore_path.read_text(encoding="UTF-8") == msg + + # Does not overwrite existing/custom one. + gitignore_path.write_text("") + cache.set("something", "else") + assert gitignore_path.read_text(encoding="UTF-8") == "" + + +def test_does_not_create_boilerplate_in_existing_dirs(testdir): + from _pytest.cacheprovider import Cache + + testdir.makeini( + """ + [pytest] + cache_dir = . + """ + ) + config = testdir.parseconfig() + cache = Cache.for_config(config) + cache.set("foo", "bar") + + assert not os.path.exists(".gitignore") + assert not os.path.exists("README.md") From 1568e38997ff6252e079c19e032c74abfe8b3f77 Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Mon, 12 Nov 2018 16:20:15 -0500 Subject: [PATCH 15/44] Use pkg_resources.parse_version in minver check Use pkg_resources.parse_version in minver check Add meself to AUTHORS & changelog Format CHANGELOG --- AUTHORS | 1 + changelog/4315.trivial.rst | 1 + src/_pytest/config/__init__.py | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 changelog/4315.trivial.rst diff --git a/AUTHORS b/AUTHORS index 777eda324..1316f7b8f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -158,6 +158,7 @@ Michael Droettboom Michael Seifert Michal Wajszczuk Mihai Capotă +Mike Hoyle (hoylemd) Mike Lundy Miro Hrončok Nathaniel Waisbrot diff --git a/changelog/4315.trivial.rst b/changelog/4315.trivial.rst new file mode 100644 index 000000000..ee7266aa0 --- /dev/null +++ b/changelog/4315.trivial.rst @@ -0,0 +1 @@ +Use ``pkg_resources.parse_version`` instead of ``LooseVersion`` in minversion check. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 0fc895546..7e24316ce 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -11,10 +11,10 @@ import shlex import sys import types import warnings -from distutils.version import LooseVersion import py import six +from pkg_resources import parse_version from pluggy import HookimplMarker from pluggy import HookspecMarker from pluggy import PluginManager @@ -822,7 +822,7 @@ class Config(object): minver = self.inicfg.get("minversion", None) if minver: - if LooseVersion(minver) > LooseVersion(pytest.__version__): + if parse_version(minver) > parse_version(pytest.__version__): raise pytest.UsageError( "%s:%d: requires pytest-%s, actual pytest-%s'" % ( From d52ea4b6cf1b7a552d52a78acfd3b3b7f642bcc6 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 19 Nov 2018 20:06:06 -0200 Subject: [PATCH 16/44] Use python 3 in 'doctesting' environment We some examples now use type annotations --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index dbfd4eef5..b9e12983b 100644 --- a/tox.ini +++ b/tox.ini @@ -141,7 +141,7 @@ commands = sphinx-build -W -b html . _build [testenv:doctesting] -basepython = python +basepython = python3 skipsdist = True deps = PyYAML From fc61bdd9073d86819eb54c8603384a10c626a9d4 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 20 Nov 2018 13:47:40 +0100 Subject: [PATCH 17/44] fix 4425: resolve --basetemp to absolute paths --- changelog/4425.bugfix.rst | 1 + src/_pytest/monkeypatch.py | 6 +++++- src/_pytest/tmpdir.py | 6 ++++-- testing/test_tmpdir.py | 33 ++++++++++++++++++++++++++++++--- 4 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 changelog/4425.bugfix.rst diff --git a/changelog/4425.bugfix.rst b/changelog/4425.bugfix.rst new file mode 100644 index 000000000..7c869cd4c --- /dev/null +++ b/changelog/4425.bugfix.rst @@ -0,0 +1 @@ +Ensure we resolve the absolute path when the given ``--basetemp`` is a relative path. diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index d536b7746..2c81de177 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -13,8 +13,9 @@ import six import pytest from _pytest.fixtures import fixture +from _pytest.pathlib import Path -RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$") +RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$") @fixture @@ -267,6 +268,9 @@ class MonkeyPatch(object): self._cwd = os.getcwd() if hasattr(path, "chdir"): path.chdir() + elif isinstance(path, Path): + # modern python uses the fspath protocol here LEGACY + os.chdir(str(path)) else: os.chdir(path) diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 6287c1705..81430e4f0 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -26,7 +26,9 @@ class TempPathFactory(object): The base directory can be configured using the ``--basetemp`` option.""" - _given_basetemp = attr.ib() + _given_basetemp = attr.ib( + convert=attr.converters.optional(lambda p: Path(p).resolve()) + ) _trace = attr.ib() _basetemp = attr.ib(default=None) @@ -53,7 +55,7 @@ class TempPathFactory(object): """ return base temporary directory. """ if self._basetemp is None: if self._given_basetemp is not None: - basetemp = Path(self._given_basetemp) + basetemp = self._given_basetemp ensure_reset_dir(basetemp) else: from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT") diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 38b0672b7..fddece2cf 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -4,6 +4,7 @@ from __future__ import print_function import sys +import attr import six import pytest @@ -25,12 +26,29 @@ def test_ensuretemp(recwarn): assert d1.check(dir=1) +@attr.s +class FakeConfig(object): + basetemp = attr.ib() + trace = attr.ib(default=None) + + @property + def trace(self): + return self + + def get(self, key): + return lambda *k: None + + @property + def option(self): + return self + + class TestTempdirHandler(object): - def test_mktemp(self, testdir): + def test_mktemp(self, tmp_path): + from _pytest.tmpdir import TempdirFactory, TempPathFactory - config = testdir.parseconfig() - config.option.basetemp = testdir.mkdir("hello") + config = FakeConfig(tmp_path) t = TempdirFactory(TempPathFactory.from_config(config)) tmp = t.mktemp("world") assert tmp.relto(t.getbasetemp()) == "world0" @@ -40,6 +58,15 @@ class TestTempdirHandler(object): assert tmp2.relto(t.getbasetemp()).startswith("this") assert tmp2 != tmp + @pytest.mark.issue(4425) + def test_tmppath_relative_basetemp_absolute(self, tmp_path, monkeypatch): + from _pytest.tmpdir import TempPathFactory + + monkeypatch.chdir(tmp_path) + config = FakeConfig("hello") + t = TempPathFactory.from_config(config) + assert t.getbasetemp() == (tmp_path / "hello") + class TestConfigTmpdir(object): def test_getbasetemp_custom_removes_old(self, testdir): From f180ab3e69438559df309e8eac563499bde4fba2 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 20 Nov 2018 20:08:01 -0200 Subject: [PATCH 18/44] Use os.path.abspath to get absolute path instead of Path.resolve() Unfortunately it seems there is a difference in resolve() behavior depending on the platform --- src/_pytest/tmpdir.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 81430e4f0..937d90c2f 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -27,7 +27,9 @@ class TempPathFactory(object): The base directory can be configured using the ``--basetemp`` option.""" _given_basetemp = attr.ib( - convert=attr.converters.optional(lambda p: Path(p).resolve()) + # using os.path.abspath() to get absolute path instead of resolve() as it + # does not work the same in all platforms + convert=attr.converters.optional(lambda p: Path(os.path.abspath(p))) ) _trace = attr.ib() _basetemp = attr.ib(default=None) From ee4f8c98a9c85773a5ed341c3d0b995ea8c60c68 Mon Sep 17 00:00:00 2001 From: Slam <3lnc.slam@gmail.com> Date: Wed, 21 Nov 2018 14:37:28 +0200 Subject: [PATCH 19/44] Adds note on multiple module marker usage --- doc/en/example/markers.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index cb6368a64..e0682c834 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -271,8 +271,12 @@ You can also set a module level marker:: import pytest pytestmark = pytest.mark.webtest -in which case it will be applied to all functions and -methods defined in the module. +or multiple markers:: + + pytestmark = [pytest.mark.webtest, pytest.mark.slowtest] + +in which case markers will be applied (in left-to-right order) to +all functions and methods defined in the module. .. _`marking individual tests when using parametrize`: From 4f5c153d29eb91a76e8f2aa519684e9710ef6624 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 21 Nov 2018 20:46:08 -0200 Subject: [PATCH 20/44] Fix call to os.path.abspath: the argument might already be a Path instance There's Path.absolute(), but it is not public, see https://bugs.python.org/issue25012. --- src/_pytest/tmpdir.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 937d90c2f..dadb196ea 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -10,6 +10,7 @@ import warnings import attr import py +import six import pytest from .pathlib import ensure_reset_dir @@ -28,8 +29,11 @@ class TempPathFactory(object): _given_basetemp = attr.ib( # using os.path.abspath() to get absolute path instead of resolve() as it - # does not work the same in all platforms - convert=attr.converters.optional(lambda p: Path(os.path.abspath(p))) + # does not work the same in all platforms; there's Path.absolute(), but it is not + # public (see https://bugs.python.org/issue25012) + convert=attr.converters.optional( + lambda p: Path(os.path.abspath(six.text_type(p))) + ) ) _trace = attr.ib() _basetemp = attr.ib(default=None) From f1fe9e41acf6e2099270a931c46cf390cdc6e348 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 21 Nov 2018 20:49:17 -0200 Subject: [PATCH 21/44] Mention PR# in the comment for future reference --- src/_pytest/tmpdir.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index dadb196ea..860c2d4af 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -29,8 +29,8 @@ class TempPathFactory(object): _given_basetemp = attr.ib( # using os.path.abspath() to get absolute path instead of resolve() as it - # does not work the same in all platforms; there's Path.absolute(), but it is not - # public (see https://bugs.python.org/issue25012) + # does not work the same in all platforms (see #4427) + # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012) convert=attr.converters.optional( lambda p: Path(os.path.abspath(six.text_type(p))) ) From b3700f61baca9920c421257d1cd8bb00c44d51bc Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 22 Nov 2018 00:15:14 -0800 Subject: [PATCH 22/44] Fix formatting of print() calls --- doc/en/capture.rst | 2 +- doc/en/example/markers.rst | 2 +- doc/en/example/special.rst | 16 +++++------ doc/en/fixture.rst | 16 +++++------ doc/en/funcarg_compare.rst | 4 +-- doc/en/getting-started.rst | 4 +-- doc/en/writing_plugins.rst | 2 +- extra/get_issues.py | 4 +-- testing/acceptance_test.py | 6 ++-- testing/python/fixture.py | 22 +++++++-------- testing/python/integration.py | 2 +- testing/python/raises.py | 2 +- testing/test_capture.py | 52 +++++++++++++++++------------------ testing/test_junitxml.py | 4 +-- testing/test_mark.py | 2 +- testing/test_nose.py | 20 +++++++------- testing/test_runner_xunit.py | 8 +++--- testing/test_terminal.py | 18 ++++++------ testing/test_unittest.py | 12 ++++---- 19 files changed, 99 insertions(+), 99 deletions(-) diff --git a/doc/en/capture.rst b/doc/en/capture.rst index ab86fb55f..87fcbc1a6 100644 --- a/doc/en/capture.rst +++ b/doc/en/capture.rst @@ -52,7 +52,7 @@ is that you can use print statements for debugging:: # content of test_module.py def setup_function(function): - print ("setting up %s" % function) + print("setting up %s" % function) def test_func1(): assert True diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index e0682c834..709aef0cd 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -466,7 +466,7 @@ test function. From a conftest file we can read it like this:: def pytest_runtest_setup(item): for mark in item.iter_markers(name='glob'): - print ("glob args=%s kwargs=%s" %(mark.args, mark.kwargs)) + print("glob args=%s kwargs=%s" % (mark.args, mark.kwargs)) sys.stdout.flush() Let's run this without capturing output and see what we get:: diff --git a/doc/en/example/special.rst b/doc/en/example/special.rst index 1fc32f6c8..524ae7883 100644 --- a/doc/en/example/special.rst +++ b/doc/en/example/special.rst @@ -13,7 +13,7 @@ calls it:: @pytest.fixture(scope="session", autouse=True) def callattr_ahead_of_alltests(request): - print ("callattr_ahead_of_alltests called") + print("callattr_ahead_of_alltests called") seen = set([None]) session = request.node for item in session.items: @@ -31,20 +31,20 @@ will be called ahead of running any tests:: class TestHello(object): @classmethod def callme(cls): - print ("callme called!") + print("callme called!") def test_method1(self): - print ("test_method1 called") + print("test_method1 called") def test_method2(self): - print ("test_method1 called") + print("test_method1 called") class TestOther(object): @classmethod def callme(cls): - print ("callme other called") + print("callme other called") def test_other(self): - print ("test other") + print("test other") # works with unittest as well ... import unittest @@ -52,10 +52,10 @@ will be called ahead of running any tests:: class SomeTest(unittest.TestCase): @classmethod def callme(self): - print ("SomeTest callme called") + print("SomeTest callme called") def test_unit1(self): - print ("test_unit1 method called") + print("test_unit1 method called") If you run this without output capturing:: diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 7d9af6fa8..1eed211b0 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -460,7 +460,7 @@ read an optional server URL from the test module which uses our fixture:: server = getattr(request.module, "smtpserver", "smtp.gmail.com") smtp_connection = smtplib.SMTP(server, 587, timeout=5) yield smtp_connection - print ("finalizing %s (%s)" % (smtp_connection, server)) + print("finalizing %s (%s)" % (smtp_connection, server)) smtp_connection.close() We use the ``request.module`` attribute to optionally obtain an @@ -821,23 +821,23 @@ to show the setup/teardown flow:: @pytest.fixture(scope="module", params=["mod1", "mod2"]) def modarg(request): param = request.param - print (" SETUP modarg %s" % param) + print(" SETUP modarg %s" % param) yield param - print (" TEARDOWN modarg %s" % param) + print(" TEARDOWN modarg %s" % param) @pytest.fixture(scope="function", params=[1,2]) def otherarg(request): param = request.param - print (" SETUP otherarg %s" % param) + print(" SETUP otherarg %s" % param) yield param - print (" TEARDOWN otherarg %s" % param) + print(" TEARDOWN otherarg %s" % param) def test_0(otherarg): - print (" RUN test0 with otherarg %s" % otherarg) + print(" RUN test0 with otherarg %s" % otherarg) def test_1(modarg): - print (" RUN test1 with modarg %s" % modarg) + print(" RUN test1 with modarg %s" % modarg) def test_2(otherarg, modarg): - print (" RUN test2 with otherarg %s and modarg %s" % (otherarg, modarg)) + print(" RUN test2 with otherarg %s and modarg %s" % (otherarg, modarg)) Let's run the tests in verbose mode and with looking at the print-output:: diff --git a/doc/en/funcarg_compare.rst b/doc/en/funcarg_compare.rst index 5403da2f2..4fa9b79ae 100644 --- a/doc/en/funcarg_compare.rst +++ b/doc/en/funcarg_compare.rst @@ -26,9 +26,9 @@ a per-session Database object:: # content of conftest.py class Database(object): def __init__(self): - print ("database instance created") + print("database instance created") def destroy(self): - print ("database instance destroyed") + print("database instance destroyed") def pytest_funcarg__db(request): return request.cached_setup(setup=DataBase, diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 418d4f8cd..56bb300c3 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -138,7 +138,7 @@ Request a unique temporary directory for functional tests # content of test_tmpdir.py def test_needsfiles(tmpdir): - print (tmpdir) + print(tmpdir) assert 0 List the name ``tmpdir`` in the test function signature and ``pytest`` will lookup and call a fixture factory to create the resource before performing the test function call. Before the test runs, ``pytest`` creates a unique-per-test-invocation temporary directory:: @@ -151,7 +151,7 @@ List the name ``tmpdir`` in the test function signature and ``pytest`` will look tmpdir = local('PYTEST_TMPDIR/test_needsfiles0') def test_needsfiles(tmpdir): - print (tmpdir) + print(tmpdir) > assert 0 E assert 0 diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 527a7263a..68ff4b831 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -73,7 +73,7 @@ sub directory but not for other directories:: a/conftest.py: def pytest_runtest_setup(item): # called for running each test in 'a' directory - print ("setting up", item) + print("setting up", item) a/test_sub.py: def test_sub(): diff --git a/extra/get_issues.py b/extra/get_issues.py index 25bfc3e9a..9407aeded 100644 --- a/extra/get_issues.py +++ b/extra/get_issues.py @@ -65,9 +65,9 @@ def report(issues): print(title) # print() # lines = body.split("\n") - # print ("\n".join(lines[:3])) + # print("\n".join(lines[:3])) # if len(lines) > 3 or len(body) > 240: - # print ("...") + # print("...") print("\n\nFound %s open issues" % len(issues)) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 37ec6d84d..b23cd7ca8 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -206,7 +206,7 @@ class TestGeneralUsage(object): testdir.makeconftest( """ import sys - print ("should not be seen") + print("should not be seen") sys.stderr.write("stder42\\n") """ ) @@ -218,7 +218,7 @@ class TestGeneralUsage(object): def test_conftest_printing_shows_if_error(self, testdir): testdir.makeconftest( """ - print ("should be seen") + print("should be seen") assert 0 """ ) @@ -301,7 +301,7 @@ class TestGeneralUsage(object): def pytest_generate_tests(metafunc): metafunc.addcall({'x': 3}, id='hello-123') def pytest_runtest_setup(item): - print (item.keywords) + print(item.keywords) if 'hello-123' in item.keywords: pytest.skip("hello") assert 0 diff --git a/testing/python/fixture.py b/testing/python/fixture.py index b483fff45..1008bd3d8 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1495,7 +1495,7 @@ class TestFixtureManagerParseFactories(object): return "class" def test_hello(self, item, fm): faclist = fm.getfixturedefs("hello", item.nodeid) - print (faclist) + print(faclist) assert len(faclist) == 3 assert faclist[0].func(item._request) == "conftest" @@ -2040,7 +2040,7 @@ class TestAutouseManagement(object): values.append("step2-%d" % item) def test_finish(): - print (values) + print(values) assert values == ["setup-1", "step1-1", "step2-1", "teardown-1", "setup-2", "step1-2", "step2-2", "teardown-2",] """ @@ -2880,7 +2880,7 @@ class TestFixtureMarker(object): def base(request, fix1): def cleanup_base(): values.append("fin_base") - print ("finalizing base") + print("finalizing base") request.addfinalizer(cleanup_base) def test_begin(): @@ -3480,13 +3480,13 @@ class TestContextManagerFixtureFuncs(object): from test_context import fixture @fixture def arg1(): - print ("setup") + print("setup") yield 1 - print ("teardown") + print("teardown") def test_1(arg1): - print ("test1", arg1) + print("test1", arg1) def test_2(arg1): - print ("test2", arg1) + print("test2", arg1) assert 0 """ ) @@ -3509,13 +3509,13 @@ class TestContextManagerFixtureFuncs(object): from test_context import fixture @fixture(scope="module") def arg1(): - print ("setup") + print("setup") yield 1 - print ("teardown") + print("teardown") def test_1(arg1): - print ("test1", arg1) + print("test1", arg1) def test_2(arg1): - print ("test2", arg1) + print("test2", arg1) """ ) result = testdir.runpytest("-s") diff --git a/testing/python/integration.py b/testing/python/integration.py index a6fb93bfb..79de048c3 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -283,7 +283,7 @@ class TestReRunTests(object): global count, req assert request != req req = request - print ("fix count %s" % count) + print("fix count %s" % count) count += 1 def test_fix(fix): pass diff --git a/testing/python/raises.py b/testing/python/raises.py index a72aeef63..3b94b05f5 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -43,7 +43,7 @@ class TestRaises(object): with pytest.raises(ZeroDivisionError) as excinfo: assert isinstance(excinfo, _pytest._code.ExceptionInfo) 1/0 - print (excinfo) + print(excinfo) assert excinfo.type == ZeroDivisionError assert isinstance(excinfo.value, ZeroDivisionError) diff --git a/testing/test_capture.py b/testing/test_capture.py index 39ddd1f79..47aba70d4 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -107,8 +107,8 @@ def test_capturing_unicode(testdir, method): # taken from issue 227 from nosetests def test_unicode(): import sys - print (sys.stdout) - print (%s) + print(sys.stdout) + print(%s) """ % obj ) @@ -121,7 +121,7 @@ def test_capturing_bytes_in_utf8_encoding(testdir, method): testdir.makepyfile( """ def test_unicode(): - print ('b\\u00f6y') + print('b\\u00f6y') """ ) result = testdir.runpytest("--capture=%s" % method) @@ -131,7 +131,7 @@ def test_capturing_bytes_in_utf8_encoding(testdir, method): def test_collect_capturing(testdir): p = testdir.makepyfile( """ - print ("collect %s failure" % 13) + print("collect %s failure" % 13) import xyz42123 """ ) @@ -144,14 +144,14 @@ class TestPerTestCapturing(object): p = testdir.makepyfile( """ def setup_module(mod): - print ("setup module") + print("setup module") def setup_function(function): - print ("setup " + function.__name__) + print("setup " + function.__name__) def test_func1(): - print ("in func1") + print("in func1") assert 0 def test_func2(): - print ("in func2") + print("in func2") assert 0 """ ) @@ -172,14 +172,14 @@ class TestPerTestCapturing(object): """ import sys def setup_module(func): - print ("module-setup") + print("module-setup") def setup_function(func): - print ("function-setup") + print("function-setup") def test_func(): - print ("in function") + print("in function") assert 0 def teardown_function(func): - print ("in teardown") + print("in teardown") """ ) result = testdir.runpytest(p) @@ -198,9 +198,9 @@ class TestPerTestCapturing(object): p = testdir.makepyfile( """ def test_func1(): - print ("in func1") + print("in func1") def test_func2(): - print ("in func2") + print("in func2") assert 0 """ ) @@ -213,12 +213,12 @@ class TestPerTestCapturing(object): p = testdir.makepyfile( """ def setup_function(function): - print ("setup func1") + print("setup func1") def teardown_function(function): - print ("teardown func1") + print("teardown func1") assert 0 def test_func1(): - print ("in func1") + print("in func1") pass """ ) @@ -238,7 +238,7 @@ class TestPerTestCapturing(object): p = testdir.makepyfile( """ def teardown_module(mod): - print ("teardown module") + print("teardown module") assert 0 def test_func(): pass @@ -259,10 +259,10 @@ class TestPerTestCapturing(object): """\ import sys def test_capturing(): - print (42) + print(42) sys.stderr.write(str(23)) def test_capturing_error(): - print (1) + print(1) sys.stderr.write(str(2)) raise ValueError """ @@ -392,7 +392,7 @@ class TestCaptureFixture(object): reprec = testdir.inline_runsource( """\ def test_hello(capsys): - print (42) + print(42) out, err = capsys.readouterr() assert out.startswith("42") """, @@ -460,7 +460,7 @@ class TestCaptureFixture(object): p = testdir.makepyfile( """\ def test_hello(cap{}): - print ("xxx42xxx") + print("xxx42xxx") assert 0 """.format( method @@ -702,7 +702,7 @@ def test_capture_conftest_runtest_setup(testdir): testdir.makeconftest( """ def pytest_runtest_setup(): - print ("hello19") + print("hello19") """ ) testdir.makepyfile("def test_func(): pass") @@ -737,7 +737,7 @@ def test_capture_early_option_parsing(testdir): testdir.makeconftest( """ def pytest_runtest_setup(): - print ("hello19") + print("hello19") """ ) testdir.makepyfile("def test_func(): pass") @@ -1302,14 +1302,14 @@ def test_capturing_and_logging_fundamentals(testdir, method): logging.warn("hello1") outerr = cap.readouterr() - print ("suspend, captured %%s" %%(outerr,)) + print("suspend, captured %%s" %%(outerr,)) logging.warn("hello2") cap.pop_outerr_to_orig() logging.warn("hello3") outerr = cap.readouterr() - print ("suspend2, captured %%s" %% (outerr,)) + print("suspend2, captured %%s" %% (outerr,)) """ % (method,) ) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index db308b688..c9dc39f82 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -371,7 +371,7 @@ class TestPython(object): import sys def test_fail(): - print ("hello-stdout") + print("hello-stdout") sys.stderr.write("hello-stderr\\n") logging.info('info msg') logging.warning('warning msg') @@ -589,7 +589,7 @@ class TestPython(object): """ # coding: latin1 def test_hello(): - print (%r) + print(%r) assert 0 """ % value diff --git a/testing/test_mark.py b/testing/test_mark.py index 1f50045c5..e32bcc395 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -189,7 +189,7 @@ def test_ini_markers(testdir): """ def test_markers(pytestconfig): markers = pytestconfig.getini("markers") - print (markers) + print(markers) assert len(markers) >= 2 assert markers[0].startswith("a1:") assert markers[1].startswith("a2:") diff --git a/testing/test_nose.py b/testing/test_nose.py index 0af591b36..e4db46802 100644 --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -71,11 +71,11 @@ def test_nose_setup_func(testdir): @with_setup(my_setup, my_teardown) def test_hello(): - print (values) + print(values) assert values == [1] def test_world(): - print (values) + print(values) assert values == [1,2] """ @@ -95,11 +95,11 @@ def test_nose_setup_func_failure(testdir): @with_setup(my_setup, my_teardown) def test_hello(): - print (values) + print(values) assert values == [1] def test_world(): - print (values) + print(values) assert values == [1,2] """ @@ -147,11 +147,11 @@ def test_nose_setup_partial(testdir): my_teardown_partial = partial(my_teardown, 2) def test_hello(): - print (values) + print(values) assert values == [1] def test_world(): - print (values) + print(values) assert values == [1,2] test_hello.setup = my_setup_partial @@ -202,21 +202,21 @@ def test_nose_test_generator_fixtures(testdir): class TestClass(object): def setup(self): - print ("setup called in %s" % self) + print("setup called in %s" % self) self.called = ['setup'] def teardown(self): - print ("teardown called in %s" % self) + print("teardown called in %s" % self) eq_(self.called, ['setup']) self.called.append('teardown') def test(self): - print ("test called in %s" % self) + print("test called in %s" % self) for i in range(0, 5): yield self.check, i def check(self, i): - print ("check called in %s" % self) + print("check called in %s" % self) expect = ['setup'] #for x in range(0, i): # expect.append('setup') diff --git a/testing/test_runner_xunit.py b/testing/test_runner_xunit.py index 31937c919..b0844dc1c 100644 --- a/testing/test_runner_xunit.py +++ b/testing/test_runner_xunit.py @@ -202,21 +202,21 @@ def test_func_generator_setup(testdir): import sys def setup_module(mod): - print ("setup_module") + print("setup_module") mod.x = [] def setup_function(fun): - print ("setup_function") + print("setup_function") x.append(1) def teardown_function(fun): - print ("teardown_function") + print("teardown_function") x.pop() def test_one(): assert x == [1] def check(): - print ("check") + print("check") sys.stderr.write("e\\n") assert x == [1] yield check diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 9c2f93ed1..a028f9c22 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -365,7 +365,7 @@ class TestFixtureReporting(object): testdir.makepyfile( """ def setup_function(function): - print ("setup func") + print("setup func") assert 0 def test_nada(): pass @@ -389,7 +389,7 @@ class TestFixtureReporting(object): def test_nada(): pass def teardown_function(function): - print ("teardown func") + print("teardown func") assert 0 """ ) @@ -412,7 +412,7 @@ class TestFixtureReporting(object): assert 0, "failingfunc" def teardown_function(function): - print ("teardown func") + print("teardown func") assert False """ ) @@ -436,13 +436,13 @@ class TestFixtureReporting(object): testdir.makepyfile( """ def setup_function(function): - print ("setup func") + print("setup func") def test_fail(): assert 0, "failingfunc" def teardown_function(function): - print ("teardown func") + print("teardown func") """ ) result = testdir.runpytest() @@ -854,7 +854,7 @@ class TestGenericReporting(object): def g(): raise IndexError def test_func(): - print (6*7) + print(6*7) g() # --calling-- """ ) @@ -863,9 +863,9 @@ class TestGenericReporting(object): result = testdir.runpytest("--tb=%s" % tbopt) s = result.stdout.str() if tbopt == "long": - assert "print (6*7)" in s + assert "print(6*7)" in s else: - assert "print (6*7)" not in s + assert "print(6*7)" not in s if tbopt != "no": assert "--calling--" in s assert "IndexError" in s @@ -881,7 +881,7 @@ class TestGenericReporting(object): def g(): raise IndexError def test_func1(): - print (6*7) + print(6*7) g() # --calling-- def test_func2(): assert 0, "hello" diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 28ce90a3d..2c60cd271 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -244,7 +244,7 @@ def test_setup_failure_is_shown(testdir): def setUp(self): assert 0, "down1" def test_method(self): - print ("never42") + print("never42") xyz """ ) @@ -610,14 +610,14 @@ def test_djangolike_testcase(testdir): class DjangoLikeTestCase(TestCase): def setUp(self): - print ("setUp()") + print("setUp()") def test_presetup_has_been_run(self): - print ("test_thing()") + print("test_thing()") self.assertTrue(hasattr(self, 'was_presetup')) def tearDown(self): - print ("tearDown()") + print("tearDown()") def __call__(self, result=None): try: @@ -639,11 +639,11 @@ def test_djangolike_testcase(testdir): return def _pre_setup(self): - print ("_pre_setup()") + print("_pre_setup()") self.was_presetup = True def _post_teardown(self): - print ("_post_teardown()") + print("_post_teardown()") """ ) result = testdir.runpytest("-s") From 664257c7a3917c88375db6c44af5ea79ccb115ed Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 22 Nov 2018 00:20:13 -0800 Subject: [PATCH 23/44] Color the setup ERROR red --- src/_pytest/terminal.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index f19325088..94ad32e0f 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -786,8 +786,7 @@ class TerminalReporter(object): self.write_line(line) else: msg = self._getfailureheadline(rep) - markup = {"red": True, "bold": True} - self.write_sep("_", msg, **markup) + self.write_sep("_", msg, red=True, bold=True) self._outrep_summary(rep) for report in self.getreports(""): if report.nodeid == rep.nodeid and report.when == "teardown": @@ -808,7 +807,7 @@ class TerminalReporter(object): msg = "ERROR at setup of " + msg elif rep.when == "teardown": msg = "ERROR at teardown of " + msg - self.write_sep("_", msg) + self.write_sep("_", msg, red=True, bold=True) self._outrep_summary(rep) def _outrep_summary(self, rep): From 5f1d69207260297738ff4dd96687fe049d2963a3 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 22 Nov 2018 16:10:12 +0100 Subject: [PATCH 24/44] use Path.resolve in test to sort out osx temporary folder being a symlink --- testing/test_tmpdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index fddece2cf..6040d9444 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -65,7 +65,7 @@ class TestTempdirHandler(object): monkeypatch.chdir(tmp_path) config = FakeConfig("hello") t = TempPathFactory.from_config(config) - assert t.getbasetemp() == (tmp_path / "hello") + assert t.getbasetemp().resolve() == (tmp_path / "hello").resolve() class TestConfigTmpdir(object): From aa765cf8c243dec9502ecf3379b2e6ed26c274d7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 22 Nov 2018 14:44:01 -0200 Subject: [PATCH 25/44] Adjust stacklevel of "config" warnings Related to #4439 --- src/_pytest/assertion/rewrite.py | 1 + src/_pytest/cacheprovider.py | 4 +++- src/_pytest/config/__init__.py | 2 +- src/_pytest/config/findpaths.py | 5 ++++- src/_pytest/resultlog.py | 2 +- src/_pytest/warnings.py | 5 +++-- testing/test_warnings.py | 2 +- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 7b9aa5006..d1231b774 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -270,6 +270,7 @@ class AssertionRewritingHook(object): _issue_config_warning( PytestWarning("Module already imported so cannot be rewritten: %s" % name), self.config, + stacklevel=5, ) def load_module(self, name): diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index d762d867d..4e51af771 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -56,7 +56,9 @@ class Cache(object): from _pytest.warning_types import PytestWarning _issue_config_warning( - PytestWarning(fmt.format(**args) if args else fmt), self._config + PytestWarning(fmt.format(**args) if args else fmt), + self._config, + stacklevel=3, ) def makedir(self, name): diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 26a0973ca..562b50c38 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -191,7 +191,7 @@ def _prepareconfig(args=None, plugins=None): if warning: from _pytest.warnings import _issue_config_warning - _issue_config_warning(warning, config=config) + _issue_config_warning(warning, config=config, stacklevel=4) return pluginmanager.hook.pytest_cmdline_parse( pluginmanager=pluginmanager, args=args ) diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 4f371ec7f..a9d674e77 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -42,6 +42,7 @@ def getcfg(args, config=None): CFG_PYTEST_SECTION.format(filename=inibasename) ), config=config, + stacklevel=2, ) return base, p, iniconfig["pytest"] if ( @@ -116,7 +117,9 @@ def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None): # TODO: [pytest] section in *.cfg files is deprecated. Need refactoring once # the deprecation expires. _issue_config_warning( - CFG_PYTEST_SECTION.format(filename=str(inifile)), config + CFG_PYTEST_SECTION.format(filename=str(inifile)), + config, + stacklevel=2, ) break except KeyError: diff --git a/src/_pytest/resultlog.py b/src/_pytest/resultlog.py index 3efdbea6e..ab2d0f98b 100644 --- a/src/_pytest/resultlog.py +++ b/src/_pytest/resultlog.py @@ -36,7 +36,7 @@ def pytest_configure(config): from _pytest.deprecated import RESULT_LOG from _pytest.warnings import _issue_config_warning - _issue_config_warning(RESULT_LOG, config) + _issue_config_warning(RESULT_LOG, config, stacklevel=2) def pytest_unconfigure(config): diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 9de9d01d5..e3e206933 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -160,7 +160,7 @@ def pytest_terminal_summary(terminalreporter): yield -def _issue_config_warning(warning, config): +def _issue_config_warning(warning, config, stacklevel): """ This function should be used instead of calling ``warnings.warn`` directly when we are in the "configure" stage: at this point the actual options might not have been set, so we manually trigger the pytest_warning_captured @@ -168,10 +168,11 @@ def _issue_config_warning(warning, config): :param warning: the warning instance. :param config: + :param stacklevel: stacklevel forwarded to warnings.warn """ with warnings.catch_warnings(record=True) as records: warnings.simplefilter("always", type(warning)) - warnings.warn(warning, stacklevel=2) + warnings.warn(warning, stacklevel=stacklevel) config.hook.pytest_warning_captured.call_historic( kwargs=dict(warning_message=records[0], when="config", item=None) ) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index d79e956e3..53d9c71cd 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -310,7 +310,7 @@ def test_warning_captured_hook(testdir): """ from _pytest.warnings import _issue_config_warning def pytest_configure(config): - _issue_config_warning(UserWarning("config warning"), config) + _issue_config_warning(UserWarning("config warning"), config, stacklevel=2) """ ) testdir.makepyfile( From d471ecc4d8f0fea5819374bc75b2f2b7c3860c69 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 22 Nov 2018 14:45:50 -0200 Subject: [PATCH 26/44] Add changelog entry --- changelog/4440.trivial.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/4440.trivial.rst diff --git a/changelog/4440.trivial.rst b/changelog/4440.trivial.rst new file mode 100644 index 000000000..7187d664f --- /dev/null +++ b/changelog/4440.trivial.rst @@ -0,0 +1 @@ +Adjust the stack level of some internal pytest warnings. From b71bd9b300b6dbab25adcc4a0d9db14763011e72 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 22 Nov 2018 20:43:58 +0100 Subject: [PATCH 27/44] fix #4386 - handle uninitialized exceptioninfo in repr/str --- changelog/4386.bugfix.rst | 1 + src/_pytest/_code/code.py | 15 +++++++++++---- testing/python/raises.py | 12 ++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 changelog/4386.bugfix.rst diff --git a/changelog/4386.bugfix.rst b/changelog/4386.bugfix.rst new file mode 100644 index 000000000..0367f3ddc --- /dev/null +++ b/changelog/4386.bugfix.rst @@ -0,0 +1 @@ +Handle uninitialized exceptioninfo in repr/str. diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index d06e24f00..26973c4e1 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -425,7 +425,10 @@ class ExceptionInfo(object): self.traceback = _pytest._code.Traceback(self.tb, excinfo=ref(self)) def __repr__(self): - return "" % (self.typename, len(self.traceback)) + try: + return "" % (self.typename, len(self.traceback)) + except AttributeError: + return "" def exconly(self, tryshort=False): """ return the exception as a string @@ -513,9 +516,13 @@ class ExceptionInfo(object): return fmt.repr_excinfo(self) def __str__(self): - entry = self.traceback[-1] - loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) - return str(loc) + try: + entry = self.traceback[-1] + except AttributeError: + return repr(self) + else: + loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) + return str(loc) def __unicode__(self): entry = self.traceback[-1] diff --git a/testing/python/raises.py b/testing/python/raises.py index a72aeef63..527d688a2 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -33,6 +33,18 @@ class TestRaises(object): except pytest.raises.Exception: pass + def test_raises_repr_inflight(self): + with pytest.raises(RuntimeError) as excinfo: + # this test prints the inflight uninitialized object + # using repr and str as well as pprint to demonstrate + # it works + print(str(excinfo)) + print(repr(excinfo)) + import pprint + + pprint.pprint(excinfo) + raise RuntimeError(1) + def test_raises_as_contextmanager(self, testdir): testdir.makepyfile( """ From 88bf01a31e6625edec134fc1d49ec882947fb680 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 22 Nov 2018 12:20:14 +0100 Subject: [PATCH 28/44] fix #4386 - restructure construction and partial state of ExceptionInfo --- changelog/4386.feature.rst | 1 + src/_pytest/_code/code.py | 91 ++++++++++++++++++++++++++--------- src/_pytest/assertion/util.py | 2 +- src/_pytest/main.py | 4 +- src/_pytest/python.py | 4 +- src/_pytest/python_api.py | 6 +-- src/_pytest/runner.py | 4 +- src/_pytest/unittest.py | 6 ++- testing/code/test_code.py | 4 +- testing/code/test_excinfo.py | 28 ++++++----- testing/test_resultlog.py | 2 +- testing/test_runner.py | 20 +++----- 12 files changed, 113 insertions(+), 59 deletions(-) create mode 100644 changelog/4386.feature.rst diff --git a/changelog/4386.feature.rst b/changelog/4386.feature.rst new file mode 100644 index 000000000..fe827cc23 --- /dev/null +++ b/changelog/4386.feature.rst @@ -0,0 +1 @@ +Restructure ExceptionInfo object construction and ensure incomplete instances have a ``repr``/``str``. diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index d06e24f00..595fee6d3 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -391,40 +391,85 @@ co_equal = compile( ) +@attr.s(repr=False) class ExceptionInfo(object): """ wraps sys.exc_info() objects and offers help for navigating the traceback. """ - _striptext = "" _assert_start_repr = ( "AssertionError(u'assert " if _PY2 else "AssertionError('assert " ) - def __init__(self, tup=None, exprinfo=None): - import _pytest._code + _excinfo = attr.ib() + _striptext = attr.ib(default="") + _traceback = attr.ib(default=None) - if tup is None: - tup = sys.exc_info() - if exprinfo is None and isinstance(tup[1], AssertionError): - exprinfo = getattr(tup[1], "msg", None) - if exprinfo is None: - exprinfo = py.io.saferepr(tup[1]) - if exprinfo and exprinfo.startswith(self._assert_start_repr): - self._striptext = "AssertionError: " - self._excinfo = tup - #: the exception class - self.type = tup[0] - #: the exception instance - self.value = tup[1] - #: the exception raw traceback - self.tb = tup[2] - #: the exception type name - self.typename = self.type.__name__ - #: the exception traceback (_pytest._code.Traceback instance) - self.traceback = _pytest._code.Traceback(self.tb, excinfo=ref(self)) + @classmethod + def from_current(cls, exprinfo=None): + """returns a exceptioninfo matching the current traceback + + .. warning:: + + experimental api + + + :param exprinfo: an text string helping to determine if we should + strip assertionerror from the output, defaults + to the exception message/__str__() + + """ + tup = sys.exc_info() + _striptext = "" + if exprinfo is None and isinstance(tup[1], AssertionError): + exprinfo = getattr(tup[1], "msg", None) + if exprinfo is None: + exprinfo = py.io.saferepr(tup[1]) + if exprinfo and exprinfo.startswith(cls._assert_start_repr): + _striptext = "AssertionError: " + + return cls(tup, _striptext) + + @classmethod + def for_later(cls): + """return an unfilled ExceptionInfo + """ + return cls(None) + + @property + def type(self): + """the exception class""" + return self._excinfo[0] + + @property + def value(self): + """the exception value""" + return self._excinfo[1] + + @property + def tb(self): + """the exception raw traceback""" + return self._excinfo[2] + + @property + def typename(self): + """the type name of the exception""" + return self.type.__name__ + + @property + def traceback(self): + """the traceback""" + if self._traceback is None: + self._traceback = Traceback(self.tb, excinfo=ref(self)) + return self._traceback + + @traceback.setter + def traceback(self, value): + self._traceback = value def __repr__(self): + if self._excinfo is None: + return "" return "" % (self.typename, len(self.traceback)) def exconly(self, tryshort=False): @@ -513,6 +558,8 @@ class ExceptionInfo(object): return fmt.repr_excinfo(self) def __str__(self): + if self._excinfo is None: + return repr(self) entry = self.traceback[-1] loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) return str(loc) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 451e45495..15561f293 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -155,7 +155,7 @@ def assertrepr_compare(config, op, left, right): explanation = [ u"(pytest_assertion plugin: representation of details failed. " u"Probably an object has a faulty __repr__.)", - six.text_type(_pytest._code.ExceptionInfo()), + six.text_type(_pytest._code.ExceptionInfo.from_current()), ] if not explanation: diff --git a/src/_pytest/main.py b/src/_pytest/main.py index df4f1c8fb..851b08ae3 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -188,7 +188,7 @@ def wrap_session(config, doit): except Failed: session.exitstatus = EXIT_TESTSFAILED except KeyboardInterrupt: - excinfo = _pytest._code.ExceptionInfo() + excinfo = _pytest._code.ExceptionInfo.from_current() exitstatus = EXIT_INTERRUPTED if initstate <= 2 and isinstance(excinfo.value, exit.Exception): sys.stderr.write("{}: {}\n".format(excinfo.typename, excinfo.value.msg)) @@ -197,7 +197,7 @@ def wrap_session(config, doit): config.hook.pytest_keyboard_interrupt(excinfo=excinfo) session.exitstatus = exitstatus except: # noqa - excinfo = _pytest._code.ExceptionInfo() + excinfo = _pytest._code.ExceptionInfo.from_current() config.notify_exception(excinfo, config.option) session.exitstatus = EXIT_INTERNALERROR if excinfo.errisinstance(SystemExit): diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 8912ca060..8c8de8e75 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -450,7 +450,7 @@ class Module(nodes.File, PyCollector): mod = self.fspath.pyimport(ensuresyspath=importmode) except SyntaxError: raise self.CollectError( - _pytest._code.ExceptionInfo().getrepr(style="short") + _pytest._code.ExceptionInfo.from_current().getrepr(style="short") ) except self.fspath.ImportMismatchError: e = sys.exc_info()[1] @@ -466,7 +466,7 @@ class Module(nodes.File, PyCollector): except ImportError: from _pytest._code.code import ExceptionInfo - exc_info = ExceptionInfo() + exc_info = ExceptionInfo.from_current() if self.config.getoption("verbose") < 2: exc_info.traceback = exc_info.traceback.filter(filter_traceback) exc_repr = ( diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 805cd85ad..f895fb8a8 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -684,13 +684,13 @@ def raises(expected_exception, *args, **kwargs): # XXX didn't mean f_globals == f_locals something special? # this is destroyed here ... except expected_exception: - return _pytest._code.ExceptionInfo() + return _pytest._code.ExceptionInfo.from_current() else: func = args[0] try: func(*args[1:], **kwargs) except expected_exception: - return _pytest._code.ExceptionInfo() + return _pytest._code.ExceptionInfo.from_current() fail(message) @@ -705,7 +705,7 @@ class RaisesContext(object): self.excinfo = None def __enter__(self): - self.excinfo = object.__new__(_pytest._code.ExceptionInfo) + self.excinfo = _pytest._code.ExceptionInfo.for_later() return self.excinfo def __exit__(self, *tp): diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 86298a7aa..9ea1a07cd 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -211,12 +211,12 @@ class CallInfo(object): self.result = func() except KeyboardInterrupt: if treat_keyboard_interrupt_as_exception: - self.excinfo = ExceptionInfo() + self.excinfo = ExceptionInfo.from_current() else: self.stop = time() raise except: # noqa - self.excinfo = ExceptionInfo() + self.excinfo = ExceptionInfo.from_current() self.stop = time() def __repr__(self): diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index a38a60d8e..d9881cd87 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -115,6 +115,10 @@ class TestCaseFunction(Function): rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo) try: excinfo = _pytest._code.ExceptionInfo(rawexcinfo) + # invoke the attributes to trigger storing the traceback + # trial causes some issue there + excinfo.value + excinfo.traceback except TypeError: try: try: @@ -136,7 +140,7 @@ class TestCaseFunction(Function): except KeyboardInterrupt: raise except fail.Exception: - excinfo = _pytest._code.ExceptionInfo() + excinfo = _pytest._code.ExceptionInfo.from_current() self.__dict__.setdefault("_excinfo", []).append(excinfo) def addError(self, testcase, rawexcinfo): diff --git a/testing/code/test_code.py b/testing/code/test_code.py index 20ca0bfce..df9f109ef 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -169,7 +169,7 @@ class TestExceptionInfo(object): else: assert False except AssertionError: - exci = _pytest._code.ExceptionInfo() + exci = _pytest._code.ExceptionInfo.from_current() assert exci.getrepr() @@ -181,7 +181,7 @@ class TestTracebackEntry(object): else: assert False except AssertionError: - exci = _pytest._code.ExceptionInfo() + exci = _pytest._code.ExceptionInfo.from_current() entry = exci.traceback[0] source = entry.getsource() assert len(source) == 6 diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index c8f4c904d..b4d64313c 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -71,7 +71,7 @@ def test_excinfo_simple(): try: raise ValueError except ValueError: - info = _pytest._code.ExceptionInfo() + info = _pytest._code.ExceptionInfo.from_current() assert info.type == ValueError @@ -85,7 +85,7 @@ def test_excinfo_getstatement(): try: f() except ValueError: - excinfo = _pytest._code.ExceptionInfo() + excinfo = _pytest._code.ExceptionInfo.from_current() linenumbers = [ _pytest._code.getrawcode(f).co_firstlineno - 1 + 4, _pytest._code.getrawcode(f).co_firstlineno - 1 + 1, @@ -126,7 +126,7 @@ class TestTraceback_f_g_h(object): try: h() except ValueError: - self.excinfo = _pytest._code.ExceptionInfo() + self.excinfo = _pytest._code.ExceptionInfo.from_current() def test_traceback_entries(self): tb = self.excinfo.traceback @@ -163,7 +163,7 @@ class TestTraceback_f_g_h(object): try: exec(source.compile()) except NameError: - tb = _pytest._code.ExceptionInfo().traceback + tb = _pytest._code.ExceptionInfo.from_current().traceback print(tb[-1].getsource()) s = str(tb[-1].getsource()) assert s.startswith("def xyz():\n try:") @@ -356,6 +356,12 @@ def test_excinfo_str(): assert len(s.split(":")) >= 3 # on windows it's 4 +def test_excinfo_for_later(): + e = ExceptionInfo.for_later() + assert "for raises" in repr(e) + assert "for raises" in str(e) + + def test_excinfo_errisinstance(): excinfo = pytest.raises(ValueError, h) assert excinfo.errisinstance(ValueError) @@ -365,7 +371,7 @@ def test_excinfo_no_sourcecode(): try: exec("raise ValueError()") except ValueError: - excinfo = _pytest._code.ExceptionInfo() + excinfo = _pytest._code.ExceptionInfo.from_current() s = str(excinfo.traceback[-1]) assert s == " File '':1 in \n ???\n" @@ -390,7 +396,7 @@ def test_entrysource_Queue_example(): try: queue.Queue().get(timeout=0.001) except queue.Empty: - excinfo = _pytest._code.ExceptionInfo() + excinfo = _pytest._code.ExceptionInfo.from_current() entry = excinfo.traceback[-1] source = entry.getsource() assert source is not None @@ -402,7 +408,7 @@ def test_codepath_Queue_example(): try: queue.Queue().get(timeout=0.001) except queue.Empty: - excinfo = _pytest._code.ExceptionInfo() + excinfo = _pytest._code.ExceptionInfo.from_current() entry = excinfo.traceback[-1] path = entry.path assert isinstance(path, py.path.local) @@ -453,7 +459,7 @@ class TestFormattedExcinfo(object): except KeyboardInterrupt: raise except: # noqa - return _pytest._code.ExceptionInfo() + return _pytest._code.ExceptionInfo.from_current() assert 0, "did not raise" def test_repr_source(self): @@ -491,7 +497,7 @@ class TestFormattedExcinfo(object): try: exec(co) except ValueError: - excinfo = _pytest._code.ExceptionInfo() + excinfo = _pytest._code.ExceptionInfo.from_current() repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" if sys.version_info[0] >= 3: @@ -510,7 +516,7 @@ raise ValueError() try: exec(co) except ValueError: - excinfo = _pytest._code.ExceptionInfo() + excinfo = _pytest._code.ExceptionInfo.from_current() repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" if sys.version_info[0] >= 3: @@ -1340,7 +1346,7 @@ def test_repr_traceback_with_unicode(style, encoding): try: raise RuntimeError(msg) except RuntimeError: - e_info = ExceptionInfo() + e_info = ExceptionInfo.from_current() formatter = FormattedExcinfo(style=style) repr_traceback = formatter.repr_traceback(e_info) assert repr_traceback is not None diff --git a/testing/test_resultlog.py b/testing/test_resultlog.py index 36f584e57..cb7b0cd3c 100644 --- a/testing/test_resultlog.py +++ b/testing/test_resultlog.py @@ -151,7 +151,7 @@ class TestWithFunctionIntegration(object): try: raise ValueError except ValueError: - excinfo = _pytest._code.ExceptionInfo() + excinfo = _pytest._code.ExceptionInfo.from_current() reslog = ResultLog(None, py.io.TextIO()) reslog.pytest_internalerror(excinfo.getrepr(style=style)) entry = reslog.logfile.getvalue() diff --git a/testing/test_runner.py b/testing/test_runner.py index c081920a5..2d047af70 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -561,20 +561,16 @@ def test_outcomeexception_passes_except_Exception(): def test_pytest_exit(): - try: + with pytest.raises(pytest.exit.Exception) as excinfo: pytest.exit("hello") - except pytest.exit.Exception: - excinfo = _pytest._code.ExceptionInfo() - assert excinfo.errisinstance(KeyboardInterrupt) + assert excinfo.errisinstance(KeyboardInterrupt) def test_pytest_fail(): - try: + with pytest.raises(pytest.fail.Exception) as excinfo: pytest.fail("hello") - except pytest.fail.Exception: - excinfo = _pytest._code.ExceptionInfo() - s = excinfo.exconly(tryshort=True) - assert s.startswith("Failed") + s = excinfo.exconly(tryshort=True) + assert s.startswith("Failed") def test_pytest_exit_msg(testdir): @@ -683,7 +679,7 @@ def test_exception_printing_skip(): try: pytest.skip("hello") except pytest.skip.Exception: - excinfo = _pytest._code.ExceptionInfo() + excinfo = _pytest._code.ExceptionInfo.from_current() s = excinfo.exconly(tryshort=True) assert s.startswith("Skipped") @@ -718,7 +714,7 @@ def test_importorskip(monkeypatch): mod2 = pytest.importorskip("hello123", minversion="1.3") assert mod2 == mod except pytest.skip.Exception: - print(_pytest._code.ExceptionInfo()) + print(_pytest._code.ExceptionInfo.from_current()) pytest.fail("spurious skip") @@ -740,7 +736,7 @@ def test_importorskip_dev_module(monkeypatch): pytest.importorskip('mockmodule1', minversion='0.14.0')""", ) except pytest.skip.Exception: - print(_pytest._code.ExceptionInfo()) + print(_pytest._code.ExceptionInfo.from_current()) pytest.fail("spurious skip") From 2eaf3db6aeda21c060b18531c74776ffb41d33fc Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 22 Nov 2018 20:21:22 -0200 Subject: [PATCH 29/44] Fix docstring indentation (docs env) --- src/_pytest/_code/code.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 595fee6d3..1b49fe75b 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -407,17 +407,16 @@ class ExceptionInfo(object): @classmethod def from_current(cls, exprinfo=None): - """returns a exceptioninfo matching the current traceback + """returns an ExceptionInfo matching the current traceback .. warning:: - experimental api + Experimental API - :param exprinfo: an text string helping to determine if we should - strip assertionerror from the output, defaults - to the exception message/__str__() - + :param exprinfo: a text string helping to determine if we should + strip ``AssertionError`` from the output, defaults + to the exception message/``__str__()`` """ tup = sys.exc_info() _striptext = "" From 9ae8429a21ed83bb668c94023e933fa4112d3831 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 22 Nov 2018 20:24:46 -0200 Subject: [PATCH 30/44] Use a more specific exception type in test_raises_repr_inflight As requested during review --- testing/python/raises.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/testing/python/raises.py b/testing/python/raises.py index 527d688a2..f2c44fcf8 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -34,7 +34,12 @@ class TestRaises(object): pass def test_raises_repr_inflight(self): - with pytest.raises(RuntimeError) as excinfo: + """Ensure repr() on an exception info inside a pytest.raises with block works (#4386)""" + + class E(Exception): + pass + + with pytest.raises(E) as excinfo: # this test prints the inflight uninitialized object # using repr and str as well as pprint to demonstrate # it works @@ -43,7 +48,7 @@ class TestRaises(object): import pprint pprint.pprint(excinfo) - raise RuntimeError(1) + raise E() def test_raises_as_contextmanager(self, testdir): testdir.makepyfile( From c5c728c8bcbc07268874c8944ee956fae9c1350c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 22 Nov 2018 21:38:33 -0200 Subject: [PATCH 31/44] Fix test/improve tests a bit in py27 --- testing/test_cacheprovider.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 3de53b59f..30fe23aeb 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -904,9 +904,9 @@ def test_gitignore(testdir): assert gitignore_path.read_text(encoding="UTF-8") == msg # Does not overwrite existing/custom one. - gitignore_path.write_text("") + gitignore_path.write_text(u"custom") cache.set("something", "else") - assert gitignore_path.read_text(encoding="UTF-8") == "" + assert gitignore_path.read_text(encoding="UTF-8") == "custom" def test_does_not_create_boilerplate_in_existing_dirs(testdir): @@ -922,5 +922,6 @@ def test_does_not_create_boilerplate_in_existing_dirs(testdir): cache = Cache.for_config(config) cache.set("foo", "bar") + assert os.path.isdir("v") # cache contents assert not os.path.exists(".gitignore") assert not os.path.exists("README.md") From 0cf45ee18a48061163cfd82c1edf1d6292d2a2a4 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 15 Nov 2018 17:05:56 +0100 Subject: [PATCH 32/44] Display "short test summary info" after (main) warnings again Fixes https://github.com/pytest-dev/pytest/issues/3952. --- changelog/3952.bugfix.rst | 1 + src/_pytest/terminal.py | 16 +++++++++++++--- testing/test_terminal.py | 20 ++++++++++++++++++-- 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 changelog/3952.bugfix.rst diff --git a/changelog/3952.bugfix.rst b/changelog/3952.bugfix.rst new file mode 100644 index 000000000..e999cdded --- /dev/null +++ b/changelog/3952.bugfix.rst @@ -0,0 +1 @@ +Display warnings before "short test summary info" again, but still later warnings in the end. diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 94ad32e0f..3c9abac8a 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -647,9 +647,11 @@ class TerminalReporter(object): def pytest_terminal_summary(self): self.summary_errors() self.summary_failures() - yield self.summary_warnings() + yield self.summary_passes() + # Display any extra warnings from teardown here (if any). + self.summary_warnings() def pytest_keyboard_interrupt(self, excinfo): self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True) @@ -726,11 +728,19 @@ class TerminalReporter(object): if not all_warnings: return + final = hasattr(self, "_already_displayed_warnings") + if final: + warnings = all_warnings[self._already_displayed_warnings :] + else: + warnings = all_warnings + self._already_displayed_warnings = len(warnings) + grouped = itertools.groupby( - all_warnings, key=lambda wr: wr.get_location(self.config) + warnings, key=lambda wr: wr.get_location(self.config) ) - self.write_sep("=", "warnings summary", yellow=True, bold=False) + title = "warnings summary (final)" if final else "warnings summary" + self.write_sep("=", title, yellow=True, bold=False) for location, warning_records in grouped: # legacy warnings show their location explicitly, while standard warnings look better without # it because the location is already formatted into the message diff --git a/testing/test_terminal.py b/testing/test_terminal.py index a028f9c22..f1bfa81f2 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1074,11 +1074,27 @@ def test_terminal_summary_warnings_are_displayed(testdir): warnings.warn(UserWarning('internal warning')) """ ) - result = testdir.runpytest() + testdir.makepyfile( + """ + def test_failure(): + import warnings + warnings.warn("warning_from_" + "test") + assert 0 + """ + ) + result = testdir.runpytest("-ra") result.stdout.fnmatch_lines( - ["*conftest.py:3:*internal warning", "*== 1 warnings in *"] + [ + "*= warnings summary =*", + "*warning_from_test*", + "*= short test summary info =*", + "*= warnings summary (final) =*", + "*conftest.py:3:*internal warning", + "*== 1 failed, 2 warnings in *", + ] ) assert "None" not in result.stdout.str() + assert result.stdout.str().count("warning_from_test") == 1 @pytest.mark.parametrize( From 16b15af6245e38624679fcdc86a4f40d0ffec294 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 23 Nov 2018 20:09:57 +0000 Subject: [PATCH 33/44] Preparing release version 4.0.1 --- CHANGELOG.rst | 37 +++++++++++++++++++++++++++++ changelog/3952.bugfix.rst | 1 - changelog/4315.trivial.rst | 1 - changelog/4386.bugfix.rst | 1 - changelog/4393.bugfix.rst | 1 - changelog/4400.bugfix.rst | 1 - changelog/4405.bugfix.rst | 1 - changelog/4412.bugfix.rst | 1 - changelog/4425.bugfix.rst | 1 - changelog/4440.trivial.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-4.0.1.rst | 23 ++++++++++++++++++ doc/en/assert.rst | 4 ++-- doc/en/cache.rst | 6 ++--- doc/en/capture.rst | 2 +- doc/en/doctest.rst | 2 +- doc/en/example/markers.rst | 28 +++++++++++----------- doc/en/example/nonpython.rst | 6 ++--- doc/en/example/parametrize.rst | 12 +++++----- doc/en/example/pythoncollection.rst | 6 ++--- doc/en/example/reportingdemo.rst | 2 +- doc/en/example/simple.rst | 22 ++++++++--------- doc/en/fixture.rst | 12 +++++----- doc/en/getting-started.rst | 4 ++-- doc/en/index.rst | 2 +- doc/en/parametrize.rst | 4 ++-- doc/en/skipping.rst | 2 +- doc/en/tmpdir.rst | 4 ++-- doc/en/unittest.rst | 2 +- doc/en/usage.rst | 4 ++-- doc/en/warnings.rst | 6 ++++- doc/en/writing_plugins.rst | 4 +++- 32 files changed, 131 insertions(+), 73 deletions(-) delete mode 100644 changelog/3952.bugfix.rst delete mode 100644 changelog/4315.trivial.rst delete mode 100644 changelog/4386.bugfix.rst delete mode 100644 changelog/4393.bugfix.rst delete mode 100644 changelog/4400.bugfix.rst delete mode 100644 changelog/4405.bugfix.rst delete mode 100644 changelog/4412.bugfix.rst delete mode 100644 changelog/4425.bugfix.rst delete mode 100644 changelog/4440.trivial.rst create mode 100644 doc/en/announce/release-4.0.1.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4c3d2b249..8e0ba82e4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,43 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 4.0.1 (2018-11-23) +========================= + +Bug Fixes +--------- + +- `#3952 `_: Display warnings before "short test summary info" again, but still later warnings in the end. + + +- `#4386 `_: Handle uninitialized exceptioninfo in repr/str. + + +- `#4393 `_: Do not create ``.gitignore``/``README.md`` files in existing cache directories. + + +- `#4400 `_: Rearrange warning handling for the yield test errors so the opt-out in 4.0.x correctly works. + + +- `#4405 `_: Fix collection of testpaths with ``--pyargs``. + + +- `#4412 `_: Fix assertion rewriting involving ``Starred`` + side-effects. + + +- `#4425 `_: Ensure we resolve the absolute path when the given ``--basetemp`` is a relative path. + + + +Trivial/Internal Changes +------------------------ + +- `#4315 `_: Use ``pkg_resources.parse_version`` instead of ``LooseVersion`` in minversion check. + + +- `#4440 `_: Adjust the stack level of some internal pytest warnings. + + pytest 4.0.0 (2018-11-13) ========================= diff --git a/changelog/3952.bugfix.rst b/changelog/3952.bugfix.rst deleted file mode 100644 index e999cdded..000000000 --- a/changelog/3952.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Display warnings before "short test summary info" again, but still later warnings in the end. diff --git a/changelog/4315.trivial.rst b/changelog/4315.trivial.rst deleted file mode 100644 index ee7266aa0..000000000 --- a/changelog/4315.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Use ``pkg_resources.parse_version`` instead of ``LooseVersion`` in minversion check. diff --git a/changelog/4386.bugfix.rst b/changelog/4386.bugfix.rst deleted file mode 100644 index 0367f3ddc..000000000 --- a/changelog/4386.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Handle uninitialized exceptioninfo in repr/str. diff --git a/changelog/4393.bugfix.rst b/changelog/4393.bugfix.rst deleted file mode 100644 index 4bde5aa43..000000000 --- a/changelog/4393.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Do not create ``.gitignore``/``README.md`` files in existing cache directories. diff --git a/changelog/4400.bugfix.rst b/changelog/4400.bugfix.rst deleted file mode 100644 index eb0df7eca..000000000 --- a/changelog/4400.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Rearrange warning handling for the yield test errors so the opt-out in 4.0.x correctly works. diff --git a/changelog/4405.bugfix.rst b/changelog/4405.bugfix.rst deleted file mode 100644 index ac05091c1..000000000 --- a/changelog/4405.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix collection of testpaths with ``--pyargs``. diff --git a/changelog/4412.bugfix.rst b/changelog/4412.bugfix.rst deleted file mode 100644 index 7a28b6108..000000000 --- a/changelog/4412.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix assertion rewriting involving ``Starred`` + side-effects. diff --git a/changelog/4425.bugfix.rst b/changelog/4425.bugfix.rst deleted file mode 100644 index 7c869cd4c..000000000 --- a/changelog/4425.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Ensure we resolve the absolute path when the given ``--basetemp`` is a relative path. diff --git a/changelog/4440.trivial.rst b/changelog/4440.trivial.rst deleted file mode 100644 index 7187d664f..000000000 --- a/changelog/4440.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Adjust the stack level of some internal pytest warnings. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 504d63484..4120ccfc9 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-4.0.1 release-4.0.0 release-3.10.1 release-3.10.0 diff --git a/doc/en/announce/release-4.0.1.rst b/doc/en/announce/release-4.0.1.rst new file mode 100644 index 000000000..31b222c03 --- /dev/null +++ b/doc/en/announce/release-4.0.1.rst @@ -0,0 +1,23 @@ +pytest-4.0.1 +======================================= + +pytest 4.0.1 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: + +* Anthony Sottile +* Bruno Oliveira +* Daniel Hahler +* Michael D. Hoyle +* Ronny Pfannschmidt +* Slam + + +Happy testing, +The pytest Development Team diff --git a/doc/en/assert.rst b/doc/en/assert.rst index d3deeb503..01767225f 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -26,7 +26,7 @@ you will see the return value of the function call:: $ pytest test_assert1.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 item @@ -169,7 +169,7 @@ if you run this module:: $ pytest test_assert2.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 item diff --git a/doc/en/cache.rst b/doc/en/cache.rst index 4a917d45a..4a493959a 100644 --- a/doc/en/cache.rst +++ b/doc/en/cache.rst @@ -76,7 +76,7 @@ If you then run it with ``--lf``:: $ pytest --lf =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 50 items / 48 deselected run-last-failure: rerun previous 2 failures @@ -117,7 +117,7 @@ of ``FF`` and dots):: $ pytest --ff =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 50 items run-last-failure: rerun previous 2 failures first @@ -236,7 +236,7 @@ You can always peek at the content of the cache using the $ pytest --cache-show =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: cachedir: $REGENDOC_TMPDIR/.pytest_cache ------------------------------- cache values ------------------------------- diff --git a/doc/en/capture.rst b/doc/en/capture.rst index 87fcbc1a6..a3b7d4d5e 100644 --- a/doc/en/capture.rst +++ b/doc/en/capture.rst @@ -65,7 +65,7 @@ of the failing function and hide the other one:: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items diff --git a/doc/en/doctest.rst b/doc/en/doctest.rst index 9488ee826..0030d8df8 100644 --- a/doc/en/doctest.rst +++ b/doc/en/doctest.rst @@ -62,7 +62,7 @@ then you can just invoke ``pytest`` without command line options:: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 1 item diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 709aef0cd..bcd42616b 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -31,7 +31,7 @@ You can then restrict a test run to only run tests marked with ``webtest``:: $ pytest -v -m webtest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items / 3 deselected @@ -44,7 +44,7 @@ Or the inverse, running all tests except the webtest ones:: $ pytest -v -m "not webtest" =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items / 1 deselected @@ -64,7 +64,7 @@ tests based on their module, class, method, or function name:: $ pytest -v test_server.py::TestClass::test_method =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 1 item @@ -77,7 +77,7 @@ You can also select on the class:: $ pytest -v test_server.py::TestClass =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 1 item @@ -90,7 +90,7 @@ Or select multiple nodes:: $ pytest -v test_server.py::TestClass test_server.py::test_send_http =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 2 items @@ -128,7 +128,7 @@ select tests based on their names:: $ pytest -v -k http # running with the above defined example module =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items / 3 deselected @@ -141,7 +141,7 @@ And you can also run all tests except the ones that match the keyword:: $ pytest -k "not send_http" -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items / 1 deselected @@ -156,7 +156,7 @@ Or to select "http" and "quick" tests:: $ pytest -k "http or quick" -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items / 2 deselected @@ -355,7 +355,7 @@ the test needs:: $ pytest -E stage2 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 item @@ -367,7 +367,7 @@ and here is one that specifies exactly the environment needed:: $ pytest -E stage1 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 item @@ -528,7 +528,7 @@ then you will see two tests skipped and two executed tests as expected:: $ pytest -rs # this option reports skip reasons =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -542,7 +542,7 @@ Note that if you specify a platform via the marker-command line option like this $ pytest -m linux =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items / 3 deselected @@ -593,7 +593,7 @@ We can now use the ``-m option`` to select one set:: $ pytest -m interface --tb=short =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items / 2 deselected @@ -614,7 +614,7 @@ or to select both "event" and "interface" tests:: $ pytest -m "interface or event" --tb=short =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items / 1 deselected diff --git a/doc/en/example/nonpython.rst b/doc/en/example/nonpython.rst index bda15065a..bab89f7b8 100644 --- a/doc/en/example/nonpython.rst +++ b/doc/en/example/nonpython.rst @@ -27,7 +27,7 @@ now execute the test specification:: nonpython $ pytest test_simple.yml =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collected 2 items @@ -59,7 +59,7 @@ consulted when reporting in ``verbose`` mode:: nonpython $ pytest -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collecting ... collected 2 items @@ -81,7 +81,7 @@ interesting to just look at the collection tree:: nonpython $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collected 2 items diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index dcd59cfb3..b983b52b5 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -139,7 +139,7 @@ objects, they are still using the default pytest representation:: $ pytest test_time.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 8 items @@ -195,7 +195,7 @@ this is a fully self-contained example which you can run with:: $ pytest test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -208,7 +208,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia $ pytest --collect-only test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -272,7 +272,7 @@ Let's first see how it looks like at collection time:: $ pytest test_backends.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -333,7 +333,7 @@ The result of this test will be successful:: $ pytest test_indirect_list.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 item @@ -460,7 +460,7 @@ If you run this with reporting for skips enabled:: $ pytest -rs test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 9a439cbae..0c1a2540b 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -127,7 +127,7 @@ The test collection would look like this:: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 2 items @@ -180,7 +180,7 @@ You can always peek at the collection tree without running tests like this:: . $ pytest --collect-only pythoncollection.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 3 items @@ -248,7 +248,7 @@ file will be left out:: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 0 items diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index 35ec7b6cc..fceb83dfc 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -11,7 +11,7 @@ get on the terminal - we are working on that):: assertion $ pytest failure_demo.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/assertion, inifile: collected 42 items diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 2edaf9a74..fa93a77f3 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -121,7 +121,7 @@ directory with the above conftest.py:: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items @@ -179,7 +179,7 @@ and when running it will see a skipped "slow" test:: $ pytest -rs # "-rs" means report details on the little 's' =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -193,7 +193,7 @@ Or run it including the ``slow`` marked test:: $ pytest --runslow =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -331,7 +331,7 @@ which will add the string to the test header accordingly:: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y project deps: mylib-1.1 rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items @@ -357,7 +357,7 @@ which will add info only when run with "--v":: $ pytest -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 cachedir: .pytest_cache info1: did you know that ... did you? @@ -370,7 +370,7 @@ and nothing when run plainly:: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items @@ -407,7 +407,7 @@ Now we can profile which test functions execute the slowest:: $ pytest --durations=3 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items @@ -479,7 +479,7 @@ If we run this:: $ pytest -rx =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -560,7 +560,7 @@ We can run this:: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 7 items @@ -671,7 +671,7 @@ and run them:: $ pytest test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -770,7 +770,7 @@ and run it:: $ pytest -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 1eed211b0..2fb3d4b56 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -70,7 +70,7 @@ marked ``smtp_connection`` fixture function. Running the test looks like this:: $ pytest test_smtpsimple.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 item @@ -208,7 +208,7 @@ inspect what is going on and can now run the tests:: $ pytest test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -690,7 +690,7 @@ Running the above tests results in the following test IDs being used:: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 10 items @@ -732,7 +732,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:: $ pytest test_fixture_marks.py -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 3 items @@ -775,7 +775,7 @@ Here we declare an ``app`` fixture which receives the previously defined $ pytest -v test_appsetup.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 2 items @@ -844,7 +844,7 @@ Let's run the tests in verbose mode and with looking at the print-output:: $ pytest -v -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 8 items diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 56bb300c3..97ae2abdc 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -27,7 +27,7 @@ Install ``pytest`` 2. Check that you installed the correct version:: $ pytest --version - This is pytest version 3.x.y, imported from $PYTHON_PREFIX/lib/python3.6/site-packages/pytest.py + This is pytest version 4.x.y, imported from $PYTHON_PREFIX/lib/python3.6/site-packages/pytest.py .. _`simpletest`: @@ -47,7 +47,7 @@ That’s it. You can now execute the test function:: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 item diff --git a/doc/en/index.rst b/doc/en/index.rst index 6a382e571..510df4a09 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -26,7 +26,7 @@ To execute it:: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 item diff --git a/doc/en/parametrize.rst b/doc/en/parametrize.rst index 90ce4ffc6..d3bce1b58 100644 --- a/doc/en/parametrize.rst +++ b/doc/en/parametrize.rst @@ -54,7 +54,7 @@ them in turn:: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items @@ -103,7 +103,7 @@ Let's run this:: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index f705422d8..075886a96 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -327,7 +327,7 @@ Running it with the report-on-xfail option gives this output:: example $ pytest -rx xfail_demo.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/example, inifile: collected 7 items diff --git a/doc/en/tmpdir.rst b/doc/en/tmpdir.rst index 21bdcdd6a..12a3e5641 100644 --- a/doc/en/tmpdir.rst +++ b/doc/en/tmpdir.rst @@ -39,7 +39,7 @@ Running this would result in a passed test except for the last $ pytest test_tmp_path.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 item @@ -99,7 +99,7 @@ Running this would result in a passed test except for the last $ pytest test_tmpdir.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 item diff --git a/doc/en/unittest.rst b/doc/en/unittest.rst index 46a9ee372..5a6561d4b 100644 --- a/doc/en/unittest.rst +++ b/doc/en/unittest.rst @@ -126,7 +126,7 @@ the ``self.db`` values in the traceback:: $ pytest test_unittest_db.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 08682d614..43f20394f 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -154,7 +154,7 @@ Example:: $ pytest -ra =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items @@ -177,7 +177,7 @@ More than one character can be used, so for example to only see failed and skipp $ pytest -rfs =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index 31bd74695..1317f020f 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -22,7 +22,7 @@ Running pytest now produces this output:: $ pytest test_show_warnings.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 item @@ -33,6 +33,8 @@ Running pytest now produces this output:: $REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2 warnings.warn(UserWarning("api v1, should use functions from v2")) + -- Docs: https://docs.pytest.org/en/latest/warnings.html + ========================= warnings summary (final) ========================= -- Docs: https://docs.pytest.org/en/latest/warnings.html =================== 1 passed, 1 warnings in 0.12 seconds =================== @@ -356,6 +358,8 @@ defines an ``__init__`` constructor, as this prevents the class from being insta $REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestWarning: cannot collect test class 'Test' because it has a __init__ constructor class Test: + -- Docs: https://docs.pytest.org/en/latest/warnings.html + ========================= warnings summary (final) ========================= -- Docs: https://docs.pytest.org/en/latest/warnings.html 1 warnings in 0.12 seconds diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 68ff4b831..e35577bf8 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -411,7 +411,7 @@ additionally it is possible to copy examples for an example folder before runnin $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 2 items @@ -422,6 +422,8 @@ additionally it is possible to copy examples for an example folder before runnin $REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time testdir.copy_example("test_example.py") + -- Docs: https://docs.pytest.org/en/latest/warnings.html + ========================= warnings summary (final) ========================= -- Docs: https://docs.pytest.org/en/latest/warnings.html =================== 2 passed, 1 warnings in 0.12 seconds =================== From be3b8fc9c1417243b9567bd31992c0c42f0bf898 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 23 Nov 2018 22:39:05 +0100 Subject: [PATCH 34/44] Fix warnings summary header appearing twice Ref: https://github.com/pytest-dev/pytest/pull/4450#discussion_r236017645 Ref: https://github.com/pytest-dev/pytest/pull/4399 --- doc/en/warnings.rst | 4 ---- doc/en/writing_plugins.rst | 2 -- src/_pytest/terminal.py | 2 ++ testing/test_terminal.py | 29 ++++++++++++++++++++++++++++- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index 1317f020f..182aae4fa 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -33,8 +33,6 @@ Running pytest now produces this output:: $REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2 warnings.warn(UserWarning("api v1, should use functions from v2")) - -- Docs: https://docs.pytest.org/en/latest/warnings.html - ========================= warnings summary (final) ========================= -- Docs: https://docs.pytest.org/en/latest/warnings.html =================== 1 passed, 1 warnings in 0.12 seconds =================== @@ -358,8 +356,6 @@ defines an ``__init__`` constructor, as this prevents the class from being insta $REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestWarning: cannot collect test class 'Test' because it has a __init__ constructor class Test: - -- Docs: https://docs.pytest.org/en/latest/warnings.html - ========================= warnings summary (final) ========================= -- Docs: https://docs.pytest.org/en/latest/warnings.html 1 warnings in 0.12 seconds diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index e35577bf8..bee3d6102 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -422,8 +422,6 @@ additionally it is possible to copy examples for an example folder before runnin $REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time testdir.copy_example("test_example.py") - -- Docs: https://docs.pytest.org/en/latest/warnings.html - ========================= warnings summary (final) ========================= -- Docs: https://docs.pytest.org/en/latest/warnings.html =================== 2 passed, 1 warnings in 0.12 seconds =================== diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 3c9abac8a..6f3893653 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -734,6 +734,8 @@ class TerminalReporter(object): else: warnings = all_warnings self._already_displayed_warnings = len(warnings) + if not warnings: + return grouped = itertools.groupby( warnings, key=lambda wr: wr.get_location(self.config) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index f1bfa81f2..86ec1cd07 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1094,7 +1094,34 @@ def test_terminal_summary_warnings_are_displayed(testdir): ] ) assert "None" not in result.stdout.str() - assert result.stdout.str().count("warning_from_test") == 1 + stdout = result.stdout.str() + assert stdout.count("warning_from_test") == 1 + assert stdout.count("=== warnings summary ") == 2 + + +@pytest.mark.filterwarnings("default") +def test_terminal_summary_warnings_header_once(testdir): + testdir.makepyfile( + """ + def test_failure(): + import warnings + warnings.warn("warning_from_" + "test") + assert 0 + """ + ) + result = testdir.runpytest("-ra") + result.stdout.fnmatch_lines( + [ + "*= warnings summary =*", + "*warning_from_test*", + "*= short test summary info =*", + "*== 1 failed, 1 warnings in *", + ] + ) + assert "None" not in result.stdout.str() + stdout = result.stdout.str() + assert stdout.count("warning_from_test") == 1 + assert stdout.count("=== warnings summary ") == 1 @pytest.mark.parametrize( From 0d5298475de40c36f591b2a57d8843082485ef1c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 23 Nov 2018 15:01:35 -0800 Subject: [PATCH 35/44] Fix `raises(..., "code(string)")` frame filename. --- changelog/4435.bugfix.rst | 1 + src/_pytest/python_api.py | 2 +- testing/python/raises.py | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelog/4435.bugfix.rst diff --git a/changelog/4435.bugfix.rst b/changelog/4435.bugfix.rst new file mode 100644 index 000000000..de60b5e62 --- /dev/null +++ b/changelog/4435.bugfix.rst @@ -0,0 +1 @@ +Fix ``raises(..., 'code(string)')`` frame filename. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 805cd85ad..7de8e154c 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -679,7 +679,7 @@ def raises(expected_exception, *args, **kwargs): loc.update(kwargs) # print "raises frame scope: %r" % frame.f_locals try: - code = _pytest._code.Source(code).compile() + code = _pytest._code.Source(code).compile(_genframe=frame) six.exec_(code, frame.f_globals, loc) # XXX didn't mean f_globals == f_locals something special? # this is destroyed here ... diff --git a/testing/python/raises.py b/testing/python/raises.py index 130b196ac..6ca19c677 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -17,6 +17,10 @@ class TestRaises(object): def test_raises_exec(self): pytest.raises(ValueError, "a,x = []") + def test_raises_exec_correct_filename(self): + excinfo = pytest.raises(ValueError, 'int("s")') + assert __file__ in excinfo.traceback[-1].path + def test_raises_syntax_error(self): pytest.raises(SyntaxError, "qwe qwe qwe") From 70158013772e600284e5274c72fb26b02c82a2b9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 23 Nov 2018 21:41:22 -0800 Subject: [PATCH 36/44] Highlight docs with pygments-pytest --- doc/en/assert.rst | 12 ++- doc/en/builtin.rst | 4 +- doc/en/cache.rst | 24 +++-- doc/en/capture.rst | 4 +- doc/en/conf.py | 1 + doc/en/doctest.rst | 4 +- doc/en/example/markers.rst | 154 +++++++++++++++++----------- doc/en/example/nonpython.rst | 12 ++- doc/en/example/parametrize.rst | 65 ++++++++---- doc/en/example/pythoncollection.rst | 36 ++++--- doc/en/example/reportingdemo.rst | 6 +- doc/en/example/simple.rst | 56 +++++++--- doc/en/example/special.rst | 4 +- doc/en/fixture.rst | 40 ++++++-- doc/en/getting-started.rst | 16 ++- doc/en/index.rst | 4 +- doc/en/parametrize.rst | 16 ++- doc/en/skipping.rst | 4 +- doc/en/tmpdir.rst | 8 +- doc/en/unittest.rst | 8 +- doc/en/usage.rst | 8 +- doc/en/warnings.rst | 10 +- doc/en/writing_plugins.rst | 13 +-- tox.ini | 1 + 24 files changed, 347 insertions(+), 163 deletions(-) diff --git a/doc/en/assert.rst b/doc/en/assert.rst index 01767225f..43fedebed 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -22,7 +22,9 @@ following:: assert f() == 4 to assert that your function returns a certain value. If this assertion fails -you will see the return value of the function call:: +you will see the return value of the function call: + +.. code-block:: pytest $ pytest test_assert1.py =========================== test session starts ============================ @@ -165,7 +167,9 @@ when it encounters comparisons. For example:: set2 = set("8035") assert set1 == set2 -if you run this module:: +if you run this module: + +.. code-block:: pytest $ pytest test_assert2.py =========================== test session starts ============================ @@ -235,7 +239,9 @@ now, given this test module:: assert f1 == f2 you can run the test module and get the custom output defined in -the conftest file:: +the conftest file: + +.. code-block:: pytest $ pytest -q test_foocompare.py F [100%] diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index 60681bc9b..1e376f0d3 100644 --- a/doc/en/builtin.rst +++ b/doc/en/builtin.rst @@ -12,7 +12,9 @@ For information on plugin hooks and objects, see :ref:`plugins`. For information on the ``pytest.mark`` mechanism, see :ref:`mark`. -For information about fixtures, see :ref:`fixtures`. To see a complete list of available fixtures (add ``-v`` to also see fixtures with leading ``_``), type :: +For information about fixtures, see :ref:`fixtures`. To see a complete list of available fixtures (add ``-v`` to also see fixtures with leading ``_``), type : + +.. code-block:: pytest $ pytest -q --fixtures cache diff --git a/doc/en/cache.rst b/doc/en/cache.rst index 4a493959a..914c36fed 100644 --- a/doc/en/cache.rst +++ b/doc/en/cache.rst @@ -43,7 +43,9 @@ First, let's create 50 test invocation of which only 2 fail:: if i in (17, 25): pytest.fail("bad luck") -If you run this for the first time you will see two failures:: +If you run this for the first time you will see two failures: + +.. code-block:: pytest $ pytest -q .................F.......F........................ [100%] @@ -72,7 +74,9 @@ If you run this for the first time you will see two failures:: test_50.py:6: Failed 2 failed, 48 passed in 0.12 seconds -If you then run it with ``--lf``:: +If you then run it with ``--lf``: + +.. code-block:: pytest $ pytest --lf =========================== test session starts ============================ @@ -113,7 +117,9 @@ not been run ("deselected"). Now, if you run with the ``--ff`` option, all tests will be run but the first previous failures will be executed first (as can be seen from the series -of ``FF`` and dots):: +of ``FF`` and dots): + +.. code-block:: pytest $ pytest --ff =========================== test session starts ============================ @@ -192,7 +198,9 @@ across pytest invocations:: assert mydata == 23 If you run this command once, it will take a while because -of the sleep:: +of the sleep: + +.. code-block:: pytest $ pytest -q F [100%] @@ -209,7 +217,9 @@ of the sleep:: 1 failed in 0.12 seconds If you run it a second time the value will be retrieved from -the cache and this will be quick:: +the cache and this will be quick: + +.. code-block:: pytest $ pytest -q F [100%] @@ -232,7 +242,9 @@ Inspecting Cache content ------------------------------- You can always peek at the content of the cache using the -``--cache-show`` command line option:: +``--cache-show`` command line option: + +.. code-block:: pytest $ pytest --cache-show =========================== test session starts ============================ diff --git a/doc/en/capture.rst b/doc/en/capture.rst index a3b7d4d5e..488b2b874 100644 --- a/doc/en/capture.rst +++ b/doc/en/capture.rst @@ -61,7 +61,9 @@ is that you can use print statements for debugging:: assert False and running this module will show you precisely the output -of the failing function and hide the other one:: +of the failing function and hide the other one: + +.. code-block:: pytest $ pytest =========================== test session starts ============================ diff --git a/doc/en/conf.py b/doc/en/conf.py index 686a05ba4..445449185 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -40,6 +40,7 @@ todo_include_todos = 1 # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ + "pygments_pytest", "sphinx.ext.autodoc", "sphinx.ext.todo", "sphinx.ext.autosummary", diff --git a/doc/en/doctest.rst b/doc/en/doctest.rst index 0030d8df8..52edd4cf2 100644 --- a/doc/en/doctest.rst +++ b/doc/en/doctest.rst @@ -58,7 +58,9 @@ and another like this:: """ return 42 -then you can just invoke ``pytest`` without command line options:: +then you can just invoke ``pytest`` without command line options: + +.. code-block:: pytest $ pytest =========================== test session starts ============================ diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index bcd42616b..8ab885f0f 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -27,7 +27,9 @@ You can "mark" a test function with custom metadata like this:: .. versionadded:: 2.2 -You can then restrict a test run to only run tests marked with ``webtest``:: +You can then restrict a test run to only run tests marked with ``webtest``: + +.. code-block:: pytest $ pytest -v -m webtest =========================== test session starts ============================ @@ -40,7 +42,9 @@ You can then restrict a test run to only run tests marked with ``webtest``:: ================== 1 passed, 3 deselected in 0.12 seconds ================== -Or the inverse, running all tests except the webtest ones:: +Or the inverse, running all tests except the webtest ones: + +.. code-block:: pytest $ pytest -v -m "not webtest" =========================== test session starts ============================ @@ -60,7 +64,9 @@ Selecting tests based on their node ID You can provide one or more :ref:`node IDs ` as positional arguments to select only specified tests. This makes it easy to select -tests based on their module, class, method, or function name:: +tests based on their module, class, method, or function name: + +.. code-block:: pytest $ pytest -v test_server.py::TestClass::test_method =========================== test session starts ============================ @@ -73,7 +79,9 @@ tests based on their module, class, method, or function name:: ========================= 1 passed in 0.12 seconds ========================= -You can also select on the class:: +You can also select on the class: + +.. code-block:: pytest $ pytest -v test_server.py::TestClass =========================== test session starts ============================ @@ -86,19 +94,21 @@ You can also select on the class:: ========================= 1 passed in 0.12 seconds ========================= -Or select multiple nodes:: +Or select multiple nodes: - $ pytest -v test_server.py::TestClass test_server.py::test_send_http - =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 - cachedir: .pytest_cache - rootdir: $REGENDOC_TMPDIR, inifile: - collecting ... collected 2 items +.. code-block:: pytest - test_server.py::TestClass::test_method PASSED [ 50%] - test_server.py::test_send_http PASSED [100%] + $ pytest -v test_server.py::TestClass test_server.py::test_send_http + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6 + cachedir: .pytest_cache + rootdir: $REGENDOC_TMPDIR, inifile: + collecting ... collected 2 items - ========================= 2 passed in 0.12 seconds ========================= + test_server.py::TestClass::test_method PASSED [ 50%] + test_server.py::test_send_http PASSED [100%] + + ========================= 2 passed in 0.12 seconds ========================= .. _node-id: @@ -124,7 +134,9 @@ Using ``-k expr`` to select tests based on their name You can use the ``-k`` command line option to specify an expression which implements a substring match on the test names instead of the exact match on markers that ``-m`` provides. This makes it easy to -select tests based on their names:: +select tests based on their names: + +.. code-block:: pytest $ pytest -v -k http # running with the above defined example module =========================== test session starts ============================ @@ -137,7 +149,9 @@ select tests based on their names:: ================== 1 passed, 3 deselected in 0.12 seconds ================== -And you can also run all tests except the ones that match the keyword:: +And you can also run all tests except the ones that match the keyword: + +.. code-block:: pytest $ pytest -k "not send_http" -v =========================== test session starts ============================ @@ -152,7 +166,9 @@ And you can also run all tests except the ones that match the keyword:: ================== 3 passed, 1 deselected in 0.12 seconds ================== -Or to select "http" and "quick" tests:: +Or to select "http" and "quick" tests: + +.. code-block:: pytest $ pytest -k "http or quick" -v =========================== test session starts ============================ @@ -351,7 +367,9 @@ A test file using this local plugin:: pass and an example invocations specifying a different environment than what -the test needs:: +the test needs: + +.. code-block:: pytest $ pytest -E stage2 =========================== test session starts ============================ @@ -363,7 +381,9 @@ the test needs:: ======================== 1 skipped in 0.12 seconds ========================= -and here is one that specifies exactly the environment needed:: +and here is one that specifies exactly the environment needed: + +.. code-block:: pytest $ pytest -E stage1 =========================== test session starts ============================ @@ -428,7 +448,9 @@ However, if there is a callable as the single positional argument with no keywor def test_with_args(): pass -The output is as follows:: +The output is as follows: + +.. code-block:: pytest $ pytest -q -s Mark(name='my_marker', args=(,), kwargs={}) @@ -469,7 +491,9 @@ test function. From a conftest file we can read it like this:: print("glob args=%s kwargs=%s" % (mark.args, mark.kwargs)) sys.stdout.flush() -Let's run this without capturing output and see what we get:: +Let's run this without capturing output and see what we get: + +.. code-block:: pytest $ pytest -q -s glob args=('function',) kwargs={'x': 3} @@ -524,7 +548,9 @@ Let's do a little test file to show how this looks like:: def test_runs_everywhere(): pass -then you will see two tests skipped and two executed tests as expected:: +then you will see two tests skipped and two executed tests as expected: + +.. code-block:: pytest $ pytest -rs # this option reports skip reasons =========================== test session starts ============================ @@ -538,7 +564,9 @@ then you will see two tests skipped and two executed tests as expected:: =================== 2 passed, 2 skipped in 0.12 seconds ==================== -Note that if you specify a platform via the marker-command line option like this:: +Note that if you specify a platform via the marker-command line option like this: + +.. code-block:: pytest $ pytest -m linux =========================== test session starts ============================ @@ -589,48 +617,52 @@ We want to dynamically define two markers and can do it in a elif "event" in item.nodeid: item.add_marker(pytest.mark.event) -We can now use the ``-m option`` to select one set:: +We can now use the ``-m option`` to select one set: - $ pytest -m interface --tb=short - =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y - rootdir: $REGENDOC_TMPDIR, inifile: - collected 4 items / 2 deselected +.. code-block:: pytest - test_module.py FF [100%] + $ pytest -m interface --tb=short + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + rootdir: $REGENDOC_TMPDIR, inifile: + collected 4 items / 2 deselected - ================================= FAILURES ================================= - __________________________ test_interface_simple ___________________________ - test_module.py:3: in test_interface_simple - assert 0 - E assert 0 - __________________________ test_interface_complex __________________________ - test_module.py:6: in test_interface_complex - assert 0 - E assert 0 - ================== 2 failed, 2 deselected in 0.12 seconds ================== + test_module.py FF [100%] -or to select both "event" and "interface" tests:: + ================================= FAILURES ================================= + __________________________ test_interface_simple ___________________________ + test_module.py:3: in test_interface_simple + assert 0 + E assert 0 + __________________________ test_interface_complex __________________________ + test_module.py:6: in test_interface_complex + assert 0 + E assert 0 + ================== 2 failed, 2 deselected in 0.12 seconds ================== - $ pytest -m "interface or event" --tb=short - =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y - rootdir: $REGENDOC_TMPDIR, inifile: - collected 4 items / 1 deselected +or to select both "event" and "interface" tests: - test_module.py FFF [100%] +.. code-block:: pytest - ================================= FAILURES ================================= - __________________________ test_interface_simple ___________________________ - test_module.py:3: in test_interface_simple - assert 0 - E assert 0 - __________________________ test_interface_complex __________________________ - test_module.py:6: in test_interface_complex - assert 0 - E assert 0 - ____________________________ test_event_simple _____________________________ - test_module.py:9: in test_event_simple - assert 0 - E assert 0 - ================== 3 failed, 1 deselected in 0.12 seconds ================== + $ pytest -m "interface or event" --tb=short + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + rootdir: $REGENDOC_TMPDIR, inifile: + collected 4 items / 1 deselected + + test_module.py FFF [100%] + + ================================= FAILURES ================================= + __________________________ test_interface_simple ___________________________ + test_module.py:3: in test_interface_simple + assert 0 + E assert 0 + __________________________ test_interface_complex __________________________ + test_module.py:6: in test_interface_complex + assert 0 + E assert 0 + ____________________________ test_event_simple _____________________________ + test_module.py:9: in test_event_simple + assert 0 + E assert 0 + ================== 3 failed, 1 deselected in 0.12 seconds ================== diff --git a/doc/en/example/nonpython.rst b/doc/en/example/nonpython.rst index bab89f7b8..5b96a33bf 100644 --- a/doc/en/example/nonpython.rst +++ b/doc/en/example/nonpython.rst @@ -23,7 +23,9 @@ You can create a simple example file: :literal: and if you installed `PyYAML`_ or a compatible YAML-parser you can -now execute the test specification:: +now execute the test specification: + +.. code-block:: pytest nonpython $ pytest test_simple.yml =========================== test session starts ============================ @@ -55,7 +57,9 @@ your own domain specific testing language this way. will be reported as a (red) string. ``reportinfo()`` is used for representing the test location and is also -consulted when reporting in ``verbose`` mode:: +consulted when reporting in ``verbose`` mode: + +.. code-block:: pytest nonpython $ pytest -v =========================== test session starts ============================ @@ -77,7 +81,9 @@ consulted when reporting in ``verbose`` mode:: .. regendoc:wipe While developing your custom test collection and execution it's also -interesting to just look at the collection tree:: +interesting to just look at the collection tree: + +.. code-block:: pytest nonpython $ pytest --collect-only =========================== test session starts ============================ diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index b983b52b5..488f6e310 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -42,14 +42,18 @@ Now we add a test configuration like this:: end = 2 metafunc.parametrize("param1", range(end)) -This means that we only run 2 tests if we do not pass ``--all``:: +This means that we only run 2 tests if we do not pass ``--all``: + +.. code-block:: pytest $ pytest -q test_compute.py .. [100%] 2 passed in 0.12 seconds We run only two computations, so we see two dots. -let's run the full monty:: +let's run the full monty: + +.. code-block:: pytest $ pytest -q --all ....F [100%] @@ -134,8 +138,9 @@ used as the test IDs. These are succinct, but can be a pain to maintain. In ``test_timedistance_v2``, we specified ``ids`` as a function that can generate a string representation to make part of the test ID. So our ``datetime`` values use the label generated by ``idfn``, but because we didn't generate a label for ``timedelta`` -objects, they are still using the default pytest representation:: +objects, they are still using the default pytest representation: +.. code-block:: pytest $ pytest test_time.py --collect-only =========================== test session starts ============================ @@ -191,7 +196,9 @@ only have to work a bit to construct the correct arguments for pytest's def test_demo2(self, attribute): assert isinstance(attribute, str) -this is a fully self-contained example which you can run with:: +this is a fully self-contained example which you can run with: + +.. code-block:: pytest $ pytest test_scenarios.py =========================== test session starts ============================ @@ -203,8 +210,9 @@ this is a fully self-contained example which you can run with:: ========================= 4 passed in 0.12 seconds ========================= -If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function:: +If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function: +.. code-block:: pytest $ pytest --collect-only test_scenarios.py =========================== test session starts ============================ @@ -268,7 +276,9 @@ creates a database object for the actual test invocations:: else: raise ValueError("invalid internal test config") -Let's first see how it looks like at collection time:: +Let's first see how it looks like at collection time: + +.. code-block:: pytest $ pytest test_backends.py --collect-only =========================== test session starts ============================ @@ -281,7 +291,9 @@ Let's first see how it looks like at collection time:: ======================= no tests ran in 0.12 seconds ======================= -And then when we run the test:: +And then when we run the test: + +.. code-block:: pytest $ pytest -q test_backends.py .F [100%] @@ -329,7 +341,9 @@ will be passed to respective fixture function:: assert x == 'aaa' assert y == 'b' -The result of this test will be successful:: +The result of this test will be successful: + +.. code-block:: pytest $ pytest test_indirect_list.py --collect-only =========================== test session starts ============================ @@ -377,7 +391,9 @@ parametrizer`_ but in a lot less code:: pytest.raises(ZeroDivisionError, "a/b") Our test generator looks up a class-level definition which specifies which -argument sets to use for each test function. Let's run it:: +argument sets to use for each test function. Let's run it: + +.. code-block:: pytest $ pytest -q F.. [100%] @@ -407,7 +423,9 @@ is to be run with different sets of arguments for its three arguments: .. literalinclude:: multipython.py -Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize):: +Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize): + +.. code-block:: pytest . $ pytest -rs -q multipython.py ...sss...sssssssss...sss... [100%] @@ -456,7 +474,9 @@ And finally a little test module:: assert round(basemod.func1(), 3) == round(optmod.func1(), 3) -If you run this with reporting for skips enabled:: +If you run this with reporting for skips enabled: + +.. code-block:: pytest $ pytest -rs test_module.py =========================== test session starts ============================ @@ -511,21 +531,22 @@ we mark the rest three parametrized tests with the custom marker ``basic``, and for the fourth test we also use the built-in mark ``xfail`` to indicate this test is expected to fail. For explicitness, we set test ids for some tests. -Then run ``pytest`` with verbose mode and with only the ``basic`` marker:: +Then run ``pytest`` with verbose mode and with only the ``basic`` marker: - pytest -v -m basic - ============================================ test session starts ============================================= - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y +.. code-block:: pytest + + $ pytest -v -m basic + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: - collected 4 items + collecting ... collected 17 items / 14 deselected - test_pytest_param_example.py::test_eval[1+7-8] PASSED - test_pytest_param_example.py::test_eval[basic_2+4] PASSED - test_pytest_param_example.py::test_eval[basic_6*9] xfail - ========================================== short test summary info =========================================== - XFAIL test_pytest_param_example.py::test_eval[basic_6*9] + test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%] + test_pytest_param_example.py::test_eval[basic_2+4] PASSED [ 66%] + test_pytest_param_example.py::test_eval[basic_6*9] xfail [100%] - ============================================= 1 tests deselected ============================================= + ============ 2 passed, 14 deselected, 1 xfailed in 0.12 seconds ============ As the result: diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 0c1a2540b..3f1dd68ee 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -24,20 +24,22 @@ by passing the ``--ignore=path`` option on the cli. ``pytest`` allows multiple '-- test_world_03.py Now if you invoke ``pytest`` with ``--ignore=tests/foobar/test_foobar_03.py --ignore=tests/hello/``, -you will see that ``pytest`` only collects test-modules, which do not match the patterns specified:: +you will see that ``pytest`` only collects test-modules, which do not match the patterns specified: - ========= test session starts ========== - platform darwin -- Python 2.7.10, pytest-2.8.2, py-1.4.30, pluggy-0.3.1 +.. code-block:: pytest + + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 5 items - tests/example/test_example_01.py . - tests/example/test_example_02.py . - tests/example/test_example_03.py . - tests/foobar/test_foobar_01.py . - tests/foobar/test_foobar_02.py . + tests/example/test_example_01.py . [ 20%] + tests/example/test_example_02.py . [ 40%] + tests/example/test_example_03.py . [ 60%] + tests/foobar/test_foobar_01.py . [ 80%] + tests/foobar/test_foobar_02.py . [100%] - ======= 5 passed in 0.02 seconds ======= + ========================= 5 passed in 0.02 seconds ========================= Deselect tests during test collection ------------------------------------- @@ -123,7 +125,9 @@ that match ``*_check``. For example, if we have:: def complex_check(self): pass -The test collection would look like this:: +The test collection would look like this: + +.. code-block:: pytest $ pytest --collect-only =========================== test session starts ============================ @@ -176,7 +180,9 @@ treat it as a filesystem path. Finding out what is collected ----------------------------------------------- -You can always peek at the collection tree without running tests like this:: +You can always peek at the collection tree without running tests like this: + +.. code-block:: pytest . $ pytest --collect-only pythoncollection.py =========================== test session starts ============================ @@ -231,7 +237,9 @@ and a ``setup.py`` dummy file like this:: 0/0 # will raise exception if imported If you run with a Python 2 interpreter then you will find the one test and will -leave out the ``setup.py`` file:: +leave out the ``setup.py`` file: + +.. code-block:: pytest #$ pytest --collect-only ====== test session starts ====== @@ -244,7 +252,9 @@ leave out the ``setup.py`` file:: ====== no tests ran in 0.04 seconds ====== If you run with a Python 3 interpreter both the one test and the ``setup.py`` -file will be left out:: +file will be left out: + +.. code-block:: pytest $ pytest --collect-only =========================== test session starts ============================ diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index fceb83dfc..ffccdf77f 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -7,7 +7,9 @@ Demo of Python failure reports with pytest Here is a nice run of several tens of failures and how ``pytest`` presents things (unfortunately not showing the nice colors here in the HTML that you -get on the terminal - we are working on that):: +get on the terminal - we are working on that): + +.. code-block:: pytest assertion $ pytest failure_demo.py =========================== test session starts ============================ @@ -364,7 +366,7 @@ get on the terminal - we are working on that):: > int(s) E ValueError: invalid literal for int() with base 10: 'qwe' - <0-codegen $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/python_api.py:682>:1: ValueError + <0-codegen $REGENDOC_TMPDIR/assertion/failure_demo.py:145>:1: ValueError ______________________ TestRaises.test_raises_doesnt _______________________ self = diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index fa93a77f3..95c0e6365 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -43,7 +43,9 @@ provide the ``cmdopt`` through a :ref:`fixture function `: def cmdopt(request): return request.config.getoption("--cmdopt") -Let's run this without supplying our new option:: +Let's run this without supplying our new option: + +.. code-block:: pytest $ pytest -q test_sample.py F [100%] @@ -65,7 +67,9 @@ Let's run this without supplying our new option:: first 1 failed in 0.12 seconds -And now with supplying a command line option:: +And now with supplying a command line option: + +.. code-block:: pytest $ pytest -q --cmdopt=type2 F [100%] @@ -117,7 +121,9 @@ the command line arguments before they get processed: If you have the `xdist plugin `_ installed you will now always perform test runs using a number of subprocesses close to your CPU. Running in an empty -directory with the above conftest.py:: +directory with the above conftest.py: + +.. code-block:: pytest $ pytest =========================== test session starts ============================ @@ -175,7 +181,9 @@ We can now write a test module like this: def test_func_slow(): pass -and when running it will see a skipped "slow" test:: +and when running it will see a skipped "slow" test: + +.. code-block:: pytest $ pytest -rs # "-rs" means report details on the little 's' =========================== test session starts ============================ @@ -189,7 +197,9 @@ and when running it will see a skipped "slow" test:: =================== 1 passed, 1 skipped in 0.12 seconds ==================== -Or run it including the ``slow`` marked test:: +Or run it including the ``slow`` marked test: + +.. code-block:: pytest $ pytest --runslow =========================== test session starts ============================ @@ -230,7 +240,9 @@ Example: The ``__tracebackhide__`` setting influences ``pytest`` showing of tracebacks: the ``checkconfig`` function will not be shown unless the ``--full-trace`` command line option is specified. -Let's run our little function:: +Let's run our little function: + +.. code-block:: pytest $ pytest -q test_checkconfig.py F [100%] @@ -327,7 +339,9 @@ It's easy to present extra information in a ``pytest`` run: def pytest_report_header(config): return "project deps: mylib-1.1" -which will add the string to the test header accordingly:: +which will add the string to the test header accordingly: + +.. code-block:: pytest $ pytest =========================== test session starts ============================ @@ -353,7 +367,9 @@ display more information if applicable: if config.getoption("verbose") > 0: return ["info1: did you know that ...", "did you?"] -which will add info only when run with "--v":: +which will add info only when run with "--v": + +.. code-block:: pytest $ pytest -v =========================== test session starts ============================ @@ -366,7 +382,9 @@ which will add info only when run with "--v":: ======================= no tests ran in 0.12 seconds ======================= -and nothing when run plainly:: +and nothing when run plainly: + +.. code-block:: pytest $ pytest =========================== test session starts ============================ @@ -403,7 +421,9 @@ out which tests are the slowest. Let's make an artificial test suite: def test_funcslow2(): time.sleep(0.3) -Now we can profile which test functions execute the slowest:: +Now we can profile which test functions execute the slowest: + +.. code-block:: pytest $ pytest --durations=3 =========================== test session starts ============================ @@ -475,7 +495,9 @@ tests in a class. Here is a test module example: def test_normal(): pass -If we run this:: +If we run this: + +.. code-block:: pytest $ pytest -rx =========================== test session starts ============================ @@ -556,7 +578,9 @@ the ``db`` fixture: def test_root(db): # no db here, will error out pass -We can run this:: +We can run this: + +.. code-block:: pytest $ pytest =========================== test session starts ============================ @@ -667,7 +691,9 @@ if you then have failing tests: def test_fail2(): assert 0 -and run them:: +and run them: + +.. code-block:: pytest $ pytest test_module.py =========================== test session starts ============================ @@ -766,7 +792,9 @@ if you then have failing tests: def test_fail2(): assert 0 -and run it:: +and run it: + +.. code-block:: pytest $ pytest -s test_module.py =========================== test session starts ============================ diff --git a/doc/en/example/special.rst b/doc/en/example/special.rst index 524ae7883..800cb8e90 100644 --- a/doc/en/example/special.rst +++ b/doc/en/example/special.rst @@ -57,7 +57,9 @@ will be called ahead of running any tests:: def test_unit1(self): print("test_unit1 method called") -If you run this without output capturing:: +If you run this without output capturing: + +.. code-block:: pytest $ pytest -q -s test_module.py callattr_ahead_of_alltests called diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 2fb3d4b56..e70787a62 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -66,7 +66,9 @@ using it:: Here, the ``test_ehlo`` needs the ``smtp_connection`` fixture value. pytest will discover and call the :py:func:`@pytest.fixture <_pytest.python.fixture>` -marked ``smtp_connection`` fixture function. Running the test looks like this:: +marked ``smtp_connection`` fixture function. Running the test looks like this: + +.. code-block:: pytest $ pytest test_smtpsimple.py =========================== test session starts ============================ @@ -204,7 +206,9 @@ located):: assert 0 # for demo purposes We deliberately insert failing ``assert 0`` statements in order to -inspect what is going on and can now run the tests:: +inspect what is going on and can now run the tests: + +.. code-block:: pytest $ pytest test_module.py =========================== test session starts ============================ @@ -482,7 +486,9 @@ server URL in its module namespace:: def test_showhelo(smtp_connection): assert 0, smtp_connection.helo() -Running it:: +Running it: + +.. code-block:: pytest $ pytest -qq --tb=short test_anothersmtp.py F [100%] @@ -584,7 +590,9 @@ The main change is the declaration of ``params`` with :py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values for each of which the fixture function will execute and can access a value via ``request.param``. No test function code needs to change. -So let's just do another run:: +So let's just do another run: + +.. code-block:: pytest $ pytest -q test_module.py FFFF [100%] @@ -686,7 +694,9 @@ a function which will be called with the fixture value and then has to return a string to use. In the latter case if the function return ``None`` then pytest's auto-generated ID will be used. -Running the above tests results in the following test IDs being used:: +Running the above tests results in the following test IDs being used: + +.. code-block:: pytest $ pytest --collect-only =========================== test session starts ============================ @@ -728,7 +738,9 @@ Example:: def test_data(data_set): pass -Running this test will *skip* the invocation of ``data_set`` with value ``2``:: +Running this test will *skip* the invocation of ``data_set`` with value ``2``: + +.. code-block:: pytest $ pytest test_fixture_marks.py -v =========================== test session starts ============================ @@ -771,7 +783,9 @@ and instantiate an object ``app`` where we stick the already defined assert app.smtp_connection Here we declare an ``app`` fixture which receives the previously defined -``smtp_connection`` fixture and instantiates an ``App`` object with it. Let's run it:: +``smtp_connection`` fixture and instantiates an ``App`` object with it. Let's run it: + +.. code-block:: pytest $ pytest -v test_appsetup.py =========================== test session starts ============================ @@ -840,7 +854,9 @@ to show the setup/teardown flow:: print(" RUN test2 with otherarg %s and modarg %s" % (otherarg, modarg)) -Let's run the tests in verbose mode and with looking at the print-output:: +Let's run the tests in verbose mode and with looking at the print-output: + +.. code-block:: pytest $ pytest -v -s test_module.py =========================== test session starts ============================ @@ -942,7 +958,9 @@ and declare its use in a test module via a ``usefixtures`` marker:: Due to the ``usefixtures`` marker, the ``cleandir`` fixture will be required for the execution of each test method, just as if you specified a "cleandir" function argument to each of them. Let's run it -to verify our fixture is activated and the tests pass:: +to verify our fixture is activated and the tests pass: + +.. code-block:: pytest $ pytest -q .. [100%] @@ -1041,7 +1059,9 @@ which implies that all test methods in the class will use this fixture without a need to state it in the test function signature or with a class-level ``usefixtures`` decorator. -If we run it, we get two passing tests:: +If we run it, we get two passing tests: + +.. code-block:: pytest $ pytest -q .. [100%] diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 97ae2abdc..22ffe7bd4 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -43,7 +43,9 @@ Create a simple test function with just four lines of code:: def test_answer(): assert func(3) == 5 -That’s it. You can now execute the test function:: +That’s it. You can now execute the test function: + +.. code-block:: pytest $ pytest =========================== test session starts ============================ @@ -90,7 +92,9 @@ Use the ``raises`` helper to assert that some code raises an exception:: with pytest.raises(SystemExit): f() -Execute the test function with “quiet” reporting mode:: +Execute the test function with “quiet” reporting mode: + +.. code-block:: pytest $ pytest -q test_sysexit.py . [100%] @@ -111,7 +115,9 @@ 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. We can simply run the module by passing its filename: + +.. code-block:: pytest $ pytest -q test_class.py .F [100%] @@ -141,7 +147,9 @@ Request a unique temporary directory for functional tests print(tmpdir) assert 0 -List the name ``tmpdir`` in the test function signature and ``pytest`` will lookup and call a fixture factory to create the resource before performing the test function call. Before the test runs, ``pytest`` creates a unique-per-test-invocation temporary directory:: +List the name ``tmpdir`` in the test function signature and ``pytest`` will lookup and call a fixture factory to create the resource before performing the test function call. Before the test runs, ``pytest`` creates a unique-per-test-invocation temporary directory: + +.. code-block:: pytest $ pytest -q test_tmpdir.py F [100%] diff --git a/doc/en/index.rst b/doc/en/index.rst index 510df4a09..7c201fbd7 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -22,7 +22,9 @@ An example of a simple test: assert inc(3) == 5 -To execute it:: +To execute it: + +.. code-block:: pytest $ pytest =========================== test session starts ============================ diff --git a/doc/en/parametrize.rst b/doc/en/parametrize.rst index d3bce1b58..099b531c2 100644 --- a/doc/en/parametrize.rst +++ b/doc/en/parametrize.rst @@ -50,7 +50,9 @@ to an expected output:: Here, the ``@parametrize`` decorator defines three different ``(test_input,expected)`` tuples so that the ``test_eval`` function will run three times using -them in turn:: +them in turn: + +.. code-block:: pytest $ pytest =========================== test session starts ============================ @@ -99,7 +101,9 @@ for example with the builtin ``mark.xfail``:: def test_eval(test_input, expected): assert eval(test_input) == expected -Let's run this:: +Let's run this: + +.. code-block:: pytest $ pytest =========================== test session starts ============================ @@ -172,7 +176,9 @@ If we now pass two stringinput values, our test will run twice:: .. [100%] 2 passed in 0.12 seconds -Let's also run with a stringinput that will lead to a failing test:: +Let's also run with a stringinput that will lead to a failing test: + +.. code-block:: pytest $ pytest -q --stringinput="!" test_strings.py F [100%] @@ -194,7 +200,9 @@ As expected our test function fails. If you don't specify a stringinput it will be skipped because ``metafunc.parametrize()`` will be called with an empty parameter -list:: +list: + +.. code-block:: pytest $ pytest -q -rs test_strings.py s [100%] diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index 075886a96..ae1dc7149 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -323,7 +323,9 @@ Here is a simple test file with the several usages: .. literalinclude:: example/xfail_demo.py -Running it with the report-on-xfail option gives this output:: +Running it with the report-on-xfail option gives this output: + +.. code-block:: pytest example $ pytest -rx xfail_demo.py =========================== test session starts ============================ diff --git a/doc/en/tmpdir.rst b/doc/en/tmpdir.rst index 12a3e5641..8c21e17e5 100644 --- a/doc/en/tmpdir.rst +++ b/doc/en/tmpdir.rst @@ -35,7 +35,9 @@ created in the `base temporary directory`_. assert 0 Running this would result in a passed test except for the last -``assert 0`` line which we use to look at values:: +``assert 0`` line which we use to look at values: + +.. code-block:: pytest $ pytest test_tmp_path.py =========================== test session starts ============================ @@ -95,7 +97,9 @@ and more. Here is an example test usage:: assert 0 Running this would result in a passed test except for the last -``assert 0`` line which we use to look at values:: +``assert 0`` line which we use to look at values: + +.. code-block:: pytest $ pytest test_tmpdir.py =========================== test session starts ============================ diff --git a/doc/en/unittest.rst b/doc/en/unittest.rst index 5a6561d4b..34c8a35db 100644 --- a/doc/en/unittest.rst +++ b/doc/en/unittest.rst @@ -122,7 +122,9 @@ fixture definition:: The ``@pytest.mark.usefixtures("db_class")`` class-decorator makes sure that the pytest fixture function ``db_class`` is called once per class. Due to the deliberately failing assert statements, we can take a look at -the ``self.db`` values in the traceback:: +the ``self.db`` values in the traceback: + +.. code-block:: pytest $ pytest test_unittest_db.py =========================== test session starts ============================ @@ -199,7 +201,9 @@ used for all methods of the class where it is defined. This is a shortcut for using a ``@pytest.mark.usefixtures("initdir")`` marker on the class like in the previous example. -Running this test module ...:: +Running this test module ...: + +.. code-block:: pytest $ pytest -q test_unittest_cleandir.py . [100%] diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 43f20394f..6c42cd0ec 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -150,7 +150,9 @@ Detailed summary report The ``-r`` flag can be used to display test results summary at the end of the test session, making it easy in large test suites to get a clear picture of all failures, skips, xfails, etc. -Example:: +Example: + +.. code-block:: pytest $ pytest -ra =========================== test session starts ============================ @@ -173,7 +175,9 @@ Here is the full list of available characters that can be used: - ``P`` - passed with output - ``a`` - all except ``pP`` -More than one character can be used, so for example to only see failed and skipped tests, you can execute:: +More than one character can be used, so for example to only see failed and skipped tests, you can execute: + +.. code-block:: pytest $ pytest -rfs =========================== test session starts ============================ diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index 182aae4fa..3e69d3480 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -18,7 +18,9 @@ and displays them at the end of the session:: def test_one(): assert api_v1() == 1 -Running pytest now produces this output:: +Running pytest now produces this output: + +.. code-block:: pytest $ pytest test_show_warnings.py =========================== test session starts ============================ @@ -37,7 +39,9 @@ Running pytest now produces this output:: =================== 1 passed, 1 warnings in 0.12 seconds =================== The ``-W`` flag can be passed to control which warnings will be displayed or even turn -them into errors:: +them into errors: + +.. code-block:: pytest $ pytest -q test_show_warnings.py -W error::UserWarning F [100%] @@ -347,7 +351,7 @@ defines an ``__init__`` constructor, as this prevents the class from being insta def test_foo(self): assert 1 == 1 -:: +.. code-block:: pytest $ pytest test_pytest_warnings.py -q diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index bee3d6102..70bf315aa 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -388,26 +388,27 @@ return a result object, with which we can assert the tests' outcomes. additionally it is possible to copy examples for an example folder before running pytest on it -.. code:: ini +.. code-block:: ini # content of pytest.ini [pytest] pytester_example_dir = . -.. code:: python +.. code-block:: python # content of test_example.py def test_plugin(testdir): - testdir.copy_example("test_example.py") - testdir.runpytest("-k", "test_example") + testdir.copy_example("test_example.py") + testdir.runpytest("-k", "test_example") + def test_example(): - pass + pass -.. code:: +.. code-block:: pytest $ pytest =========================== test session starts ============================ diff --git a/tox.ini b/tox.ini index 92cdfc85a..1244206a0 100644 --- a/tox.ini +++ b/tox.ini @@ -126,6 +126,7 @@ usedevelop = True changedir = doc/en deps = PyYAML + pygments-pytest>=1.0.2 sphinx sphinxcontrib-trio From 9e522c97fa7533e836bb2691533aae7d5a6c26ba Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 24 Nov 2018 08:29:39 -0200 Subject: [PATCH 37/44] Try to fix/improve reporting of test_request_garbage * Run it in a subprocess to reduce possible intereference from the outer pytest * Print the leaked objects to see if they provide any hints --- testing/python/fixture.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 1008bd3d8..86cd29724 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -526,15 +526,8 @@ class TestRequestBasic(object): try: gc.collect() - leaked_types = sum(1 for _ in gc.garbage - if isinstance(_, PseudoFixtureDef)) - - # debug leaked types if the test fails - print(leaked_types) - - gc.garbage[:] = [] - - assert leaked_types == 0 + leaked = [x for _ in gc.garbage if isinstance(_, PseudoFixtureDef)] + assert leaked == [] finally: gc.set_debug(original) @@ -542,7 +535,7 @@ class TestRequestBasic(object): pass """ ) - result = testdir.runpytest() + result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines("* 1 passed in *") def test_getfixturevalue_recursive(self, testdir): From d219e033e9081413a5c5c6c10b6fb4df7891d528 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 24 Nov 2018 14:48:12 -0800 Subject: [PATCH 38/44] Fix rtd by installing pygments-pytest --- doc/en/requirements.txt | 1 + tox.ini | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt index e3cc47ed5..9594e76e3 100644 --- a/doc/en/requirements.txt +++ b/doc/en/requirements.txt @@ -1,3 +1,4 @@ +pygments-pytest>=1.0.2 # pinning sphinx to 1.4.* due to search issues with rtd: # https://github.com/rtfd/readthedocs-sphinx-ext/issues/25 sphinx ==1.4.* diff --git a/tox.ini b/tox.ini index 1244206a0..711954720 100644 --- a/tox.ini +++ b/tox.ini @@ -124,11 +124,7 @@ setenv = {[testenv:py27-pluggymaster]setenv} skipsdist = True usedevelop = True changedir = doc/en -deps = - PyYAML - pygments-pytest>=1.0.2 - sphinx - sphinxcontrib-trio +deps = -r{toxinidir}/doc/en/requirements.txt commands = sphinx-build -W -b html . _build From 2eac1bfcb87d63ba245e3dd1a0b9d6d6d48f1cf6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 24 Nov 2018 17:25:53 -0800 Subject: [PATCH 39/44] Bump pygments-pytest --- doc/en/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt index 9594e76e3..b9fdcb8f6 100644 --- a/doc/en/requirements.txt +++ b/doc/en/requirements.txt @@ -1,4 +1,4 @@ -pygments-pytest>=1.0.2 +pygments-pytest>=1.0.4 # pinning sphinx to 1.4.* due to search issues with rtd: # https://github.com/rtfd/readthedocs-sphinx-ext/issues/25 sphinx ==1.4.* From e9b2475e291d49c23beba6d35dbd08222d1b2763 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 25 Nov 2018 09:33:18 -0800 Subject: [PATCH 40/44] Display actual test ids in `--collect-only` --- changelog/4458.bugfix.rst | 1 + src/_pytest/nodes.py | 2 +- testing/python/collect.py | 18 ++++++++++++----- testing/python/metafunc.py | 6 +++--- testing/test_collection.py | 40 +++++++++++++++++++------------------- testing/test_terminal.py | 11 +++++------ 6 files changed, 43 insertions(+), 35 deletions(-) create mode 100644 changelog/4458.bugfix.rst diff --git a/changelog/4458.bugfix.rst b/changelog/4458.bugfix.rst new file mode 100644 index 000000000..891fb9a2f --- /dev/null +++ b/changelog/4458.bugfix.rst @@ -0,0 +1 @@ +Display actual test ids in ``--collect-only``. diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 86e541152..77e6f02c1 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -138,7 +138,7 @@ class Node(object): return cls def __repr__(self): - return "<%s %r>" % (self.__class__.__name__, getattr(self, "name", None)) + return "<%s %s>" % (self.__class__.__name__, getattr(self, "name", None)) def warn(self, _code_or_warning=None, message=None, code=None): """Issue a warning for this item. diff --git a/testing/python/collect.py b/testing/python/collect.py index 2e5d62dd5..4ce3d120d 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -489,26 +489,34 @@ class TestFunction(object): ] ) - def test_function_equality(self, testdir, tmpdir): + @staticmethod + def make_function(testdir, **kwargs): from _pytest.fixtures import FixtureManager config = testdir.parseconfigure() session = testdir.Session(config) session._fixturemanager = FixtureManager(session) + return pytest.Function(config=config, parent=session, **kwargs) + + def test_function_equality(self, testdir, tmpdir): def func1(): pass def func2(): pass - f1 = pytest.Function( - name="name", parent=session, config=config, args=(1,), callobj=func1 - ) + f1 = self.make_function(testdir, name="name", args=(1,), callobj=func1) assert f1 == f1 - f2 = pytest.Function(name="name", config=config, callobj=func2, parent=session) + f2 = self.make_function(testdir, name="name", callobj=func2) assert f1 != f2 + def test_repr_produces_actual_test_id(self, testdir): + f = self.make_function( + testdir, name=r"test[\xe5]", callobj=self.test_repr_produces_actual_test_id + ) + assert repr(f) == r"" + def test_issue197_parametrize_emptyset(self, testdir): testdir.makepyfile( """ diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 0d5b6037f..243d50d2d 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -474,9 +474,9 @@ class TestMetafunc(object): result = testdir.runpytest("--collect-only", SHOW_PYTEST_WARNINGS_ARG) result.stdout.fnmatch_lines( [ - "", - " ", - " ", + "", + " ", + " ", "*test_parametrize_ids_exception.py:6: *parameter arg at position 0*", "*test_parametrize_ids_exception.py:6: *parameter arg at position 1*", ] diff --git a/testing/test_collection.py b/testing/test_collection.py index fae23025e..473883b0d 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -950,10 +950,10 @@ def test_collect_init_tests(testdir): [ "collected 2 items", "", - " ", - " ", - " ", + " ", + " ", + " ", + " ", ] ) result = testdir.runpytest("./tests", "--collect-only") @@ -961,10 +961,10 @@ def test_collect_init_tests(testdir): [ "collected 2 items", "", - " ", - " ", - " ", + " ", + " ", + " ", + " ", ] ) # Ignores duplicates with "." and pkginit (#4310). @@ -972,11 +972,11 @@ def test_collect_init_tests(testdir): result.stdout.fnmatch_lines( [ "collected 2 items", - "", - " ", - " ", - " ", - " ", + "", + " ", + " ", + " ", + " ", ] ) # Same as before, but different order. @@ -984,21 +984,21 @@ def test_collect_init_tests(testdir): result.stdout.fnmatch_lines( [ "collected 2 items", - "", - " ", - " ", - " ", - " ", + "", + " ", + " ", + " ", + " ", ] ) result = testdir.runpytest("./tests/test_foo.py", "--collect-only") result.stdout.fnmatch_lines( - ["", " ", " "] + ["", " ", " "] ) assert "test_init" not in result.stdout.str() result = testdir.runpytest("./tests/__init__.py", "--collect-only") result.stdout.fnmatch_lines( - ["", " ", " "] + ["", " ", " "] ) assert "test_foo" not in result.stdout.str() diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 86ec1cd07..60a64cdd6 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -263,7 +263,7 @@ class TestCollectonly(object): ) result = testdir.runpytest("--collect-only") result.stdout.fnmatch_lines( - ["", " "] + ["", " "] ) def test_collectonly_skipped_module(self, testdir): @@ -307,11 +307,10 @@ class TestCollectonly(object): assert result.ret == 0 result.stdout.fnmatch_lines( [ - "*", - "* ", - "* ", - # "* ", - "* ", + "*", + "* ", + "* ", + "* ", ] ) From b00b715cb6a7f6b2455bcf80aad875432f98bd38 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 26 Nov 2018 11:35:34 -0200 Subject: [PATCH 41/44] [skip travis] Use retry script on AppVeyor Fix #4387 --- scripts/appveyor-retry.cmd | 21 +++++++++++++++++++++ scripts/upload-coverage.bat | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 scripts/appveyor-retry.cmd diff --git a/scripts/appveyor-retry.cmd b/scripts/appveyor-retry.cmd new file mode 100644 index 000000000..ac3836508 --- /dev/null +++ b/scripts/appveyor-retry.cmd @@ -0,0 +1,21 @@ +@echo off +rem Source: https://github.com/appveyor/ci/blob/master/scripts/appveyor-retry.cmd +rem initiate the retry number +set retryNumber=0 +set maxRetries=3 + +:RUN +%* +set LastErrorLevel=%ERRORLEVEL% +IF %LastErrorLevel% == 0 GOTO :EOF +set /a retryNumber=%retryNumber%+1 +IF %reTryNumber% == %maxRetries% (GOTO :FAILED) + +:RETRY +set /a retryNumberDisp=%retryNumber%+1 +@echo Command "%*" failed with exit code %LastErrorLevel%. Retrying %retryNumberDisp% of %maxRetries% +GOTO :RUN + +: FAILED +@echo Sorry, we tried running command for %maxRetries% times and all attempts were unsuccessful! +EXIT /B %LastErrorLevel% diff --git a/scripts/upload-coverage.bat b/scripts/upload-coverage.bat index 97553676c..8a8d94c98 100644 --- a/scripts/upload-coverage.bat +++ b/scripts/upload-coverage.bat @@ -5,7 +5,7 @@ if not defined PYTEST_NO_COVERAGE ( C:\Python36\Scripts\coverage combine C:\Python36\Scripts\coverage xml --ignore-errors C:\Python36\Scripts\coverage report -m --ignore-errors - C:\Python36\Scripts\codecov --required -X gcov pycov search -f coverage.xml --flags %TOXENV:-= % windows + scripts\appveyor-retry C:\Python36\Scripts\codecov --required -X gcov pycov search -f coverage.xml --flags %TOXENV:-= % windows ) else ( echo Skipping coverage upload, PYTEST_NO_COVERAGE is set ) From 1bba0a97146575b496ac3a021f6a68e1be74ec0d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 22 Nov 2018 10:05:10 -0800 Subject: [PATCH 42/44] Deprecate `raises(..., 'code(as_a_string)')` / `warns(..., 'code(as_a_string)') --- changelog/4435.deprecation.rst | 1 + doc/en/assert.rst | 5 ++- doc/en/deprecations.rst | 35 +++++++++++++++++++ doc/en/example/assertion/failure_demo.py | 6 ++-- doc/en/example/parametrize.rst | 3 +- src/_pytest/deprecated.py | 9 +++++ src/_pytest/python_api.py | 17 ++++----- src/_pytest/recwarn.py | 2 ++ testing/code/test_code.py | 2 +- testing/code/test_excinfo.py | 6 ++-- testing/code/test_source.py | 25 +++++-------- .../sub2/conftest.py | 2 +- testing/python/collect.py | 5 +-- testing/python/fixture.py | 3 +- testing/python/metafunc.py | 8 ++--- testing/python/raises.py | 17 ++++++--- testing/test_capture.py | 10 +++--- testing/test_config.py | 7 ++-- testing/test_monkeypatch.py | 8 ++--- testing/test_parseopt.py | 6 +--- testing/test_pluginmanager.py | 10 +++--- testing/test_pytester.py | 4 +-- testing/test_recwarn.py | 17 ++++++--- testing/test_runner.py | 17 +++------ testing/test_session.py | 8 ++--- testing/test_terminal.py | 3 +- 26 files changed, 140 insertions(+), 96 deletions(-) create mode 100644 changelog/4435.deprecation.rst diff --git a/changelog/4435.deprecation.rst b/changelog/4435.deprecation.rst new file mode 100644 index 000000000..f12f0bc6c --- /dev/null +++ b/changelog/4435.deprecation.rst @@ -0,0 +1 @@ +Deprecate ``raises(..., 'code(as_a_string)')`` and ``warns(..., 'code(as_a_string)')``. See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec diff --git a/doc/en/assert.rst b/doc/en/assert.rst index 43fedebed..b13a071f6 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -100,10 +100,9 @@ If you want to write test code that works on Python 2.4 as well, you may also use two other ways to test for an expected exception:: pytest.raises(ExpectedException, func, *args, **kwargs) - pytest.raises(ExpectedException, "func(*args, **kwargs)") -both of which execute the specified function with args and kwargs and -asserts that the given ``ExpectedException`` is raised. The reporter will +which will execute the specified function with args and kwargs and +assert that the given ``ExpectedException`` is raised. The reporter will provide you with helpful output in case of failures such as *no exception* or *wrong exception*. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 3398c92a2..414e2e3f3 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -14,6 +14,41 @@ Below is a complete list of all pytest features which are considered deprecated. :class:`_pytest.warning_types.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters `. +.. _raises-warns-exec: + +``raises`` / ``warns`` with a string as the second argument +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 4.1 + +Use the context manager form of these instead. When necessary, invoke ``exec`` +directly. + +Example: + +.. code-block:: python + + pytest.raises(ZeroDivisionError, "1 / 0") + pytest.raises(SyntaxError, "a $ b") + + pytest.warns(DeprecationWarning, "my_function()") + pytest.warns(SyntaxWarning, "assert(1, 2)") + +Becomes: + +.. code-block:: python + + with pytest.raises(ZeroDivisionError): + 1 / 0 + with pytest.raises(SyntaxError): + exec("a $ b") # exec is required for invalid syntax + + with pytest.warns(DeprecationWarning): + my_function() + with pytest.warns(SyntaxWarning): + exec("assert(1, 2)") # exec is used to avoid a top-level warning + + Internal classes accessed through ``Node`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/en/example/assertion/failure_demo.py b/doc/en/example/assertion/failure_demo.py index 5bd95a37b..31a9f2577 100644 --- a/doc/en/example/assertion/failure_demo.py +++ b/doc/en/example/assertion/failure_demo.py @@ -165,11 +165,11 @@ def globf(x): class TestRaises(object): def test_raises(self): - s = "qwe" # NOQA - raises(TypeError, "int(s)") + s = "qwe" + raises(TypeError, int, s) def test_raises_doesnt(self): - raises(IOError, "int('3')") + raises(IOError, int, "3") def test_raise(self): raise ValueError("demo error") diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 488f6e310..bb8ea5996 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -388,7 +388,8 @@ parametrizer`_ but in a lot less code:: assert a == b def test_zerodivision(self, a, b): - pytest.raises(ZeroDivisionError, "a/b") + with pytest.raises(ZeroDivisionError): + a / b Our test generator looks up a class-level definition which specifies which argument sets to use for each test function. Let's run it: diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index a34366280..ce3b91802 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -92,6 +92,15 @@ NODE_WARN = RemovedInPytest4Warning( "Node.warn(code, message) form has been deprecated, use Node.warn(warning_instance) instead." ) +RAISES_EXEC = PytestDeprecationWarning( + "raises(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly\n\n" + "See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec" +) +WARNS_EXEC = PytestDeprecationWarning( + "warns(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly.\n\n" + "See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec" +) + RECORD_XML_PROPERTY = RemovedInPytest4Warning( 'Fixture renamed from "record_xml_property" to "record_property" as user ' "properties are now available to all reporters.\n" diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index e9cc2bbde..7e5dc74a8 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -1,6 +1,9 @@ +from __future__ import absolute_import + import math import pprint import sys +import warnings from decimal import Decimal from numbers import Number @@ -14,6 +17,7 @@ from _pytest.compat import isclass from _pytest.compat import Mapping from _pytest.compat import Sequence from _pytest.compat import STRING_TYPES +from _pytest.deprecated import RAISES_EXEC from _pytest.outcomes import fail BASE_TYPE = (type, STRING_TYPES) @@ -604,9 +608,9 @@ def raises(expected_exception, *args, **kwargs): >>> with raises(ValueError, match=r'must be \d+$'): ... raise ValueError("value must be 42") - **Legacy forms** + **Legacy form** - The forms below are fully supported but are discouraged for new code because the + The form below is fully supported but discouraged for new code because the context manager form is regarded as more readable and less error-prone. It is possible to specify a callable by passing a to-be-called lambda:: @@ -623,14 +627,6 @@ def raises(expected_exception, *args, **kwargs): >>> raises(ZeroDivisionError, f, x=0) - It is also possible to pass a string to be evaluated at runtime:: - - >>> raises(ZeroDivisionError, "f(0)") - - - The string will be evaluated using the same ``locals()`` and ``globals()`` - at the moment of the ``raises`` call. - .. currentmodule:: _pytest._code Consult the API of ``excinfo`` objects: :class:`ExceptionInfo`. @@ -672,6 +668,7 @@ def raises(expected_exception, *args, **kwargs): raise TypeError(msg) return RaisesContext(expected_exception, message, match_expr) elif isinstance(args[0], str): + warnings.warn(RAISES_EXEC, stacklevel=2) code, = args assert isinstance(code, str) frame = sys._getframe(1) diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 4f3ab7f29..f39f7aee7 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -11,6 +11,7 @@ import warnings import six import _pytest._code +from _pytest.deprecated import WARNS_EXEC from _pytest.fixtures import yield_fixture from _pytest.outcomes import fail @@ -89,6 +90,7 @@ def warns(expected_warning, *args, **kwargs): match_expr = kwargs.pop("match") return WarningsChecker(expected_warning, match_expr=match_expr) elif isinstance(args[0], str): + warnings.warn(WARNS_EXEC, stacklevel=2) code, = args assert isinstance(code, str) frame = sys._getframe(1) diff --git a/testing/code/test_code.py b/testing/code/test_code.py index df9f109ef..3362d4604 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -37,7 +37,7 @@ def test_code_with_class(): class A(object): pass - pytest.raises(TypeError, "_pytest._code.Code(A)") + pytest.raises(TypeError, _pytest._code.Code, A) def x(): diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index b4d64313c..4e36fb946 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -180,7 +180,8 @@ class TestTraceback_f_g_h(object): def test_traceback_cut_excludepath(self, testdir): p = testdir.makepyfile("def f(): raise ValueError") - excinfo = pytest.raises(ValueError, "p.pyimport().f()") + with pytest.raises(ValueError) as excinfo: + p.pyimport().f() basedir = py.path.local(pytest.__file__).dirpath() newtraceback = excinfo.traceback.cut(excludepath=basedir) for x in newtraceback: @@ -336,7 +337,8 @@ class TestTraceback_f_g_h(object): def test_excinfo_exconly(): excinfo = pytest.raises(ValueError, h) assert excinfo.exconly().startswith("ValueError") - excinfo = pytest.raises(ValueError, "raise ValueError('hello\\nworld')") + with pytest.raises(ValueError) as excinfo: + raise ValueError("hello\nworld") msg = excinfo.exconly(tryshort=True) assert msg.startswith("ValueError") assert msg.endswith("world") diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 3ee46c1b8..0103acb70 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -6,6 +6,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import ast import inspect import sys @@ -14,7 +15,6 @@ import six import _pytest._code import pytest from _pytest._code import Source -from _pytest._code.source import ast astonly = pytest.mark.nothing @@ -306,8 +306,6 @@ class TestSourceParsingAndCompiling(object): pytest.raises(SyntaxError, lambda: source.getstatementrange(0)) def test_compile_to_ast(self): - import ast - source = Source("x = 4") mod = source.compile(flag=ast.PyCF_ONLY_AST) assert isinstance(mod, ast.Module) @@ -317,10 +315,9 @@ class TestSourceParsingAndCompiling(object): co = self.source.compile() six.exec_(co, globals()) f(7) - excinfo = pytest.raises(AssertionError, "f(6)") + excinfo = pytest.raises(AssertionError, f, 6) frame = excinfo.traceback[-1].frame stmt = frame.code.fullsource.getstatement(frame.lineno) - # print "block", str(block) assert str(stmt).strip().startswith("assert") @pytest.mark.parametrize("name", ["", None, "my"]) @@ -361,17 +358,13 @@ def test_getline_finally(): def c(): pass - excinfo = pytest.raises( - TypeError, - """ - teardown = None - try: - c(1) - finally: - if teardown: - teardown() - """, - ) + with pytest.raises(TypeError) as excinfo: + teardown = None + try: + c(1) + finally: + if teardown: + teardown() source = excinfo.traceback[-1].statement assert str(source).strip() == "c(1)" diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py index c37045454..00981c5dc 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py @@ -3,4 +3,4 @@ import pytest @pytest.fixture def arg2(request): - pytest.raises(Exception, "request.getfixturevalue('arg1')") + pytest.raises(Exception, request.getfixturevalue, "arg1") diff --git a/testing/python/collect.py b/testing/python/collect.py index 4ce3d120d..83fcdc3bd 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -325,7 +325,7 @@ class TestGenerator(object): assert len(colitems) == 1 gencol = colitems[0] assert isinstance(gencol, pytest.Generator) - pytest.raises(ValueError, "gencol.collect()") + pytest.raises(ValueError, gencol.collect) def test_generative_methods_with_explicit_names(self, testdir): modcol = testdir.getmodulecol( @@ -1103,7 +1103,8 @@ def test_modulecol_roundtrip(testdir): class TestTracebackCutting(object): def test_skip_simple(self): - excinfo = pytest.raises(pytest.skip.Exception, 'pytest.skip("xxx")') + with pytest.raises(pytest.skip.Exception) as excinfo: + pytest.skip("xxx") assert excinfo.traceback[-1].frame.code.name == "skip" assert excinfo.traceback[-1].ishidden() diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 86cd29724..b7ce16eb5 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -906,7 +906,8 @@ class TestRequestMarking(object): assert "skipif" not in item1.keywords req1.applymarker(pytest.mark.skipif) assert "skipif" in item1.keywords - pytest.raises(ValueError, "req1.applymarker(42)") + with pytest.raises(ValueError): + req1.applymarker(42) def test_accesskeywords(self, testdir): testdir.makepyfile( diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 243d50d2d..833eb5641 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -70,11 +70,11 @@ class TestMetafunc(object): pass metafunc = self.Metafunc(func) - pytest.raises(ValueError, "metafunc.addcall(id=None)") + pytest.raises(ValueError, metafunc.addcall, id=None) metafunc.addcall(id=1) - pytest.raises(ValueError, "metafunc.addcall(id=1)") - pytest.raises(ValueError, "metafunc.addcall(id='1')") + pytest.raises(ValueError, metafunc.addcall, id=1) + pytest.raises(ValueError, metafunc.addcall, id="1") metafunc.addcall(id=2) assert len(metafunc._calls) == 2 assert metafunc._calls[0].id == "1" @@ -108,7 +108,7 @@ class TestMetafunc(object): metafunc.addcall(funcargs={"x": 2}) metafunc.addcall(funcargs={"x": 3}) - pytest.raises(pytest.fail.Exception, "metafunc.addcall({'xyz': 0})") + pytest.raises(pytest.fail.Exception, metafunc.addcall, {"xyz": 0}) assert len(metafunc._calls) == 2 assert metafunc._calls[0].funcargs == {"x": 2} assert metafunc._calls[1].funcargs == {"x": 3} diff --git a/testing/python/raises.py b/testing/python/raises.py index 6ca19c677..e3a0c4a05 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -4,25 +4,32 @@ import six import pytest from _pytest.outcomes import Failed +from _pytest.warning_types import PytestDeprecationWarning class TestRaises(object): def test_raises(self): source = "int('qwe')" - excinfo = pytest.raises(ValueError, source) + with pytest.warns(PytestDeprecationWarning): + excinfo = pytest.raises(ValueError, source) code = excinfo.traceback[-1].frame.code s = str(code.fullsource) assert s == source def test_raises_exec(self): - pytest.raises(ValueError, "a,x = []") + with pytest.warns(PytestDeprecationWarning) as warninfo: + pytest.raises(ValueError, "a,x = []") + assert warninfo[0].filename == __file__ def test_raises_exec_correct_filename(self): - excinfo = pytest.raises(ValueError, 'int("s")') - assert __file__ in excinfo.traceback[-1].path + with pytest.warns(PytestDeprecationWarning): + excinfo = pytest.raises(ValueError, 'int("s")') + assert __file__ in excinfo.traceback[-1].path def test_raises_syntax_error(self): - pytest.raises(SyntaxError, "qwe qwe qwe") + with pytest.warns(PytestDeprecationWarning) as warninfo: + pytest.raises(SyntaxError, "qwe qwe qwe") + assert warninfo[0].filename == __file__ def test_raises_function(self): pytest.raises(ValueError, int, "hello") diff --git a/testing/test_capture.py b/testing/test_capture.py index 47aba70d4..17bb82967 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -87,7 +87,7 @@ class TestCaptureManager(object): try: capman = CaptureManager("fd") capman.start_global_capturing() - pytest.raises(AssertionError, "capman.start_global_capturing()") + pytest.raises(AssertionError, capman.start_global_capturing) capman.stop_global_capturing() finally: capouter.stop_capturing() @@ -798,10 +798,10 @@ class TestCaptureIO(object): f = capture.CaptureIO() if sys.version_info >= (3, 0): f.write("\u00f6") - pytest.raises(TypeError, "f.write(bytes('hello', 'UTF-8'))") + pytest.raises(TypeError, f.write, b"hello") else: - f.write(text_type("\u00f6", "UTF-8")) - f.write("hello") # bytes + f.write(u"\u00f6") + f.write(b"hello") s = f.getvalue() f.close() assert isinstance(s, text_type) @@ -1149,7 +1149,7 @@ class TestStdCapture(object): print("XXX which indicates an error in the underlying capturing") print("XXX mechanisms") with self.getcapture(): - pytest.raises(IOError, "sys.stdin.read()") + pytest.raises(IOError, sys.stdin.read) class TestStdCaptureFD(TestStdCapture): diff --git a/testing/test_config.py b/testing/test_config.py index 605d28aa0..fcb886d53 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -194,7 +194,7 @@ class TestConfigAPI(object): config = testdir.parseconfig("--hello=this") for x in ("hello", "--hello", "-X"): assert config.getoption(x) == "this" - pytest.raises(ValueError, "config.getoption('qweqwe')") + pytest.raises(ValueError, config.getoption, "qweqwe") @pytest.mark.skipif("sys.version_info[0] < 3") def test_config_getoption_unicode(self, testdir): @@ -211,7 +211,7 @@ class TestConfigAPI(object): def test_config_getvalueorskip(self, testdir): config = testdir.parseconfig() - pytest.raises(pytest.skip.Exception, "config.getvalueorskip('hello')") + pytest.raises(pytest.skip.Exception, config.getvalueorskip, "hello") verbose = config.getvalueorskip("verbose") assert verbose == config.option.verbose @@ -723,7 +723,8 @@ def test_config_in_subdirectory_colon_command_line_issue2148(testdir): def test_notify_exception(testdir, capfd): config = testdir.parseconfig() - excinfo = pytest.raises(ValueError, "raise ValueError(1)") + with pytest.raises(ValueError) as excinfo: + raise ValueError(1) config.notify_exception(excinfo) out, err = capfd.readouterr() assert "ValueError" in err diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index ebc233fbf..9e44b4975 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -27,7 +27,7 @@ def test_setattr(): x = 1 monkeypatch = MonkeyPatch() - pytest.raises(AttributeError, "monkeypatch.setattr(A, 'notexists', 2)") + pytest.raises(AttributeError, monkeypatch.setattr, A, "notexists", 2) monkeypatch.setattr(A, "y", 2, raising=False) assert A.y == 2 monkeypatch.undo() @@ -99,7 +99,7 @@ def test_delattr(): monkeypatch = MonkeyPatch() monkeypatch.delattr(A, "x") - pytest.raises(AttributeError, "monkeypatch.delattr(A, 'y')") + pytest.raises(AttributeError, monkeypatch.delattr, A, "y") monkeypatch.delattr(A, "y", raising=False) monkeypatch.setattr(A, "x", 5, raising=False) assert A.x == 5 @@ -156,7 +156,7 @@ def test_delitem(): monkeypatch.delitem(d, "x") assert "x" not in d monkeypatch.delitem(d, "y", raising=False) - pytest.raises(KeyError, "monkeypatch.delitem(d, 'y')") + pytest.raises(KeyError, monkeypatch.delitem, d, "y") assert not d monkeypatch.setitem(d, "y", 1700) assert d["y"] == 1700 @@ -182,7 +182,7 @@ def test_delenv(): name = "xyz1234" assert name not in os.environ monkeypatch = MonkeyPatch() - pytest.raises(KeyError, "monkeypatch.delenv(%r, raising=True)" % name) + pytest.raises(KeyError, monkeypatch.delenv, name, raising=True) monkeypatch.delenv(name, raising=False) monkeypatch.undo() os.environ[name] = "1" diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index 0dafa248b..3048c96bd 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -100,12 +100,8 @@ class TestParser(object): def test_group_shortopt_lowercase(self, parser): group = parser.getgroup("hello") - pytest.raises( - ValueError, - """ + with pytest.raises(ValueError): group.addoption("-x", action="store_true") - """, - ) assert len(group.options) == 0 group._addoption("-x", action="store_true") assert len(group.options) == 1 diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 64d05d383..6137b2771 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -196,7 +196,7 @@ class TestPytestPluginManager(object): assert pm.is_registered(mod) values = pm.get_plugins() assert mod in values - pytest.raises(ValueError, "pm.register(mod)") + pytest.raises(ValueError, pm.register, mod) pytest.raises(ValueError, lambda: pm.register(mod)) # assert not pm.is_registered(mod2) assert pm.get_plugins() == values @@ -284,8 +284,8 @@ class TestPytestPluginManager(object): result.stdout.fnmatch_lines(["*1 passed*"]) def test_import_plugin_importname(self, testdir, pytestpm): - pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")') - pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwx.y")') + pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y") + pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwx.y") testdir.syspathinsert() pluginname = "pytest_hello" @@ -301,8 +301,8 @@ class TestPytestPluginManager(object): assert plugin2 is plugin1 def test_import_plugin_dotted_name(self, testdir, pytestpm): - pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")') - pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwex.y")') + pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y") + pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwex.y") testdir.syspathinsert() testdir.mkpydir("pkg").join("plug.py").write("x=3") diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 0c28bc91b..669da6e17 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -71,7 +71,7 @@ def test_make_hook_recorder(testdir): recorder.unregister() recorder.clear() recorder.hook.pytest_runtest_logreport(report=rep) - pytest.raises(ValueError, "recorder.getfailures()") + pytest.raises(ValueError, recorder.getfailures) def test_parseconfig(testdir): @@ -174,7 +174,7 @@ def test_hookrecorder_basic(holder): call = rec.popcall("pytest_xyz") assert call.arg == 123 assert call._name == "pytest_xyz" - pytest.raises(pytest.fail.Exception, "rec.popcall('abc')") + pytest.raises(pytest.fail.Exception, rec.popcall, "abc") pm.hook.pytest_xyz_noarg() call = rec.popcall("pytest_xyz_noarg") assert call._name == "pytest_xyz_noarg" diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 223521a5e..9bf6a2ffb 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -7,6 +7,7 @@ import warnings import pytest from _pytest.recwarn import WarningsRecorder +from _pytest.warning_types import PytestDeprecationWarning def test_recwarn_stacklevel(recwarn): @@ -44,7 +45,7 @@ class TestWarningsRecorderChecker(object): rec.clear() assert len(rec.list) == 0 assert values is rec.list - pytest.raises(AssertionError, "rec.pop()") + pytest.raises(AssertionError, rec.pop) @pytest.mark.issue(4243) def test_warn_stacklevel(self): @@ -214,9 +215,17 @@ class TestWarns(object): source1 = "warnings.warn('w1', RuntimeWarning)" source2 = "warnings.warn('w2', RuntimeWarning)" source3 = "warnings.warn('w3', RuntimeWarning)" - pytest.warns(RuntimeWarning, source1) - pytest.raises(pytest.fail.Exception, lambda: pytest.warns(UserWarning, source2)) - pytest.warns(RuntimeWarning, source3) + with pytest.warns(PytestDeprecationWarning) as warninfo: # yo dawg + pytest.warns(RuntimeWarning, source1) + pytest.raises( + pytest.fail.Exception, lambda: pytest.warns(UserWarning, source2) + ) + pytest.warns(RuntimeWarning, source3) + assert len(warninfo) == 3 + for w in warninfo: + assert w.filename == __file__ + msg, = w.message.args + assert msg.startswith("warns(..., 'code(as_a_string)') is deprecated") def test_function(self): pytest.warns( diff --git a/testing/test_runner.py b/testing/test_runner.py index 2d047af70..d76f3da9b 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -700,17 +700,13 @@ def test_importorskip(monkeypatch): # check that importorskip reports the actual call # in this test the test_runner.py file assert path.purebasename == "test_runner" - pytest.raises(SyntaxError, "pytest.importorskip('x y z')") - pytest.raises(SyntaxError, "pytest.importorskip('x=y')") + pytest.raises(SyntaxError, pytest.importorskip, "x y z") + pytest.raises(SyntaxError, pytest.importorskip, "x=y") mod = types.ModuleType("hello123") mod.__version__ = "1.3" monkeypatch.setitem(sys.modules, "hello123", mod) - pytest.raises( - pytest.skip.Exception, - """ + with pytest.raises(pytest.skip.Exception): pytest.importorskip("hello123", minversion="1.3.1") - """, - ) mod2 = pytest.importorskip("hello123", minversion="1.3") assert mod2 == mod except pytest.skip.Exception: @@ -730,11 +726,8 @@ def test_importorskip_dev_module(monkeypatch): monkeypatch.setitem(sys.modules, "mockmodule", mod) mod2 = pytest.importorskip("mockmodule", minversion="0.12.0") assert mod2 == mod - pytest.raises( - pytest.skip.Exception, - """ - pytest.importorskip('mockmodule1', minversion='0.14.0')""", - ) + with pytest.raises(pytest.skip.Exception): + pytest.importorskip("mockmodule1", minversion="0.14.0") except pytest.skip.Exception: print(_pytest._code.ExceptionInfo.from_current()) pytest.fail("spurious skip") diff --git a/testing/test_session.py b/testing/test_session.py index 0dc98a703..32d71a18e 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -243,12 +243,8 @@ class TestNewSession(SessionTests): def test_plugin_specify(testdir): - pytest.raises( - ImportError, - """ - testdir.parseconfig("-p", "nqweotexistent") - """, - ) + with pytest.raises(ImportError): + testdir.parseconfig("-p", "nqweotexistent") # pytest.raises(ImportError, # "config.do_configure(config)" # ) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 60a64cdd6..0faf9b401 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -105,7 +105,8 @@ class TestTerminal(object): def test_internalerror(self, testdir, linecomp): modcol = testdir.getmodulecol("def test_one(): pass") rep = TerminalReporter(modcol.config, file=linecomp.stringio) - excinfo = pytest.raises(ValueError, "raise ValueError('hello')") + with pytest.raises(ValueError) as excinfo: + raise ValueError("hello") rep.pytest_internalerror(excinfo.getrepr()) linecomp.assert_contains_lines(["INTERNALERROR> *ValueError*hello*"]) From 7eb28f9eb7222ea9a08d0b7ffdec29353769519f Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Aug 2018 10:26:18 +0200 Subject: [PATCH 43/44] remove yield tests and compat properties --- changelog/3079.removal.rst | 1 + changelog/3616.removal.rst | 1 + src/_pytest/compat.py | 1 - src/_pytest/deprecated.py | 17 +- src/_pytest/fixtures.py | 6 - src/_pytest/nodes.py | 35 ----- src/_pytest/nose.py | 12 -- src/_pytest/python.py | 62 ++------ src/pytest.py | 2 - testing/deprecated_test.py | 71 --------- testing/python/collect.py | 293 ----------------------------------- testing/python/fixture.py | 18 --- testing/test_collection.py | 14 -- testing/test_nose.py | 68 -------- testing/test_pdb.py | 22 --- testing/test_runner_xunit.py | 59 ------- testing/test_session.py | 14 -- testing/test_terminal.py | 24 ++- 18 files changed, 29 insertions(+), 691 deletions(-) create mode 100644 changelog/3079.removal.rst create mode 100644 changelog/3616.removal.rst diff --git a/changelog/3079.removal.rst b/changelog/3079.removal.rst new file mode 100644 index 000000000..c289176d2 --- /dev/null +++ b/changelog/3079.removal.rst @@ -0,0 +1 @@ +Remove support for yield tests - they are fundamentally broken since collection and test execution were separated. diff --git a/changelog/3616.removal.rst b/changelog/3616.removal.rst new file mode 100644 index 000000000..a88c4534a --- /dev/null +++ b/changelog/3616.removal.rst @@ -0,0 +1 @@ +Remove the deprecated compat properties for node.Class/Function/Module - use pytest... now. diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 1857f51a8..6008b8b40 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -389,7 +389,6 @@ else: COLLECT_FAKEMODULE_ATTRIBUTES = ( "Collector", "Module", - "Generator", "Function", "Instance", "Session", diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index ce3b91802..ba7426a11 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -22,28 +22,13 @@ MAIN_STR_ARGS = RemovedInPytest4Warning( "pass a list of arguments instead." ) -YIELD_TESTS = RemovedInPytest4Warning( - "yield tests are deprecated, and scheduled to be removed in pytest 4.0" -) +YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored" CACHED_SETUP = RemovedInPytest4Warning( "cached_setup is deprecated and will be removed in a future release. " "Use standard fixture functions instead." ) -COMPAT_PROPERTY = UnformattedWarning( - RemovedInPytest4Warning, - "usage of {owner}.{name} is deprecated, please use pytest.{name} instead", -) - -CUSTOM_CLASS = UnformattedWarning( - RemovedInPytest4Warning, - 'use of special named "{name}" objects in collectors of type "{type_name}" to ' - "customize the created nodes is deprecated. " - "Use pytest_pycollect_makeitem(...) to create custom " - "collection nodes instead.", -) - FUNCARG_PREFIX = UnformattedWarning( RemovedInPytest4Warning, '{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated ' diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 124b611db..924352afb 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1303,17 +1303,11 @@ class FixtureManager(object): if holderobj in self._holderobjseen: return - from _pytest.nodes import _CompatProperty - self._holderobjseen.add(holderobj) autousenames = [] for name in dir(holderobj): # The attribute can be an arbitrary descriptor, so the attribute # access below can raise. safe_getatt() ignores such exceptions. - maybe_property = safe_getattr(type(holderobj), name, None) - if isinstance(maybe_property, _CompatProperty): - # deprecated - continue obj = safe_getattr(holderobj, name, None) marker = getfixturemarker(obj) # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style) diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 77e6f02c1..1b41898c4 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -5,7 +5,6 @@ from __future__ import print_function import os import warnings -import attr import py import six @@ -56,22 +55,6 @@ def ischildnode(baseid, nodeid): return node_parts[: len(base_parts)] == base_parts -@attr.s -class _CompatProperty(object): - name = attr.ib() - - def __get__(self, obj, owner): - if obj is None: - return self - - from _pytest.deprecated import COMPAT_PROPERTY - - warnings.warn( - COMPAT_PROPERTY.format(name=self.name, owner=owner.__name__), stacklevel=2 - ) - return getattr(__import__("pytest"), self.name) - - class Node(object): """ base class for Collector and Item the test collection tree. Collector subclasses have children, Items are terminal nodes.""" @@ -119,24 +102,6 @@ class Node(object): """ fspath sensitive hook proxy used to call pytest hooks""" return self.session.gethookproxy(self.fspath) - Module = _CompatProperty("Module") - Class = _CompatProperty("Class") - Instance = _CompatProperty("Instance") - Function = _CompatProperty("Function") - File = _CompatProperty("File") - Item = _CompatProperty("Item") - - def _getcustomclass(self, name): - maybe_compatprop = getattr(type(self), name) - if isinstance(maybe_compatprop, _CompatProperty): - return getattr(__import__("pytest"), name) - else: - from _pytest.deprecated import CUSTOM_CLASS - - cls = getattr(self, name) - self.warn(CUSTOM_CLASS.format(name=name, type_name=type(self).__name__)) - return cls - def __repr__(self): return "<%s %s>" % (self.__class__.__name__, getattr(self, "name", None)) diff --git a/src/_pytest/nose.py b/src/_pytest/nose.py index 4bfa9c583..416381bb5 100644 --- a/src/_pytest/nose.py +++ b/src/_pytest/nose.py @@ -30,13 +30,6 @@ def pytest_runtest_makereport(item, call): @hookimpl(trylast=True) def pytest_runtest_setup(item): if is_potential_nosetest(item): - if isinstance(item.parent, python.Generator): - gen = item.parent - if not hasattr(gen, "_nosegensetup"): - call_optional(gen.obj, "setup") - if isinstance(gen.parent, python.Instance): - call_optional(gen.parent.obj, "setup") - gen._nosegensetup = True if not call_optional(item.obj, "setup"): # call module level setup if there is no object level one call_optional(item.parent.obj, "setup") @@ -53,11 +46,6 @@ def teardown_nose(item): # del item.parent._nosegensetup -def pytest_make_collect_report(collector): - if isinstance(collector, python.Generator): - call_optional(collector.obj, "setup") - - def is_potential_nosetest(item): # extra check needed since we do not do nose style setup/teardown # on direct unittest style classes diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 8c8de8e75..7ddcc2762 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -38,6 +38,7 @@ from _pytest.compat import safe_str from _pytest.compat import STRING_TYPES from _pytest.config import hookimpl from _pytest.main import FSHookProxy +from _pytest.mark import MARK_GEN from _pytest.mark.structures import get_unpacked_marks from _pytest.mark.structures import normalize_mark_list from _pytest.mark.structures import transfer_markers @@ -199,7 +200,6 @@ def pytest_pycollect_makeitem(collector, name, obj): # nothing was collected elsewhere, let's do it here if safe_isclass(obj): if collector.istestclass(obj, name): - Class = collector._getcustomclass("Class") outcome.force_result(Class(name, parent=collector)) elif collector.istestfunction(obj, name): # mock seems to store unbound methods (issue473), normalize it @@ -219,7 +219,10 @@ def pytest_pycollect_makeitem(collector, name, obj): ) elif getattr(obj, "__test__", True): if is_generator(obj): - res = Generator(name, parent=collector) + res = Function(name, parent=collector) + reason = deprecated.YIELD_TESTS.format(name=name) + res.add_marker(MARK_GEN.xfail(run=False, reason=reason)) + res.warn(PytestWarning(reason)) else: res = list(collector._genfunctions(name, obj)) outcome.force_result(res) @@ -408,7 +411,6 @@ class PyCollector(PyobjMixin, nodes.Collector): else: self.ihook.pytest_generate_tests(metafunc=metafunc) - Function = self._getcustomclass("Function") if not metafunc._calls: yield Function(name, parent=self, fixtureinfo=fixtureinfo) else: @@ -648,7 +650,7 @@ class Class(PyCollector): ) ) return [] - return [self._getcustomclass("Instance")(name="()", parent=self)] + return [Instance(name="()", parent=self)] def setup(self): setup_class = _get_xunit_func(self.obj, "setup_class") @@ -739,51 +741,6 @@ class FunctionMixin(PyobjMixin): return self._repr_failure_py(excinfo, style=style) -class Generator(FunctionMixin, PyCollector): - def collect(self): - - # test generators are seen as collectors but they also - # invoke setup/teardown on popular request - # (induced by the common "test_*" naming shared with normal tests) - from _pytest import deprecated - - self.warn(deprecated.YIELD_TESTS) - - self.session._setupstate.prepare(self) - # see FunctionMixin.setup and test_setupstate_is_preserved_134 - self._preservedparent = self.parent.obj - values = [] - seen = {} - _Function = self._getcustomclass("Function") - for i, x in enumerate(self.obj()): - name, call, args = self.getcallargs(x) - if not callable(call): - raise TypeError("%r yielded non callable test %r" % (self.obj, call)) - if name is None: - name = "[%d]" % i - else: - name = "['%s']" % name - if name in seen: - raise ValueError( - "%r generated tests with non-unique name %r" % (self, name) - ) - seen[name] = True - values.append(_Function(name, self, args=args, callobj=call)) - return values - - def getcallargs(self, obj): - if not isinstance(obj, (tuple, list)): - obj = (obj,) - # explicit naming - if isinstance(obj[0], six.string_types): - name = obj[0] - obj = obj[1:] - else: - name = None - call, args = obj[0], obj[1:] - return name, call, args - - def hasinit(obj): init = getattr(obj, "__init__", None) if init: @@ -1326,8 +1283,7 @@ def _showfixtures_main(config, session): tw.line(" %s: no docstring available" % (loc,), red=True) -def write_docstring(tw, doc): - INDENT = " " +def write_docstring(tw, doc, indent=" "): doc = doc.rstrip() if "\n" in doc: firstline, rest = doc.split("\n", 1) @@ -1335,11 +1291,11 @@ def write_docstring(tw, doc): firstline, rest = doc, "" if firstline.strip(): - tw.line(INDENT + firstline.strip()) + tw.line(indent + firstline.strip()) if rest: for line in dedent(rest).split("\n"): - tw.write(INDENT + line + "\n") + tw.write(indent + line + "\n") class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr): diff --git a/src/pytest.py b/src/pytest.py index 14ed1acaa..c0010f166 100644 --- a/src/pytest.py +++ b/src/pytest.py @@ -28,7 +28,6 @@ from _pytest.outcomes import skip from _pytest.outcomes import xfail from _pytest.python import Class from _pytest.python import Function -from _pytest.python import Generator from _pytest.python import Instance from _pytest.python import Module from _pytest.python import Package @@ -57,7 +56,6 @@ __all__ = [ "fixture", "freeze_includes", "Function", - "Generator", "hookimpl", "hookspec", "importorskip", diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index bc120b263..bc2e8378b 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -10,47 +10,6 @@ from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG pytestmark = pytest.mark.pytester_example_path("deprecated") -def test_yield_tests_deprecation(testdir): - testdir.makepyfile( - """ - def func1(arg, arg2): - assert arg == arg2 - def test_gen(): - yield "m1", func1, 15, 3*5 - yield "m2", func1, 42, 6*7 - def test_gen2(): - for k in range(10): - yield func1, 1, 1 - """ - ) - result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG) - result.stdout.fnmatch_lines( - [ - "*test_yield_tests_deprecation.py:3:*yield tests are deprecated*", - "*test_yield_tests_deprecation.py:6:*yield tests are deprecated*", - "*2 passed*", - ] - ) - assert result.stdout.str().count("yield tests are deprecated") == 2 - - -def test_compat_properties_deprecation(testdir): - testdir.makepyfile( - """ - def test_foo(request): - print(request.node.Module) - """ - ) - result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG) - result.stdout.fnmatch_lines( - [ - "*test_compat_properties_deprecation.py:2:*usage of Function.Module is deprecated, " - "please use pytest.Module instead*", - "*1 passed, 1 warnings in*", - ] - ) - - def test_cached_setup_deprecation(testdir): testdir.makepyfile( """ @@ -72,36 +31,6 @@ def test_cached_setup_deprecation(testdir): ) -def test_custom_class_deprecation(testdir): - testdir.makeconftest( - """ - import pytest - - class MyModule(pytest.Module): - - class Class(pytest.Class): - pass - - def pytest_pycollect_makemodule(path, parent): - return MyModule(path, parent) - """ - ) - testdir.makepyfile( - """ - class Test: - def test_foo(self): - pass - """ - ) - result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG) - result.stdout.fnmatch_lines( - [ - '*test_custom_class_deprecation.py:1:*"Class" objects in collectors of type "MyModule*', - "*1 passed, 1 warnings in*", - ] - ) - - def test_funcarg_prefix_deprecation(testdir): testdir.makepyfile( """ diff --git a/testing/python/collect.py b/testing/python/collect.py index 83fcdc3bd..2e534a259 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -7,7 +7,6 @@ import _pytest._code import pytest from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.nodes import Collector -from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG class TestModule(object): @@ -244,217 +243,6 @@ class TestClass(object): @pytest.mark.filterwarnings( "ignore:usage of Generator.Function is deprecated, please use pytest.Function instead" ) -class TestGenerator(object): - def test_generative_functions(self, testdir): - modcol = testdir.getmodulecol( - """ - def func1(arg, arg2): - assert arg == arg2 - - def test_gen(): - yield func1, 17, 3*5 - yield func1, 42, 6*7 - """ - ) - colitems = modcol.collect() - assert len(colitems) == 1 - gencol = colitems[0] - assert isinstance(gencol, pytest.Generator) - gencolitems = gencol.collect() - assert len(gencolitems) == 2 - assert isinstance(gencolitems[0], pytest.Function) - assert isinstance(gencolitems[1], pytest.Function) - assert gencolitems[0].name == "[0]" - assert gencolitems[0].obj.__name__ == "func1" - - def test_generative_methods(self, testdir): - modcol = testdir.getmodulecol( - """ - def func1(arg, arg2): - assert arg == arg2 - class TestGenMethods(object): - def test_gen(self): - yield func1, 17, 3*5 - yield func1, 42, 6*7 - """ - ) - gencol = modcol.collect()[0].collect()[0].collect()[0] - assert isinstance(gencol, pytest.Generator) - gencolitems = gencol.collect() - assert len(gencolitems) == 2 - assert isinstance(gencolitems[0], pytest.Function) - assert isinstance(gencolitems[1], pytest.Function) - assert gencolitems[0].name == "[0]" - assert gencolitems[0].obj.__name__ == "func1" - - def test_generative_functions_with_explicit_names(self, testdir): - modcol = testdir.getmodulecol( - """ - def func1(arg, arg2): - assert arg == arg2 - - def test_gen(): - yield "seventeen", func1, 17, 3*5 - yield "fortytwo", func1, 42, 6*7 - """ - ) - colitems = modcol.collect() - assert len(colitems) == 1 - gencol = colitems[0] - assert isinstance(gencol, pytest.Generator) - gencolitems = gencol.collect() - assert len(gencolitems) == 2 - assert isinstance(gencolitems[0], pytest.Function) - assert isinstance(gencolitems[1], pytest.Function) - assert gencolitems[0].name == "['seventeen']" - assert gencolitems[0].obj.__name__ == "func1" - assert gencolitems[1].name == "['fortytwo']" - assert gencolitems[1].obj.__name__ == "func1" - - def test_generative_functions_unique_explicit_names(self, testdir): - # generative - modcol = testdir.getmodulecol( - """ - def func(): pass - def test_gen(): - yield "name", func - yield "name", func - """ - ) - colitems = modcol.collect() - assert len(colitems) == 1 - gencol = colitems[0] - assert isinstance(gencol, pytest.Generator) - pytest.raises(ValueError, gencol.collect) - - def test_generative_methods_with_explicit_names(self, testdir): - modcol = testdir.getmodulecol( - """ - def func1(arg, arg2): - assert arg == arg2 - class TestGenMethods(object): - def test_gen(self): - yield "m1", func1, 17, 3*5 - yield "m2", func1, 42, 6*7 - """ - ) - gencol = modcol.collect()[0].collect()[0].collect()[0] - assert isinstance(gencol, pytest.Generator) - gencolitems = gencol.collect() - assert len(gencolitems) == 2 - assert isinstance(gencolitems[0], pytest.Function) - assert isinstance(gencolitems[1], pytest.Function) - assert gencolitems[0].name == "['m1']" - assert gencolitems[0].obj.__name__ == "func1" - assert gencolitems[1].name == "['m2']" - assert gencolitems[1].obj.__name__ == "func1" - - def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir): - o = testdir.makepyfile( - """ - from __future__ import print_function - def test_generative_order_of_execution(): - import py, pytest - test_list = [] - expected_list = list(range(6)) - - def list_append(item): - test_list.append(item) - - def assert_order_of_execution(): - print('expected order', expected_list) - print('but got ', test_list) - assert test_list == expected_list - - for i in expected_list: - yield list_append, i - yield assert_order_of_execution - """ - ) - reprec = testdir.inline_run(o, SHOW_PYTEST_WARNINGS_ARG) - passed, skipped, failed = reprec.countoutcomes() - assert passed == 7 - assert not skipped and not failed - - def test_order_of_execution_generator_different_codeline(self, testdir): - o = testdir.makepyfile( - """ - from __future__ import print_function - def test_generative_tests_different_codeline(): - import py, pytest - test_list = [] - expected_list = list(range(3)) - - def list_append_2(): - test_list.append(2) - - def list_append_1(): - test_list.append(1) - - def list_append_0(): - test_list.append(0) - - def assert_order_of_execution(): - print('expected order', expected_list) - print('but got ', test_list) - assert test_list == expected_list - - yield list_append_0 - yield list_append_1 - yield list_append_2 - yield assert_order_of_execution - """ - ) - reprec = testdir.inline_run(o, SHOW_PYTEST_WARNINGS_ARG) - passed, skipped, failed = reprec.countoutcomes() - assert passed == 4 - assert not skipped and not failed - - def test_setupstate_is_preserved_134(self, testdir): - # yield-based tests are messy wrt to setupstate because - # during collection they already invoke setup functions - # and then again when they are run. For now, we want to make sure - # that the old 1.3.4 behaviour is preserved such that all - # yielded functions all share the same "self" instance that - # has been used during collection. - o = testdir.makepyfile( - """ - setuplist = [] - class TestClass(object): - def setup_method(self, func): - #print "setup_method", self, func - setuplist.append(self) - self.init = 42 - - def teardown_method(self, func): - self.init = None - - def test_func1(self): - pass - - def test_func2(self): - yield self.func2 - yield self.func2 - - def func2(self): - assert self.init - - def test_setuplist(): - # once for test_func2 during collection - # once for test_func1 during test run - # once for test_func2 during test run - #print setuplist - assert len(setuplist) == 3, len(setuplist) - assert setuplist[0] == setuplist[2], setuplist - assert setuplist[1] != setuplist[2], setuplist - """ - ) - reprec = testdir.inline_run(o, "-v", SHOW_PYTEST_WARNINGS_ARG) - passed, skipped, failed = reprec.countoutcomes() - assert passed == 4 - assert not skipped and not failed - - class TestFunction(object): @pytest.fixture def ignore_parametrized_marks_args(self): @@ -1271,39 +1059,6 @@ class TestReportInfo(object): @pytest.mark.filterwarnings( "ignore:usage of Generator.Function is deprecated, please use pytest.Function instead" ) - def test_generator_reportinfo(self, testdir): - modcol = testdir.getmodulecol( - """ - # lineno 0 - def test_gen(): - def check(x): - assert x - yield check, 3 - """ - ) - gencol = testdir.collect_by_name(modcol, "test_gen") - fspath, lineno, modpath = gencol.reportinfo() - assert fspath == modcol.fspath - assert lineno == 1 - assert modpath == "test_gen" - - genitem = gencol.collect()[0] - fspath, lineno, modpath = genitem.reportinfo() - assert fspath == modcol.fspath - assert lineno == 2 - assert modpath == "test_gen[0]" - """ - def test_func(): - pass - def test_genfunc(): - def check(x): - pass - yield check, 3 - class TestClass(object): - def test_method(self): - pass - """ - def test_reportinfo_with_nasty_getattr(self, testdir): # https://github.com/pytest-dev/pytest/issues/1204 modcol = testdir.getmodulecol( @@ -1373,54 +1128,6 @@ def test_customized_python_discovery_functions(testdir): result.stdout.fnmatch_lines(["*1 passed*"]) -def test_collector_attributes(testdir): - testdir.makeconftest( - """ - import pytest - def pytest_pycollect_makeitem(collector): - assert collector.Function == pytest.Function - assert collector.Class == pytest.Class - assert collector.Instance == pytest.Instance - assert collector.Module == pytest.Module - """ - ) - testdir.makepyfile( - """ - def test_hello(): - pass - """ - ) - result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG) - result.stdout.fnmatch_lines(["*1 passed*"]) - - -def test_customize_through_attributes(testdir): - testdir.makeconftest( - """ - import pytest - class MyFunction(pytest.Function): - pass - class MyInstance(pytest.Instance): - Function = MyFunction - class MyClass(pytest.Class): - Instance = MyInstance - - def pytest_pycollect_makeitem(collector, name, obj): - if name.startswith("MyTestClass"): - return MyClass(name, parent=collector) - """ - ) - testdir.makepyfile( - """ - class MyTestClass(object): - def test_hello(self): - pass - """ - ) - result = testdir.runpytest("--collect-only", SHOW_PYTEST_WARNINGS_ARG) - result.stdout.fnmatch_lines(["*MyClass*", "*MyFunction*test_hello*"]) - - def test_unorderable_types(testdir): testdir.makepyfile( """ diff --git a/testing/python/fixture.py b/testing/python/fixture.py index b7ce16eb5..f5813b5d8 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1850,24 +1850,6 @@ class TestAutouseManagement(object): reprec = testdir.inline_run("-s") reprec.assertoutcome(passed=1) - def test_autouse_honored_for_yield(self, testdir): - testdir.makepyfile( - """ - import pytest - @pytest.fixture(autouse=True) - def tst(): - global x - x = 3 - def test_gen(): - def f(hello): - assert x == abs(hello) - yield f, 3 - yield f, -3 - """ - ) - reprec = testdir.inline_run(SHOW_PYTEST_WARNINGS_ARG) - reprec.assertoutcome(passed=2) - def test_funcarg_and_setup(self, testdir): testdir.makepyfile( """ diff --git a/testing/test_collection.py b/testing/test_collection.py index 473883b0d..36e8a69ce 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -21,20 +21,6 @@ class TestCollector(object): assert not issubclass(Collector, Item) assert not issubclass(Item, Collector) - def test_compat_attributes(self, testdir, recwarn): - modcol = testdir.getmodulecol( - """ - def test_pass(): pass - def test_fail(): assert 0 - """ - ) - recwarn.clear() - assert modcol.Module == pytest.Module - assert modcol.Class == pytest.Class - assert modcol.Item == pytest.Item - assert modcol.File == pytest.File - assert modcol.Function == pytest.Function - def test_check_equality(self, testdir): modcol = testdir.getmodulecol( """ diff --git a/testing/test_nose.py b/testing/test_nose.py index e4db46802..3e9966529 100644 --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -3,7 +3,6 @@ from __future__ import division from __future__ import print_function import pytest -from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG def setup_module(mod): @@ -162,73 +161,6 @@ def test_nose_setup_partial(testdir): result.stdout.fnmatch_lines(["*2 passed*"]) -def test_nose_test_generator_fixtures(testdir): - p = testdir.makepyfile( - """ - # taken from nose-0.11.1 unit_tests/test_generator_fixtures.py - from nose.tools import eq_ - called = [] - - def outer_setup(): - called.append('outer_setup') - - def outer_teardown(): - called.append('outer_teardown') - - def inner_setup(): - called.append('inner_setup') - - def inner_teardown(): - called.append('inner_teardown') - - def test_gen(): - called[:] = [] - for i in range(0, 5): - yield check, i - - def check(i): - expect = ['outer_setup'] - for x in range(0, i): - expect.append('inner_setup') - expect.append('inner_teardown') - expect.append('inner_setup') - eq_(called, expect) - - - test_gen.setup = outer_setup - test_gen.teardown = outer_teardown - check.setup = inner_setup - check.teardown = inner_teardown - - class TestClass(object): - def setup(self): - print("setup called in %s" % self) - self.called = ['setup'] - - def teardown(self): - print("teardown called in %s" % self) - eq_(self.called, ['setup']) - self.called.append('teardown') - - def test(self): - print("test called in %s" % self) - for i in range(0, 5): - yield self.check, i - - def check(self, i): - print("check called in %s" % self) - expect = ['setup'] - #for x in range(0, i): - # expect.append('setup') - # expect.append('teardown') - #expect.append('setup') - eq_(self.called, expect) - """ - ) - result = testdir.runpytest(p, "-p", "nose", SHOW_PYTEST_WARNINGS_ARG) - result.stdout.fnmatch_lines(["*10 passed*"]) - - def test_module_level_setup(testdir): testdir.makepyfile( """ diff --git a/testing/test_pdb.py b/testing/test_pdb.py index dd349454b..9bb1ca546 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -8,7 +8,6 @@ import sys import _pytest._code import pytest -from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG try: breakpoint @@ -809,27 +808,6 @@ class TestTraceOption: assert "reading from stdin while output" not in rest TestPDB.flush(child) - def test_trace_against_yield_test(self, testdir): - p1 = testdir.makepyfile( - """ - def is_equal(a, b): - assert a == b - - def test_1(): - yield is_equal, 1, 1 - """ - ) - child = testdir.spawn_pytest( - "{} --trace {}".format(SHOW_PYTEST_WARNINGS_ARG, str(p1)) - ) - child.expect("is_equal") - child.expect("Pdb") - child.sendeof() - rest = child.read().decode("utf8") - assert "1 passed" in rest - assert "reading from stdin while output" not in rest - TestPDB.flush(child) - def test_trace_after_runpytest(testdir): """Test that debugging's pytest_configure is re-entrant.""" diff --git a/testing/test_runner_xunit.py b/testing/test_runner_xunit.py index b0844dc1c..6b5752b77 100644 --- a/testing/test_runner_xunit.py +++ b/testing/test_runner_xunit.py @@ -7,7 +7,6 @@ from __future__ import division from __future__ import print_function import pytest -from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG def test_module_and_function_setup(testdir): @@ -170,64 +169,6 @@ def test_method_setup_failure_no_teardown(testdir): reprec.assertoutcome(failed=1, passed=1) -def test_method_generator_setup(testdir): - reprec = testdir.inline_runsource( - """ - class TestSetupTeardownOnInstance(object): - def setup_class(cls): - cls.classsetup = True - - def setup_method(self, method): - self.methsetup = method - - def test_generate(self): - assert self.classsetup - assert self.methsetup == self.test_generate - yield self.generated, 5 - yield self.generated, 2 - - def generated(self, value): - assert self.classsetup - assert self.methsetup == self.test_generate - assert value == 5 - """, - SHOW_PYTEST_WARNINGS_ARG, - ) - reprec.assertoutcome(passed=1, failed=1) - - -def test_func_generator_setup(testdir): - reprec = testdir.inline_runsource( - """ - import sys - - def setup_module(mod): - print("setup_module") - mod.x = [] - - def setup_function(fun): - print("setup_function") - x.append(1) - - def teardown_function(fun): - print("teardown_function") - x.pop() - - def test_one(): - assert x == [1] - def check(): - print("check") - sys.stderr.write("e\\n") - assert x == [1] - yield check - assert x == [1] - """, - SHOW_PYTEST_WARNINGS_ARG, - ) - rep = reprec.matchreport("test_one", names="pytest_runtest_logreport") - assert rep.passed - - def test_method_setup_uses_fresh_instances(testdir): reprec = testdir.inline_runsource( """ diff --git a/testing/test_session.py b/testing/test_session.py index 32d71a18e..d68fc9d41 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -4,7 +4,6 @@ from __future__ import print_function import pytest from _pytest.main import EXIT_NOTESTSCOLLECTED -from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG class SessionTests(object): @@ -73,19 +72,6 @@ class SessionTests(object): print(out) pytest.fail("incorrect raises() output") - def test_generator_yields_None(self, testdir): - reprec = testdir.inline_runsource( - """ - def test_1(): - yield None - """, - SHOW_PYTEST_WARNINGS_ARG, - ) - failures = reprec.getfailedcollections() - out = failures[0].longrepr.reprcrash.message - i = out.find("TypeError") - assert i != -1 - def test_syntax_error_module(self, testdir): reprec = testdir.inline_runsource("this is really not python") values = reprec.getfailedcollections() diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 0faf9b401..2a7a646ee 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -20,7 +20,6 @@ from _pytest.terminal import build_summary_stats_line from _pytest.terminal import getreportopt from _pytest.terminal import repr_pythonversion from _pytest.terminal import TerminalReporter -from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG DistInfo = collections.namedtuple("DistInfo", ["project_name", "version"]) @@ -585,8 +584,9 @@ class TestTerminalFunctional(object): ] ) - def test_verbose_reporting(self, testdir, pytestconfig): - p1 = testdir.makepyfile( + @pytest.fixture + def verbose_testfile(self, testdir): + return testdir.makepyfile( """ import pytest def test_fail(): @@ -602,22 +602,32 @@ class TestTerminalFunctional(object): yield check, 0 """ ) - result = testdir.runpytest(p1, "-v", SHOW_PYTEST_WARNINGS_ARG) + + def test_verbose_reporting(self, verbose_testfile, testdir, pytestconfig): + + result = testdir.runpytest( + verbose_testfile, "-v", "-Walways::pytest.PytestWarning" + ) result.stdout.fnmatch_lines( [ "*test_verbose_reporting.py::test_fail *FAIL*", "*test_verbose_reporting.py::test_pass *PASS*", "*test_verbose_reporting.py::TestClass::test_skip *SKIP*", - "*test_verbose_reporting.py::test_gen*0* *FAIL*", + "*test_verbose_reporting.py::test_gen *xfail*", ] ) assert result.ret == 1 + def test_verbose_reporting_xdist(self, verbose_testfile, testdir, pytestconfig): if not pytestconfig.pluginmanager.get_plugin("xdist"): pytest.skip("xdist plugin not installed") - result = testdir.runpytest(p1, "-v", "-n 1", SHOW_PYTEST_WARNINGS_ARG) - result.stdout.fnmatch_lines(["*FAIL*test_verbose_reporting.py::test_fail*"]) + result = testdir.runpytest( + verbose_testfile, "-v", "-n 1", "-Walways::pytest.PytestWarning" + ) + result.stdout.fnmatch_lines( + ["*FAIL*test_verbose_reporting_xdist.py::test_fail*"] + ) assert result.ret == 1 def test_quiet_reporting(self, testdir): From 847eacea19a02887fb6e63601908a37d7c2576a8 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sat, 3 Nov 2018 21:50:13 +0100 Subject: [PATCH 44/44] refactor CallInfo constructor magic into named constructor --- src/_pytest/nose.py | 4 ++- src/_pytest/runner.py | 59 ++++++++++++++++++++++++++---------------- testing/test_runner.py | 14 ++-------- 3 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/_pytest/nose.py b/src/_pytest/nose.py index 416381bb5..6facc547f 100644 --- a/src/_pytest/nose.py +++ b/src/_pytest/nose.py @@ -23,7 +23,9 @@ def get_skip_exceptions(): def pytest_runtest_makereport(item, call): if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()): # let's substitute the excinfo with a pytest.skip one - call2 = call.__class__(lambda: runner.skip(str(call.excinfo.value)), call.when) + call2 = runner.CallInfo.from_call( + lambda: runner.skip(str(call.excinfo.value)), call.when + ) call.excinfo = call2.excinfo diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 9ea1a07cd..27f244a80 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -8,6 +8,7 @@ import os import sys from time import time +import attr import six from .reports import CollectErrorRepr @@ -189,43 +190,57 @@ def check_interactive_exception(call, report): def call_runtest_hook(item, when, **kwds): hookname = "pytest_runtest_" + when ihook = getattr(item.ihook, hookname) - return CallInfo( + return CallInfo.from_call( lambda: ihook(item=item, **kwds), when=when, - treat_keyboard_interrupt_as_exception=item.config.getvalue("usepdb"), + reraise=KeyboardInterrupt if not item.config.getvalue("usepdb") else (), ) +@attr.s(repr=False) class CallInfo(object): """ Result/Exception info a function invocation. """ - #: None or ExceptionInfo object. - excinfo = None + _result = attr.ib() + # type: Optional[ExceptionInfo] + excinfo = attr.ib() + start = attr.ib() + stop = attr.ib() + when = attr.ib() - def __init__(self, func, when, treat_keyboard_interrupt_as_exception=False): + @property + def result(self): + if self.excinfo is not None: + raise AttributeError("{!r} has no valid result".format(self)) + return self._result + + @classmethod + def from_call(cls, func, when, reraise=None): #: context of invocation: one of "setup", "call", #: "teardown", "memocollect" - self.when = when - self.start = time() + start = time() + excinfo = None try: - self.result = func() - except KeyboardInterrupt: - if treat_keyboard_interrupt_as_exception: - self.excinfo = ExceptionInfo.from_current() - else: - self.stop = time() - raise + result = func() except: # noqa - self.excinfo = ExceptionInfo.from_current() - self.stop = time() + excinfo = ExceptionInfo.from_current() + if reraise is not None and excinfo.errisinstance(reraise): + raise + result = None + stop = time() + return cls(start=start, stop=stop, when=when, result=result, excinfo=excinfo) def __repr__(self): - if self.excinfo: - status = "exception: %s" % str(self.excinfo.value) + if self.excinfo is not None: + status = "exception" + value = self.excinfo.value else: - result = getattr(self, "result", "") - status = "result: %r" % (result,) - return "" % (self.when, status) + # TODO: investigate unification + value = repr(self._result) + status = "result" + return "".format( + when=self.when, value=value, status=status + ) def pytest_runtest_makereport(item, call): @@ -269,7 +284,7 @@ def pytest_runtest_makereport(item, call): def pytest_make_collect_report(collector): - call = CallInfo(lambda: list(collector.collect()), "collect") + call = CallInfo.from_call(lambda: list(collector.collect()), "collect") longrepr = None if not call.excinfo: outcome = "passed" diff --git a/testing/test_runner.py b/testing/test_runner.py index d76f3da9b..916c2ea4a 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -487,13 +487,13 @@ def test_report_extra_parameters(reporttype): def test_callinfo(): - ci = runner.CallInfo(lambda: 0, "123") + ci = runner.CallInfo.from_call(lambda: 0, "123") assert ci.when == "123" assert ci.result == 0 assert "result" in repr(ci) assert repr(ci) == "" - ci = runner.CallInfo(lambda: 0 / 0, "123") + ci = runner.CallInfo.from_call(lambda: 0 / 0, "123") assert ci.when == "123" assert not hasattr(ci, "result") assert repr(ci) == "" @@ -501,16 +501,6 @@ def test_callinfo(): assert "exc" in repr(ci) -def test_callinfo_repr_while_running(): - def repr_while_running(): - f = sys._getframe().f_back - assert "func" in f.f_locals - assert repr(f.f_locals["self"]) == "'>" - - ci = runner.CallInfo(repr_while_running, "when") - assert repr(ci) == "" - - # design question: do we want general hooks in python files? # then something like the following functional tests makes sense