diff --git a/changelog/12017.improvement.rst b/changelog/12017.improvement.rst new file mode 100644 index 000000000..03c1e4a88 --- /dev/null +++ b/changelog/12017.improvement.rst @@ -0,0 +1,5 @@ +* migrate formatting tests to fstrings +* use typesafe constructs in the junitxml tests +* separate mocktiming into ``_pytest.timing`` + +-- by :user:`RonnyPfannschmidt` diff --git a/src/_pytest/setuponly.py b/src/_pytest/setuponly.py index de297f408..40fe6d5d1 100644 --- a/src/_pytest/setuponly.py +++ b/src/_pytest/setuponly.py @@ -73,13 +73,9 @@ def _show_fixture_action( # Use smaller indentation the higher the scope: Session = 0, Package = 1, etc. scope_indent = list(reversed(Scope)).index(fixturedef._scope) tw.write(" " * 2 * scope_indent) - tw.write( - "{step} {scope} {fixture}".format( # noqa: UP032 (Readability) - step=msg.ljust(8), # align the output to TEARDOWN - scope=fixturedef.scope[0].upper(), - fixture=fixturedef.argname, - ) - ) + + scopename = fixturedef.scope[0].upper() + tw.write(f"{msg:<8} {scopename} {fixturedef.argname}") if msg == "SETUP": deps = sorted(arg for arg in fixturedef.argnames if arg != "request") diff --git a/src/_pytest/timing.py b/src/_pytest/timing.py index b23c7f69e..e2dde1617 100644 --- a/src/_pytest/timing.py +++ b/src/_pytest/timing.py @@ -8,9 +8,45 @@ Fixture "mock_timing" also interacts with this module for pytest's own tests. from __future__ import annotations +import dataclasses +from datetime import datetime from time import perf_counter from time import sleep from time import time +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from pytest import MonkeyPatch + + +@dataclasses.dataclass +class MockTiming: + """Mocks _pytest.timing with a known object that can be used to control timing in tests + deterministically. + + pytest itself should always use functions from `_pytest.timing` instead of `time` directly. + + This then allows us more control over time during testing, if testing code also + uses `_pytest.timing` functions. + + Time is static, and only advances through `sleep` calls, thus tests might sleep over large + numbers and obtain accurate time() calls at the end, making tests reliable and instant.""" + + _current_time: float = datetime(2020, 5, 22, 14, 20, 50).timestamp() # noqa: RUF009 + + def sleep(self, seconds: float) -> None: + self._current_time += seconds + + def time(self) -> float: + return self._current_time + + def patch(self, monkeypatch: MonkeyPatch) -> None: + from _pytest import timing # noqa: PLW0406 + + monkeypatch.setattr(timing, "sleep", self.sleep) + monkeypatch.setattr(timing, "time", self.time) + monkeypatch.setattr(timing, "perf_counter", self.time) __all__ = ["perf_counter", "sleep", "time"] diff --git a/testing/conftest.py b/testing/conftest.py index 24e5d1830..3a5421f45 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,7 +1,6 @@ # mypy: allow-untyped-defs from __future__ import annotations -import dataclasses import re import sys from typing import Generator @@ -226,24 +225,8 @@ def mock_timing(monkeypatch: MonkeyPatch): Time is static, and only advances through `sleep` calls, thus tests might sleep over large numbers and obtain accurate time() calls at the end, making tests reliable and instant. """ - - @dataclasses.dataclass - class MockTiming: - _current_time: float = 1590150050.0 - - def sleep(self, seconds: float) -> None: - self._current_time += seconds - - def time(self) -> float: - return self._current_time - - def patch(self) -> None: - from _pytest import timing - - monkeypatch.setattr(timing, "sleep", self.sleep) - monkeypatch.setattr(timing, "time", self.time) - monkeypatch.setattr(timing, "perf_counter", self.time) + from _pytest.timing import MockTiming result = MockTiming() - result.patch() + result.patch(monkeypatch) return result diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index bc091bb1f..1f2d44669 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -2199,14 +2199,14 @@ class TestAutouseManagement: ) -> None: """#226""" pytester.makepyfile( - """ + f""" import pytest values = [] - @pytest.fixture(%(param1)s) + @pytest.fixture({param1}) def arg1(request): request.addfinalizer(lambda: values.append("fin1")) values.append("new1") - @pytest.fixture(%(param2)s) + @pytest.fixture({param2}) def arg2(request, arg1): request.addfinalizer(lambda: values.append("fin2")) values.append("new2") @@ -2215,8 +2215,7 @@ class TestAutouseManagement: pass def test_check(): assert values == ["new1", "new2", "fin2", "fin1"] - """ # noqa: UP031 (python syntax issues) - % locals() + """ ) reprec = pytester.inline_run("-s") reprec.assertoutcome(passed=2) @@ -3096,21 +3095,21 @@ class TestFixtureMarker: ) -> None: """#246""" pytester.makepyfile( - """ + f""" import pytest values = [] - @pytest.fixture(scope=%(scope)r, params=["1"]) + @pytest.fixture(scope={scope!r}, params=["1"]) def fix1(request): return request.param - @pytest.fixture(scope=%(scope)r) + @pytest.fixture(scope={scope!r}) def fix2(request, base): def cleanup_fix2(): assert not values, "base should not have been finalized" request.addfinalizer(cleanup_fix2) - @pytest.fixture(scope=%(scope)r) + @pytest.fixture(scope={scope!r}) def base(request, fix1): def cleanup_base(): values.append("fin_base") @@ -3123,8 +3122,7 @@ class TestFixtureMarker: pass def test_other(): pass - """ # noqa: UP031 (python syntax issues) - % {"scope": scope} + """ ) reprec = pytester.inline_run("-lvs") reprec.assertoutcome(passed=3) @@ -3310,42 +3308,40 @@ class TestRequestScopeAccess: def test_setup(self, pytester: Pytester, scope, ok, error) -> None: pytester.makepyfile( - """ + f""" import pytest - @pytest.fixture(scope=%r, autouse=True) + @pytest.fixture(scope={scope!r}, autouse=True) def myscoped(request): - for x in %r: + for x in {ok.split()}: assert hasattr(request, x) - for x in %r: + for x in {error.split()}: pytest.raises(AttributeError, lambda: getattr(request, x)) assert request.session assert request.config def test_func(): pass - """ # noqa: UP031 (python syntax issues) - % (scope, ok.split(), error.split()) + """ ) reprec = pytester.inline_run("-l") reprec.assertoutcome(passed=1) def test_funcarg(self, pytester: Pytester, scope, ok, error) -> None: pytester.makepyfile( - """ + f""" import pytest - @pytest.fixture(scope=%r) + @pytest.fixture(scope={scope!r}) def arg(request): - for x in %r: + for x in {ok.split()!r}: assert hasattr(request, x) - for x in %r: + for x in {error.split()!r}: pytest.raises(AttributeError, lambda: getattr(request, x)) assert request.session assert request.config def test_func(arg): pass - """ # noqa: UP031 (python syntax issues) - % (scope, ok.split(), error.split()) + """ ) reprec = pytester.inline_run() reprec.assertoutcome(passed=1) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 67234302a..de3886b72 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1,10 +1,10 @@ -# mypy: allow-untyped-defs from __future__ import annotations from datetime import datetime import os from pathlib import Path import platform +from typing import Any from typing import cast from typing import TYPE_CHECKING from xml.dom import minidom @@ -20,6 +20,7 @@ from _pytest.pytester import RunResult from _pytest.reports import BaseReport from _pytest.reports import TestReport from _pytest.stash import Stash +import _pytest.timing import pytest @@ -38,7 +39,7 @@ class RunAndParse: def __call__( self, *args: str | os.PathLike[str], family: str | None = "xunit1" - ) -> tuple[RunResult, DomNode]: + ) -> tuple[RunResult, DomDocument]: if family: args = ("-o", "junit_family=" + family, *args) xml_path = self.pytester.path.joinpath("junit.xml") @@ -47,7 +48,7 @@ class RunAndParse: with xml_path.open(encoding="utf-8") as f: self.schema.validate(f) xmldoc = minidom.parse(str(xml_path)) - return result, DomNode(xmldoc) + return result, DomDocument(xmldoc) @pytest.fixture @@ -61,78 +62,91 @@ def run_and_parse(pytester: Pytester, schema: xmlschema.XMLSchema) -> RunAndPars return RunAndParse(pytester, schema) -def assert_attr(node, **kwargs): +def assert_attr(node: minidom.Element, **kwargs: object) -> None: __tracebackhide__ = True - def nodeval(node, name): + def nodeval(node: minidom.Element, name: str) -> str | None: anode = node.getAttributeNode(name) - if anode is not None: - return anode.value + return anode.value if anode is not None else None expected = {name: str(value) for name, value in kwargs.items()} on_node = {name: nodeval(node, name) for name in expected} assert on_node == expected -class DomNode: - def __init__(self, dom): - self.__node = dom +class DomDocument: + def __init__(self, dom: minidom.Document): + self._node = dom - def __repr__(self): - return self.__node.toxml() + _node: minidom.Document | minidom.Element - def find_first_by_tag(self, tag): + def find_first_by_tag(self, tag: str) -> DomNode | None: return self.find_nth_by_tag(tag, 0) - def _by_tag(self, tag): - return self.__node.getElementsByTagName(tag) + def get_first_by_tag(self, tag: str) -> DomNode: + maybe = self.find_first_by_tag(tag) + if maybe is None: + raise LookupError(tag) + else: + return maybe + + def find_nth_by_tag(self, tag: str, n: int) -> DomNode | None: + items = self._node.getElementsByTagName(tag) + try: + nth = items[n] + except IndexError: + return None + else: + return DomNode(nth) + + def find_by_tag(self, tag: str) -> list[DomNode]: + return [DomNode(x) for x in self._node.getElementsByTagName(tag)] @property - def children(self): - return [type(self)(x) for x in self.__node.childNodes] + def children(self) -> list[DomNode]: + return [DomNode(x) for x in self._node.childNodes] @property - def get_unique_child(self): + def get_unique_child(self) -> DomNode: children = self.children assert len(children) == 1 return children[0] - def find_nth_by_tag(self, tag, n): - items = self._by_tag(tag) - try: - nth = items[n] - except IndexError: - pass - else: - return type(self)(nth) + def toxml(self) -> str: + return self._node.toxml() - def find_by_tag(self, tag): - t = type(self) - return [t(x) for x in self.__node.getElementsByTagName(tag)] - def __getitem__(self, key): - node = self.__node.getAttributeNode(key) +class DomNode(DomDocument): + _node: minidom.Element + + def __init__(self, dom: minidom.Element): + self._node = dom + + def __repr__(self) -> str: + return self.toxml() + + def __getitem__(self, key: str) -> str: + node = self._node.getAttributeNode(key) if node is not None: - return node.value + return cast(str, node.value) + else: + raise KeyError(key) - def assert_attr(self, **kwargs): + def assert_attr(self, **kwargs: object) -> None: __tracebackhide__ = True - return assert_attr(self.__node, **kwargs) - - def toxml(self): - return self.__node.toxml() + return assert_attr(self._node, **kwargs) @property - def text(self): - return self.__node.childNodes[0].wholeText + def text(self) -> str: + return cast(str, self._node.childNodes[0].wholeText) @property - def tag(self): - return self.__node.tagName + def tag(self) -> str: + return self._node.tagName @property - def next_sibling(self): - return type(self)(self.__node.nextSibling) + def next_sibling(self) -> DomNode: + return DomNode(self._node.nextSibling) parametrize_families = pytest.mark.parametrize("xunit_family", ["xunit1", "xunit2"]) @@ -162,7 +176,7 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) assert result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(name="pytest", errors=0, failures=1, skipped=2, tests=5) @parametrize_families @@ -191,7 +205,7 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) assert result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(name="pytest", errors=1, failures=2, skipped=1, tests=5) @parametrize_families @@ -205,7 +219,7 @@ class TestPython: """ ) result, dom = run_and_parse(family=xunit_family) - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(hostname=platform.node()) @parametrize_families @@ -220,12 +234,15 @@ class TestPython: ) start_time = datetime.now() result, dom = run_and_parse(family=xunit_family) - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") timestamp = datetime.strptime(node["timestamp"], "%Y-%m-%dT%H:%M:%S.%f") assert start_time <= timestamp < datetime.now() def test_timing_function( - self, pytester: Pytester, run_and_parse: RunAndParse, mock_timing + self, + pytester: Pytester, + run_and_parse: RunAndParse, + mock_timing: _pytest.timing.MockTiming, ) -> None: pytester.makepyfile( """ @@ -239,9 +256,10 @@ class TestPython: """ ) result, dom = run_and_parse() - node = dom.find_first_by_tag("testsuite") - tnode = node.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testsuite") + tnode = node.get_first_by_tag("testcase") val = tnode["time"] + assert val is not None assert float(val) == 7.0 @pytest.mark.parametrize("duration_report", ["call", "total"]) @@ -255,7 +273,7 @@ class TestPython: # mock LogXML.node_reporter so it always sets a known duration to each test report object original_node_reporter = LogXML.node_reporter - def node_reporter_wrapper(s, report): + def node_reporter_wrapper(s: Any, report: TestReport) -> Any: report.duration = 1.0 reporter = original_node_reporter(s, report) return reporter @@ -269,8 +287,8 @@ class TestPython: """ ) result, dom = run_and_parse("-o", f"junit_duration_report={duration_report}") - node = dom.find_first_by_tag("testsuite") - tnode = node.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testsuite") + tnode = node.get_first_by_tag("testcase") val = float(tnode["time"]) if duration_report == "total": assert val == 3.0 @@ -295,11 +313,11 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) assert result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(errors=1, tests=1) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr(classname="test_setup_error", name="test_function") - fnode = tnode.find_first_by_tag("error") + fnode = tnode.get_first_by_tag("error") fnode.assert_attr(message='failed on setup with "ValueError: Error reason"') assert "ValueError" in fnode.toxml() @@ -321,10 +339,10 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) assert result.ret - node = dom.find_first_by_tag("testsuite") - tnode = node.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testsuite") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr(classname="test_teardown_error", name="test_function") - fnode = tnode.find_first_by_tag("error") + fnode = tnode.get_first_by_tag("error") fnode.assert_attr(message='failed on teardown with "ValueError: Error reason"') assert "ValueError" in fnode.toxml() @@ -346,15 +364,15 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) assert result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(errors=1, failures=1, tests=1) first, second = dom.find_by_tag("testcase") assert first assert second assert first != second - fnode = first.find_first_by_tag("failure") + fnode = first.get_first_by_tag("failure") fnode.assert_attr(message="Exception: Call Exception") - snode = second.find_first_by_tag("error") + snode = second.get_first_by_tag("error") snode.assert_attr( message='failed on teardown with "Exception: Teardown Exception"' ) @@ -372,11 +390,11 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(skipped=1) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr(classname="test_skip_contains_name_reason", name="test_skip") - snode = tnode.find_first_by_tag("skipped") + snode = tnode.get_first_by_tag("skipped") snode.assert_attr(type="pytest.skip", message="hello23") @parametrize_families @@ -393,13 +411,13 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(skipped=1) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr( classname="test_mark_skip_contains_name_reason", name="test_skip" ) - snode = tnode.find_first_by_tag("skipped") + snode = tnode.get_first_by_tag("skipped") snode.assert_attr(type="pytest.skip", message="hello24") @parametrize_families @@ -417,13 +435,13 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(skipped=1) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr( classname="test_mark_skipif_contains_name_reason", name="test_skip" ) - snode = tnode.find_first_by_tag("skipped") + snode = tnode.get_first_by_tag("skipped") snode.assert_attr(type="pytest.skip", message="hello25") @parametrize_families @@ -440,7 +458,7 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 - node_xml = dom.find_first_by_tag("testsuite").toxml() + node_xml = dom.get_first_by_tag("testsuite").toxml() assert "bar!" not in node_xml @parametrize_families @@ -456,9 +474,9 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) assert result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(failures=1) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr( classname="test_classname_instance.TestClass", name="test_method" ) @@ -471,9 +489,9 @@ class TestPython: p.write_text("def test_func(): 0/0", encoding="utf-8") result, dom = run_and_parse(family=xunit_family) assert result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(failures=1) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr(classname="sub.test_hello", name="test_func") @parametrize_families @@ -484,11 +502,11 @@ class TestPython: pytester.makepyfile("def test_function(): pass") result, dom = run_and_parse(family=xunit_family) assert result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(errors=1, tests=1) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr(classname="pytest", name="internal") - fnode = tnode.find_first_by_tag("error") + fnode = tnode.get_first_by_tag("error") fnode.assert_attr(message="internal error") assert "Division" in fnode.toxml() @@ -499,9 +517,9 @@ class TestPython: def test_failure_function( self, pytester: Pytester, - junit_logging, + junit_logging: str, run_and_parse: RunAndParse, - xunit_family, + xunit_family: str, ) -> None: pytester.makepyfile( """ @@ -521,22 +539,22 @@ class TestPython: "-o", f"junit_logging={junit_logging}", family=xunit_family ) assert result.ret, "Expected ret > 0" - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(failures=1, tests=1) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr(classname="test_failure_function", name="test_fail") - fnode = tnode.find_first_by_tag("failure") + fnode = tnode.get_first_by_tag("failure") fnode.assert_attr(message="ValueError: 42") assert "ValueError" in fnode.toxml(), "ValueError not included" if junit_logging in ["log", "all"]: - logdata = tnode.find_first_by_tag("system-out") + logdata = tnode.get_first_by_tag("system-out") log_xml = logdata.toxml() assert logdata.tag == "system-out", "Expected tag: system-out" assert "info msg" not in log_xml, "Unexpected INFO message" assert "warning msg" in log_xml, "Missing WARN message" if junit_logging in ["system-out", "out-err", "all"]: - systemout = tnode.find_first_by_tag("system-out") + systemout = tnode.get_first_by_tag("system-out") systemout_xml = systemout.toxml() assert systemout.tag == "system-out", "Expected tag: system-out" assert "info msg" not in systemout_xml, "INFO message found in system-out" @@ -544,7 +562,7 @@ class TestPython: "hello-stdout" in systemout_xml ), "Missing 'hello-stdout' in system-out" if junit_logging in ["system-err", "out-err", "all"]: - systemerr = tnode.find_first_by_tag("system-err") + systemerr = tnode.get_first_by_tag("system-err") systemerr_xml = systemerr.toxml() assert systemerr.tag == "system-err", "Expected tag: system-err" assert "info msg" not in systemerr_xml, "INFO message found in system-err" @@ -575,9 +593,9 @@ class TestPython: """ ) result, dom = run_and_parse(family=xunit_family) - node = dom.find_first_by_tag("testsuite") - tnode = node.find_first_by_tag("testcase") - fnode = tnode.find_first_by_tag("failure") + node = dom.get_first_by_tag("testsuite") + tnode = node.get_first_by_tag("testcase") + fnode = tnode.get_first_by_tag("failure") fnode.assert_attr(message="AssertionError: An error\nassert 0") @parametrize_families @@ -597,15 +615,15 @@ class TestPython: "-o", "junit_logging=system-out", family=xunit_family ) assert result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(failures=3, tests=3) - - for index, char in enumerate("<&'"): - tnode = node.find_nth_by_tag("testcase", index) + tnodes = node.find_by_tag("testcase") + assert len(tnodes) == 3 + for tnode, char in zip(tnodes, "<&'"): tnode.assert_attr( classname="test_failure_escape", name=f"test_func[{char}]" ) - sysout = tnode.find_first_by_tag("system-out") + sysout = tnode.get_first_by_tag("system-out") text = sysout.text assert f"{char}\n" in text @@ -624,11 +642,11 @@ class TestPython: ) result, dom = run_and_parse("--junitprefix=xyz", family=xunit_family) assert result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(failures=1, tests=2) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr(classname="xyz.test_junit_prefixing", name="test_func") - tnode = node.find_nth_by_tag("testcase", 1) + tnode = node.find_by_tag("testcase")[1] tnode.assert_attr( classname="xyz.test_junit_prefixing.TestHello", name="test_hello" ) @@ -646,11 +664,11 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) assert not result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(skipped=1, tests=1) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr(classname="test_xfailure_function", name="test_xfail") - fnode = tnode.find_first_by_tag("skipped") + fnode = tnode.get_first_by_tag("skipped") fnode.assert_attr(type="pytest.xfail", message="42") @parametrize_families @@ -667,11 +685,11 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) assert not result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(skipped=1, tests=1) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr(classname="test_xfailure_marker", name="test_xfail") - fnode = tnode.find_first_by_tag("skipped") + fnode = tnode.get_first_by_tag("skipped") fnode.assert_attr(type="pytest.xfail", message="42") @pytest.mark.parametrize( @@ -693,17 +711,17 @@ class TestPython: """ ) result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") - node = dom.find_first_by_tag("testsuite") - tnode = node.find_first_by_tag("testcase") - if junit_logging in ["system-err", "out-err", "all"]: - assert len(tnode.find_by_tag("system-err")) == 1 - else: - assert len(tnode.find_by_tag("system-err")) == 0 + node = dom.get_first_by_tag("testsuite") + tnode = node.get_first_by_tag("testcase") - if junit_logging in ["log", "system-out", "out-err", "all"]: - assert len(tnode.find_by_tag("system-out")) == 1 - else: - assert len(tnode.find_by_tag("system-out")) == 0 + has_err_logging = junit_logging in ["system-err", "out-err", "all"] + expected_err_output_len = 1 if has_err_logging else 0 + assert len(tnode.find_by_tag("system-err")) == expected_err_output_len + + has_out_logigng = junit_logging in ("log", "system-out", "out-err", "all") + expected_out_output_len = 1 if has_out_logigng else 0 + + assert len(tnode.find_by_tag("system-out")) == expected_out_output_len @parametrize_families def test_xfailure_xpass( @@ -719,9 +737,9 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) # assert result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(skipped=0, tests=1) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr(classname="test_xfailure_xpass", name="test_xpass") @parametrize_families @@ -738,11 +756,11 @@ class TestPython: ) result, dom = run_and_parse(family=xunit_family) # assert result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(skipped=0, tests=1) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr(classname="test_xfailure_xpass_strict", name="test_xpass") - fnode = tnode.find_first_by_tag("failure") + fnode = tnode.get_first_by_tag("failure") fnode.assert_attr(message="[XPASS(strict)] This needs to fail!") @parametrize_families @@ -752,10 +770,10 @@ class TestPython: pytester.makepyfile("syntax error") result, dom = run_and_parse(family=xunit_family) assert result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(errors=1, tests=1) - tnode = node.find_first_by_tag("testcase") - fnode = tnode.find_first_by_tag("error") + tnode = node.get_first_by_tag("testcase") + fnode = tnode.get_first_by_tag("error") fnode.assert_attr(message="collection failure") assert "SyntaxError" in fnode.toxml() @@ -771,8 +789,8 @@ class TestPython: ) result, dom = run_and_parse() assert result.ret == 1 - tnode = dom.find_first_by_tag("testcase") - fnode = tnode.find_first_by_tag("failure") + tnode = dom.get_first_by_tag("testcase") + fnode = tnode.get_first_by_tag("failure") assert "hx" in fnode.toxml() def test_assertion_binchars( @@ -803,14 +821,14 @@ class TestPython: """ ) result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") - node = dom.find_first_by_tag("testsuite") - pnode = node.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testsuite") + pnode = node.get_first_by_tag("testcase") if junit_logging == "no": assert not node.find_by_tag( "system-out" ), "system-out should not be generated" if junit_logging == "system-out": - systemout = pnode.find_first_by_tag("system-out") + systemout = pnode.get_first_by_tag("system-out") assert ( "hello-stdout" in systemout.toxml() ), "'hello-stdout' should be in system-out" @@ -827,14 +845,14 @@ class TestPython: """ ) result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") - node = dom.find_first_by_tag("testsuite") - pnode = node.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testsuite") + pnode = node.get_first_by_tag("testcase") if junit_logging == "no": assert not node.find_by_tag( "system-err" ), "system-err should not be generated" if junit_logging == "system-err": - systemerr = pnode.find_first_by_tag("system-err") + systemerr = pnode.get_first_by_tag("system-err") assert ( "hello-stderr" in systemerr.toxml() ), "'hello-stderr' should be in system-err" @@ -856,14 +874,14 @@ class TestPython: """ ) result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") - node = dom.find_first_by_tag("testsuite") - pnode = node.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testsuite") + pnode = node.get_first_by_tag("testcase") if junit_logging == "no": assert not node.find_by_tag( "system-out" ), "system-out should not be generated" if junit_logging == "system-out": - systemout = pnode.find_first_by_tag("system-out") + systemout = pnode.get_first_by_tag("system-out") assert ( "hello-stdout" in systemout.toxml() ), "'hello-stdout' should be in system-out" @@ -886,14 +904,14 @@ class TestPython: """ ) result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") - node = dom.find_first_by_tag("testsuite") - pnode = node.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testsuite") + pnode = node.get_first_by_tag("testcase") if junit_logging == "no": assert not node.find_by_tag( "system-err" ), "system-err should not be generated" if junit_logging == "system-err": - systemerr = pnode.find_first_by_tag("system-err") + systemerr = pnode.get_first_by_tag("system-err") assert ( "hello-stderr" in systemerr.toxml() ), "'hello-stderr' should be in system-err" @@ -917,14 +935,14 @@ class TestPython: """ ) result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") - node = dom.find_first_by_tag("testsuite") - pnode = node.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testsuite") + pnode = node.get_first_by_tag("testcase") if junit_logging == "no": assert not node.find_by_tag( "system-out" ), "system-out should not be generated" if junit_logging == "system-out": - systemout = pnode.find_first_by_tag("system-out") + systemout = pnode.get_first_by_tag("system-out") assert "hello-stdout call" in systemout.toxml() assert "hello-stdout teardown" in systemout.toxml() @@ -944,12 +962,12 @@ def test_dont_configure_on_workers(tmp_path: Path) -> None: if TYPE_CHECKING: workerinput = None - def __init__(self): + def __init__(self) -> None: self.pluginmanager = self self.option = self self.stash = Stash() - def getini(self, name): + def getini(self, name: str) -> str: return "pytest" junitprefix = None @@ -988,11 +1006,11 @@ class TestNonPython: pytester.path.joinpath("myfile.xyz").write_text("hello", encoding="utf-8") result, dom = run_and_parse(family=xunit_family) assert result.ret - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(errors=0, failures=1, skipped=0, tests=1) - tnode = node.find_first_by_tag("testcase") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr(name="myfile.xyz") - fnode = tnode.find_first_by_tag("failure") + fnode = tnode.get_first_by_tag("failure") fnode.assert_attr(message="custom item runtest failed") assert "custom item runtest failed" in fnode.toxml() @@ -1133,7 +1151,7 @@ def test_escaped_parametrized_names_xml( ) result, dom = run_and_parse() assert result.ret == 0 - node = dom.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testcase") node.assert_attr(name="test_func[\\x00]") @@ -1150,7 +1168,7 @@ def test_double_colon_split_function_issue469( ) result, dom = run_and_parse() assert result.ret == 0 - node = dom.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testcase") node.assert_attr(classname="test_double_colon_split_function_issue469") node.assert_attr(name="test_func[double::colon]") @@ -1169,7 +1187,7 @@ def test_double_colon_split_method_issue469( ) result, dom = run_and_parse() assert result.ret == 0 - node = dom.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testcase") node.assert_attr(classname="test_double_colon_split_method_issue469.TestClass") node.assert_attr(name="test_func[double::colon]") @@ -1217,9 +1235,9 @@ def test_record_property(pytester: Pytester, run_and_parse: RunAndParse) -> None """ ) result, dom = run_and_parse() - node = dom.find_first_by_tag("testsuite") - tnode = node.find_first_by_tag("testcase") - psnode = tnode.find_first_by_tag("properties") + node = dom.get_first_by_tag("testsuite") + tnode = node.get_first_by_tag("testcase") + psnode = tnode.get_first_by_tag("properties") pnodes = psnode.find_by_tag("property") pnodes[0].assert_attr(name="bar", value="1") pnodes[1].assert_attr(name="foo", value="<1") @@ -1245,10 +1263,10 @@ def test_record_property_on_test_and_teardown_failure( """ ) result, dom = run_and_parse() - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") tnodes = node.find_by_tag("testcase") for tnode in tnodes: - psnode = tnode.find_first_by_tag("properties") + psnode = tnode.get_first_by_tag("properties") assert psnode, f"testcase didn't had expected properties:\n{tnode}" pnodes = psnode.find_by_tag("property") pnodes[0].assert_attr(name="bar", value="1") @@ -1267,9 +1285,9 @@ def test_record_property_same_name( """ ) result, dom = run_and_parse() - node = dom.find_first_by_tag("testsuite") - tnode = node.find_first_by_tag("testcase") - psnode = tnode.find_first_by_tag("properties") + node = dom.get_first_by_tag("testsuite") + tnode = node.get_first_by_tag("testcase") + psnode = tnode.get_first_by_tag("properties") pnodes = psnode.find_by_tag("property") pnodes[0].assert_attr(name="foo", value="bar") pnodes[1].assert_attr(name="foo", value="baz") @@ -1309,8 +1327,8 @@ def test_record_attribute(pytester: Pytester, run_and_parse: RunAndParse) -> Non """ ) result, dom = run_and_parse() - node = dom.find_first_by_tag("testsuite") - tnode = node.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testsuite") + tnode = node.get_first_by_tag("testcase") tnode.assert_attr(bar="1") tnode.assert_attr(foo="<1") result.stdout.fnmatch_lines( @@ -1372,7 +1390,7 @@ def test_random_report_log_xdist( """ ) _, dom = run_and_parse("-n2") - suite_node = dom.find_first_by_tag("testsuite") + suite_node = dom.get_first_by_tag("testsuite") failed = [] for case_node in suite_node.find_by_tag("testcase"): if case_node.find_first_by_tag("failure"): @@ -1468,7 +1486,7 @@ def test_fancy_items_regression(pytester: Pytester, run_and_parse: RunAndParse) result.stdout.no_fnmatch_line("*INTERNALERROR*") items = sorted( - "%(classname)s %(name)s" % x # noqa: UP031 + f"{x['classname']} {x['name']}" # dom is a DomNode not a mapping, it's not possible to ** it. for x in dom.find_by_tag("testcase") ) @@ -1563,10 +1581,11 @@ def test_record_testsuite_property( ) result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 - node = dom.find_first_by_tag("testsuite") - properties_node = node.find_first_by_tag("properties") - p1_node = properties_node.find_nth_by_tag("property", 0) - p2_node = properties_node.find_nth_by_tag("property", 1) + node = dom.get_first_by_tag("testsuite") + properties_node = node.get_first_by_tag("properties") + p1_node, p2_node = properties_node.find_by_tag( + "property", + )[:2] p1_node.assert_attr(name="stats", value="all good") p2_node.assert_attr(name="stats", value="10") @@ -1626,7 +1645,7 @@ def test_set_suite_name( ) result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 - node = dom.find_first_by_tag("testsuite") + node = dom.get_first_by_tag("testsuite") node.assert_attr(name=expected) @@ -1642,8 +1661,8 @@ def test_escaped_skipreason_issue3533( """ ) _, dom = run_and_parse() - node = dom.find_first_by_tag("testcase") - snode = node.find_first_by_tag("skipped") + node = dom.get_first_by_tag("testcase") + snode = node.get_first_by_tag("skipped") assert "1 <> 2" in snode.text snode.assert_attr(message="1 <> 2") @@ -1659,8 +1678,8 @@ def test_bin_escaped_skipreason(pytester: Pytester, run_and_parse: RunAndParse) """ ) _, dom = run_and_parse() - node = dom.find_first_by_tag("testcase") - snode = node.find_first_by_tag("skipped") + node = dom.get_first_by_tag("testcase") + snode = node.get_first_by_tag("skipped") assert "#x1B[31;1mred#x1B[0m" in snode.text snode.assert_attr(message="#x1B[31;1mred#x1B[0m") @@ -1681,8 +1700,8 @@ def test_escaped_setup_teardown_error( """ ) _, dom = run_and_parse() - node = dom.find_first_by_tag("testcase") - snode = node.find_first_by_tag("error") + node = dom.get_first_by_tag("testcase") + snode = node.get_first_by_tag("error") assert "#x1B[31mred#x1B[m" in snode["message"] assert "#x1B[31mred#x1B[m" in snode.text @@ -1713,7 +1732,7 @@ def test_logging_passing_tests_disabled_does_not_log_test_output( ) result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 - node = dom.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testcase") assert len(node.find_by_tag("system-err")) == 0 assert len(node.find_by_tag("system-out")) == 0 @@ -1748,7 +1767,7 @@ def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430( "-o", f"junit_logging={junit_logging}", family=xunit_family ) assert result.ret == 1 - node = dom.find_first_by_tag("testcase") + node = dom.get_first_by_tag("testcase") if junit_logging == "system-out": assert len(node.find_by_tag("system-err")) == 0 assert len(node.find_by_tag("system-out")) == 1 diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 558e3d35c..5d6709cec 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -602,13 +602,12 @@ class TestXFail: self, expected, actual, matchline, pytester: Pytester ) -> None: p = pytester.makepyfile( - """ + f""" import pytest - @pytest.mark.xfail(raises=%s) + @pytest.mark.xfail(raises={expected}) def test_raises(): - raise %s() - """ # noqa: UP031 (python syntax issues) - % (expected, actual) + raise {actual}() + """ ) result = pytester.runpytest(p) result.stdout.fnmatch_lines([matchline]) @@ -900,13 +899,12 @@ class TestSkipif: ) def test_skipif_reporting(self, pytester: Pytester, params) -> None: p = pytester.makepyfile( - test_foo=""" + test_foo=f""" import pytest - @pytest.mark.skipif(%(params)s) + @pytest.mark.skipif({params}) def test_that(): assert 0 - """ # noqa: UP031 (python syntax issues) - % dict(params=params) + """ ) result = pytester.runpytest(p, "-s", "-rs") result.stdout.fnmatch_lines(["*SKIP*1*test_foo.py*platform*", "*1 skipped*"])