This commit is contained in:
Ronny Pfannschmidt 2024-06-26 18:21:35 +02:00 committed by GitHub
commit bae7edc7c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 263 additions and 230 deletions

View File

@ -0,0 +1,5 @@
* migrate formatting tests to fstrings
* use typesafe constructs in the junitxml tests
* separate mocktiming into ``_pytest.timing``
-- by :user:`RonnyPfannschmidt`

View File

@ -73,13 +73,9 @@ def _show_fixture_action(
# Use smaller indentation the higher the scope: Session = 0, Package = 1, etc. # Use smaller indentation the higher the scope: Session = 0, Package = 1, etc.
scope_indent = list(reversed(Scope)).index(fixturedef._scope) scope_indent = list(reversed(Scope)).index(fixturedef._scope)
tw.write(" " * 2 * scope_indent) tw.write(" " * 2 * scope_indent)
tw.write(
"{step} {scope} {fixture}".format( # noqa: UP032 (Readability) scopename = fixturedef.scope[0].upper()
step=msg.ljust(8), # align the output to TEARDOWN tw.write(f"{msg:<8} {scopename} {fixturedef.argname}")
scope=fixturedef.scope[0].upper(),
fixture=fixturedef.argname,
)
)
if msg == "SETUP": if msg == "SETUP":
deps = sorted(arg for arg in fixturedef.argnames if arg != "request") deps = sorted(arg for arg in fixturedef.argnames if arg != "request")

View File

@ -8,9 +8,45 @@ Fixture "mock_timing" also interacts with this module for pytest's own tests.
from __future__ import annotations from __future__ import annotations
import dataclasses
from datetime import datetime
from time import perf_counter from time import perf_counter
from time import sleep from time import sleep
from time import time 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"] __all__ = ["perf_counter", "sleep", "time"]

View File

@ -1,7 +1,6 @@
# mypy: allow-untyped-defs # mypy: allow-untyped-defs
from __future__ import annotations from __future__ import annotations
import dataclasses
import re import re
import sys import sys
from typing import Generator 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 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. numbers and obtain accurate time() calls at the end, making tests reliable and instant.
""" """
from _pytest.timing import MockTiming
@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)
result = MockTiming() result = MockTiming()
result.patch() result.patch(monkeypatch)
return result return result

View File

@ -2199,14 +2199,14 @@ class TestAutouseManagement:
) -> None: ) -> None:
"""#226""" """#226"""
pytester.makepyfile( pytester.makepyfile(
""" f"""
import pytest import pytest
values = [] values = []
@pytest.fixture(%(param1)s) @pytest.fixture({param1})
def arg1(request): def arg1(request):
request.addfinalizer(lambda: values.append("fin1")) request.addfinalizer(lambda: values.append("fin1"))
values.append("new1") values.append("new1")
@pytest.fixture(%(param2)s) @pytest.fixture({param2})
def arg2(request, arg1): def arg2(request, arg1):
request.addfinalizer(lambda: values.append("fin2")) request.addfinalizer(lambda: values.append("fin2"))
values.append("new2") values.append("new2")
@ -2215,8 +2215,7 @@ class TestAutouseManagement:
pass pass
def test_check(): def test_check():
assert values == ["new1", "new2", "fin2", "fin1"] assert values == ["new1", "new2", "fin2", "fin1"]
""" # noqa: UP031 (python syntax issues) """
% locals()
) )
reprec = pytester.inline_run("-s") reprec = pytester.inline_run("-s")
reprec.assertoutcome(passed=2) reprec.assertoutcome(passed=2)
@ -3096,21 +3095,21 @@ class TestFixtureMarker:
) -> None: ) -> None:
"""#246""" """#246"""
pytester.makepyfile( pytester.makepyfile(
""" f"""
import pytest import pytest
values = [] values = []
@pytest.fixture(scope=%(scope)r, params=["1"]) @pytest.fixture(scope={scope!r}, params=["1"])
def fix1(request): def fix1(request):
return request.param return request.param
@pytest.fixture(scope=%(scope)r) @pytest.fixture(scope={scope!r})
def fix2(request, base): def fix2(request, base):
def cleanup_fix2(): def cleanup_fix2():
assert not values, "base should not have been finalized" assert not values, "base should not have been finalized"
request.addfinalizer(cleanup_fix2) request.addfinalizer(cleanup_fix2)
@pytest.fixture(scope=%(scope)r) @pytest.fixture(scope={scope!r})
def base(request, fix1): def base(request, fix1):
def cleanup_base(): def cleanup_base():
values.append("fin_base") values.append("fin_base")
@ -3123,8 +3122,7 @@ class TestFixtureMarker:
pass pass
def test_other(): def test_other():
pass pass
""" # noqa: UP031 (python syntax issues) """
% {"scope": scope}
) )
reprec = pytester.inline_run("-lvs") reprec = pytester.inline_run("-lvs")
reprec.assertoutcome(passed=3) reprec.assertoutcome(passed=3)
@ -3310,42 +3308,40 @@ class TestRequestScopeAccess:
def test_setup(self, pytester: Pytester, scope, ok, error) -> None: def test_setup(self, pytester: Pytester, scope, ok, error) -> None:
pytester.makepyfile( pytester.makepyfile(
""" f"""
import pytest import pytest
@pytest.fixture(scope=%r, autouse=True) @pytest.fixture(scope={scope!r}, autouse=True)
def myscoped(request): def myscoped(request):
for x in %r: for x in {ok.split()}:
assert hasattr(request, x) assert hasattr(request, x)
for x in %r: for x in {error.split()}:
pytest.raises(AttributeError, lambda: pytest.raises(AttributeError, lambda:
getattr(request, x)) getattr(request, x))
assert request.session assert request.session
assert request.config assert request.config
def test_func(): def test_func():
pass pass
""" # noqa: UP031 (python syntax issues) """
% (scope, ok.split(), error.split())
) )
reprec = pytester.inline_run("-l") reprec = pytester.inline_run("-l")
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
def test_funcarg(self, pytester: Pytester, scope, ok, error) -> None: def test_funcarg(self, pytester: Pytester, scope, ok, error) -> None:
pytester.makepyfile( pytester.makepyfile(
""" f"""
import pytest import pytest
@pytest.fixture(scope=%r) @pytest.fixture(scope={scope!r})
def arg(request): def arg(request):
for x in %r: for x in {ok.split()!r}:
assert hasattr(request, x) assert hasattr(request, x)
for x in %r: for x in {error.split()!r}:
pytest.raises(AttributeError, lambda: pytest.raises(AttributeError, lambda:
getattr(request, x)) getattr(request, x))
assert request.session assert request.session
assert request.config assert request.config
def test_func(arg): def test_func(arg):
pass pass
""" # noqa: UP031 (python syntax issues) """
% (scope, ok.split(), error.split())
) )
reprec = pytester.inline_run() reprec = pytester.inline_run()
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)

View File

@ -1,10 +1,10 @@
# mypy: allow-untyped-defs
from __future__ import annotations from __future__ import annotations
from datetime import datetime from datetime import datetime
import os import os
from pathlib import Path from pathlib import Path
import platform import platform
from typing import Any
from typing import cast from typing import cast
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from xml.dom import minidom from xml.dom import minidom
@ -20,6 +20,7 @@ from _pytest.pytester import RunResult
from _pytest.reports import BaseReport from _pytest.reports import BaseReport
from _pytest.reports import TestReport from _pytest.reports import TestReport
from _pytest.stash import Stash from _pytest.stash import Stash
import _pytest.timing
import pytest import pytest
@ -38,7 +39,7 @@ class RunAndParse:
def __call__( def __call__(
self, *args: str | os.PathLike[str], family: str | None = "xunit1" self, *args: str | os.PathLike[str], family: str | None = "xunit1"
) -> tuple[RunResult, DomNode]: ) -> tuple[RunResult, DomDocument]:
if family: if family:
args = ("-o", "junit_family=" + family, *args) args = ("-o", "junit_family=" + family, *args)
xml_path = self.pytester.path.joinpath("junit.xml") xml_path = self.pytester.path.joinpath("junit.xml")
@ -47,7 +48,7 @@ class RunAndParse:
with xml_path.open(encoding="utf-8") as f: with xml_path.open(encoding="utf-8") as f:
self.schema.validate(f) self.schema.validate(f)
xmldoc = minidom.parse(str(xml_path)) xmldoc = minidom.parse(str(xml_path))
return result, DomNode(xmldoc) return result, DomDocument(xmldoc)
@pytest.fixture @pytest.fixture
@ -61,78 +62,91 @@ def run_and_parse(pytester: Pytester, schema: xmlschema.XMLSchema) -> RunAndPars
return RunAndParse(pytester, schema) return RunAndParse(pytester, schema)
def assert_attr(node, **kwargs): def assert_attr(node: minidom.Element, **kwargs: object) -> None:
__tracebackhide__ = True __tracebackhide__ = True
def nodeval(node, name): def nodeval(node: minidom.Element, name: str) -> str | None:
anode = node.getAttributeNode(name) anode = node.getAttributeNode(name)
if anode is not None: return anode.value if anode is not None else None
return anode.value
expected = {name: str(value) for name, value in kwargs.items()} expected = {name: str(value) for name, value in kwargs.items()}
on_node = {name: nodeval(node, name) for name in expected} on_node = {name: nodeval(node, name) for name in expected}
assert on_node == expected assert on_node == expected
class DomNode: class DomDocument:
def __init__(self, dom): def __init__(self, dom: minidom.Document):
self.__node = dom self._node = dom
def __repr__(self): _node: minidom.Document | minidom.Element
return self.__node.toxml()
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) return self.find_nth_by_tag(tag, 0)
def _by_tag(self, tag): def get_first_by_tag(self, tag: str) -> DomNode:
return self.__node.getElementsByTagName(tag) 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 @property
def children(self): def children(self) -> list[DomNode]:
return [type(self)(x) for x in self.__node.childNodes] return [DomNode(x) for x in self._node.childNodes]
@property @property
def get_unique_child(self): def get_unique_child(self) -> DomNode:
children = self.children children = self.children
assert len(children) == 1 assert len(children) == 1
return children[0] return children[0]
def find_nth_by_tag(self, tag, n): def toxml(self) -> str:
items = self._by_tag(tag) return self._node.toxml()
try:
nth = items[n]
except IndexError:
pass
else:
return type(self)(nth)
def find_by_tag(self, tag):
t = type(self)
return [t(x) for x in self.__node.getElementsByTagName(tag)]
def __getitem__(self, key): class DomNode(DomDocument):
node = self.__node.getAttributeNode(key) _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: 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 __tracebackhide__ = True
return assert_attr(self.__node, **kwargs) return assert_attr(self._node, **kwargs)
def toxml(self):
return self.__node.toxml()
@property @property
def text(self): def text(self) -> str:
return self.__node.childNodes[0].wholeText return cast(str, self._node.childNodes[0].wholeText)
@property @property
def tag(self): def tag(self) -> str:
return self.__node.tagName return self._node.tagName
@property @property
def next_sibling(self): def next_sibling(self) -> DomNode:
return type(self)(self.__node.nextSibling) return DomNode(self._node.nextSibling)
parametrize_families = pytest.mark.parametrize("xunit_family", ["xunit1", "xunit2"]) parametrize_families = pytest.mark.parametrize("xunit_family", ["xunit1", "xunit2"])
@ -162,7 +176,7 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret 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) node.assert_attr(name="pytest", errors=0, failures=1, skipped=2, tests=5)
@parametrize_families @parametrize_families
@ -191,7 +205,7 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret 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) node.assert_attr(name="pytest", errors=1, failures=2, skipped=1, tests=5)
@parametrize_families @parametrize_families
@ -205,7 +219,7 @@ class TestPython:
""" """
) )
result, dom = run_and_parse(family=xunit_family) 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()) node.assert_attr(hostname=platform.node())
@parametrize_families @parametrize_families
@ -220,12 +234,15 @@ class TestPython:
) )
start_time = datetime.now() start_time = datetime.now()
result, dom = run_and_parse(family=xunit_family) 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") timestamp = datetime.strptime(node["timestamp"], "%Y-%m-%dT%H:%M:%S.%f")
assert start_time <= timestamp < datetime.now() assert start_time <= timestamp < datetime.now()
def test_timing_function( 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: ) -> None:
pytester.makepyfile( pytester.makepyfile(
""" """
@ -239,9 +256,10 @@ class TestPython:
""" """
) )
result, dom = run_and_parse() result, dom = run_and_parse()
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase") tnode = node.get_first_by_tag("testcase")
val = tnode["time"] val = tnode["time"]
assert val is not None
assert float(val) == 7.0 assert float(val) == 7.0
@pytest.mark.parametrize("duration_report", ["call", "total"]) @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 # mock LogXML.node_reporter so it always sets a known duration to each test report object
original_node_reporter = LogXML.node_reporter 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 report.duration = 1.0
reporter = original_node_reporter(s, report) reporter = original_node_reporter(s, report)
return reporter return reporter
@ -269,8 +287,8 @@ class TestPython:
""" """
) )
result, dom = run_and_parse("-o", f"junit_duration_report={duration_report}") result, dom = run_and_parse("-o", f"junit_duration_report={duration_report}")
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase") tnode = node.get_first_by_tag("testcase")
val = float(tnode["time"]) val = float(tnode["time"])
if duration_report == "total": if duration_report == "total":
assert val == 3.0 assert val == 3.0
@ -295,11 +313,11 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret assert result.ret
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
node.assert_attr(errors=1, tests=1) 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") 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"') fnode.assert_attr(message='failed on setup with "ValueError: Error reason"')
assert "ValueError" in fnode.toxml() assert "ValueError" in fnode.toxml()
@ -321,10 +339,10 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret assert result.ret
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase") tnode = node.get_first_by_tag("testcase")
tnode.assert_attr(classname="test_teardown_error", name="test_function") 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"') fnode.assert_attr(message='failed on teardown with "ValueError: Error reason"')
assert "ValueError" in fnode.toxml() assert "ValueError" in fnode.toxml()
@ -346,15 +364,15 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret 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) node.assert_attr(errors=1, failures=1, tests=1)
first, second = dom.find_by_tag("testcase") first, second = dom.find_by_tag("testcase")
assert first assert first
assert second assert second
assert first != 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") fnode.assert_attr(message="Exception: Call Exception")
snode = second.find_first_by_tag("error") snode = second.get_first_by_tag("error")
snode.assert_attr( snode.assert_attr(
message='failed on teardown with "Exception: Teardown Exception"' message='failed on teardown with "Exception: Teardown Exception"'
) )
@ -372,11 +390,11 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret == 0 assert result.ret == 0
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
node.assert_attr(skipped=1) 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") 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") snode.assert_attr(type="pytest.skip", message="hello23")
@parametrize_families @parametrize_families
@ -393,13 +411,13 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret == 0 assert result.ret == 0
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
node.assert_attr(skipped=1) node.assert_attr(skipped=1)
tnode = node.find_first_by_tag("testcase") tnode = node.get_first_by_tag("testcase")
tnode.assert_attr( tnode.assert_attr(
classname="test_mark_skip_contains_name_reason", name="test_skip" 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") snode.assert_attr(type="pytest.skip", message="hello24")
@parametrize_families @parametrize_families
@ -417,13 +435,13 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret == 0 assert result.ret == 0
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
node.assert_attr(skipped=1) node.assert_attr(skipped=1)
tnode = node.find_first_by_tag("testcase") tnode = node.get_first_by_tag("testcase")
tnode.assert_attr( tnode.assert_attr(
classname="test_mark_skipif_contains_name_reason", name="test_skip" 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") snode.assert_attr(type="pytest.skip", message="hello25")
@parametrize_families @parametrize_families
@ -440,7 +458,7 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret == 0 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 assert "bar!" not in node_xml
@parametrize_families @parametrize_families
@ -456,9 +474,9 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret assert result.ret
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
node.assert_attr(failures=1) node.assert_attr(failures=1)
tnode = node.find_first_by_tag("testcase") tnode = node.get_first_by_tag("testcase")
tnode.assert_attr( tnode.assert_attr(
classname="test_classname_instance.TestClass", name="test_method" 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") p.write_text("def test_func(): 0/0", encoding="utf-8")
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret assert result.ret
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
node.assert_attr(failures=1) 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") tnode.assert_attr(classname="sub.test_hello", name="test_func")
@parametrize_families @parametrize_families
@ -484,11 +502,11 @@ class TestPython:
pytester.makepyfile("def test_function(): pass") pytester.makepyfile("def test_function(): pass")
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret assert result.ret
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
node.assert_attr(errors=1, tests=1) 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") 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") fnode.assert_attr(message="internal error")
assert "Division" in fnode.toxml() assert "Division" in fnode.toxml()
@ -499,9 +517,9 @@ class TestPython:
def test_failure_function( def test_failure_function(
self, self,
pytester: Pytester, pytester: Pytester,
junit_logging, junit_logging: str,
run_and_parse: RunAndParse, run_and_parse: RunAndParse,
xunit_family, xunit_family: str,
) -> None: ) -> None:
pytester.makepyfile( pytester.makepyfile(
""" """
@ -521,22 +539,22 @@ class TestPython:
"-o", f"junit_logging={junit_logging}", family=xunit_family "-o", f"junit_logging={junit_logging}", family=xunit_family
) )
assert result.ret, "Expected ret > 0" 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) 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") 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") fnode.assert_attr(message="ValueError: 42")
assert "ValueError" in fnode.toxml(), "ValueError not included" assert "ValueError" in fnode.toxml(), "ValueError not included"
if junit_logging in ["log", "all"]: 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() log_xml = logdata.toxml()
assert logdata.tag == "system-out", "Expected tag: system-out" assert logdata.tag == "system-out", "Expected tag: system-out"
assert "info msg" not in log_xml, "Unexpected INFO message" assert "info msg" not in log_xml, "Unexpected INFO message"
assert "warning msg" in log_xml, "Missing WARN message" assert "warning msg" in log_xml, "Missing WARN message"
if junit_logging in ["system-out", "out-err", "all"]: 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() systemout_xml = systemout.toxml()
assert systemout.tag == "system-out", "Expected tag: system-out" assert systemout.tag == "system-out", "Expected tag: system-out"
assert "info msg" not in systemout_xml, "INFO message found in 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 "hello-stdout" in systemout_xml
), "Missing 'hello-stdout' in system-out" ), "Missing 'hello-stdout' in system-out"
if junit_logging in ["system-err", "out-err", "all"]: 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() systemerr_xml = systemerr.toxml()
assert systemerr.tag == "system-err", "Expected tag: system-err" assert systemerr.tag == "system-err", "Expected tag: system-err"
assert "info msg" not in systemerr_xml, "INFO message found in 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) result, dom = run_and_parse(family=xunit_family)
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase") tnode = node.get_first_by_tag("testcase")
fnode = tnode.find_first_by_tag("failure") fnode = tnode.get_first_by_tag("failure")
fnode.assert_attr(message="AssertionError: An error\nassert 0") fnode.assert_attr(message="AssertionError: An error\nassert 0")
@parametrize_families @parametrize_families
@ -597,15 +615,15 @@ class TestPython:
"-o", "junit_logging=system-out", family=xunit_family "-o", "junit_logging=system-out", family=xunit_family
) )
assert result.ret assert result.ret
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
node.assert_attr(failures=3, tests=3) node.assert_attr(failures=3, tests=3)
tnodes = node.find_by_tag("testcase")
for index, char in enumerate("<&'"): assert len(tnodes) == 3
tnode = node.find_nth_by_tag("testcase", index) for tnode, char in zip(tnodes, "<&'"):
tnode.assert_attr( tnode.assert_attr(
classname="test_failure_escape", name=f"test_func[{char}]" 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 text = sysout.text
assert f"{char}\n" in text assert f"{char}\n" in text
@ -624,11 +642,11 @@ class TestPython:
) )
result, dom = run_and_parse("--junitprefix=xyz", family=xunit_family) result, dom = run_and_parse("--junitprefix=xyz", family=xunit_family)
assert result.ret assert result.ret
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
node.assert_attr(failures=1, tests=2) 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.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( tnode.assert_attr(
classname="xyz.test_junit_prefixing.TestHello", name="test_hello" classname="xyz.test_junit_prefixing.TestHello", name="test_hello"
) )
@ -646,11 +664,11 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert not result.ret 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) 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") 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") fnode.assert_attr(type="pytest.xfail", message="42")
@parametrize_families @parametrize_families
@ -667,11 +685,11 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert not result.ret 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) 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") 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") fnode.assert_attr(type="pytest.xfail", message="42")
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -693,17 +711,17 @@ class TestPython:
""" """
) )
result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") result, dom = run_and_parse("-o", f"junit_logging={junit_logging}")
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase") tnode = node.get_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
if junit_logging in ["log", "system-out", "out-err", "all"]: has_err_logging = junit_logging in ["system-err", "out-err", "all"]
assert len(tnode.find_by_tag("system-out")) == 1 expected_err_output_len = 1 if has_err_logging else 0
else: assert len(tnode.find_by_tag("system-err")) == expected_err_output_len
assert len(tnode.find_by_tag("system-out")) == 0
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 @parametrize_families
def test_xfailure_xpass( def test_xfailure_xpass(
@ -719,9 +737,9 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
# assert result.ret # assert result.ret
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
node.assert_attr(skipped=0, tests=1) 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") tnode.assert_attr(classname="test_xfailure_xpass", name="test_xpass")
@parametrize_families @parametrize_families
@ -738,11 +756,11 @@ class TestPython:
) )
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
# assert result.ret # assert result.ret
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
node.assert_attr(skipped=0, tests=1) 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") 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!") fnode.assert_attr(message="[XPASS(strict)] This needs to fail!")
@parametrize_families @parametrize_families
@ -752,10 +770,10 @@ class TestPython:
pytester.makepyfile("syntax error") pytester.makepyfile("syntax error")
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret assert result.ret
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
node.assert_attr(errors=1, tests=1) node.assert_attr(errors=1, tests=1)
tnode = node.find_first_by_tag("testcase") tnode = node.get_first_by_tag("testcase")
fnode = tnode.find_first_by_tag("error") fnode = tnode.get_first_by_tag("error")
fnode.assert_attr(message="collection failure") fnode.assert_attr(message="collection failure")
assert "SyntaxError" in fnode.toxml() assert "SyntaxError" in fnode.toxml()
@ -771,8 +789,8 @@ class TestPython:
) )
result, dom = run_and_parse() result, dom = run_and_parse()
assert result.ret == 1 assert result.ret == 1
tnode = dom.find_first_by_tag("testcase") tnode = dom.get_first_by_tag("testcase")
fnode = tnode.find_first_by_tag("failure") fnode = tnode.get_first_by_tag("failure")
assert "hx" in fnode.toxml() assert "hx" in fnode.toxml()
def test_assertion_binchars( def test_assertion_binchars(
@ -803,14 +821,14 @@ class TestPython:
""" """
) )
result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") result, dom = run_and_parse("-o", f"junit_logging={junit_logging}")
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
pnode = node.find_first_by_tag("testcase") pnode = node.get_first_by_tag("testcase")
if junit_logging == "no": if junit_logging == "no":
assert not node.find_by_tag( assert not node.find_by_tag(
"system-out" "system-out"
), "system-out should not be generated" ), "system-out should not be generated"
if junit_logging == "system-out": if junit_logging == "system-out":
systemout = pnode.find_first_by_tag("system-out") systemout = pnode.get_first_by_tag("system-out")
assert ( assert (
"hello-stdout" in systemout.toxml() "hello-stdout" in systemout.toxml()
), "'hello-stdout' should be in system-out" ), "'hello-stdout' should be in system-out"
@ -827,14 +845,14 @@ class TestPython:
""" """
) )
result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") result, dom = run_and_parse("-o", f"junit_logging={junit_logging}")
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
pnode = node.find_first_by_tag("testcase") pnode = node.get_first_by_tag("testcase")
if junit_logging == "no": if junit_logging == "no":
assert not node.find_by_tag( assert not node.find_by_tag(
"system-err" "system-err"
), "system-err should not be generated" ), "system-err should not be generated"
if junit_logging == "system-err": if junit_logging == "system-err":
systemerr = pnode.find_first_by_tag("system-err") systemerr = pnode.get_first_by_tag("system-err")
assert ( assert (
"hello-stderr" in systemerr.toxml() "hello-stderr" in systemerr.toxml()
), "'hello-stderr' should be in system-err" ), "'hello-stderr' should be in system-err"
@ -856,14 +874,14 @@ class TestPython:
""" """
) )
result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") result, dom = run_and_parse("-o", f"junit_logging={junit_logging}")
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
pnode = node.find_first_by_tag("testcase") pnode = node.get_first_by_tag("testcase")
if junit_logging == "no": if junit_logging == "no":
assert not node.find_by_tag( assert not node.find_by_tag(
"system-out" "system-out"
), "system-out should not be generated" ), "system-out should not be generated"
if junit_logging == "system-out": if junit_logging == "system-out":
systemout = pnode.find_first_by_tag("system-out") systemout = pnode.get_first_by_tag("system-out")
assert ( assert (
"hello-stdout" in systemout.toxml() "hello-stdout" in systemout.toxml()
), "'hello-stdout' should be in system-out" ), "'hello-stdout' should be in system-out"
@ -886,14 +904,14 @@ class TestPython:
""" """
) )
result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") result, dom = run_and_parse("-o", f"junit_logging={junit_logging}")
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
pnode = node.find_first_by_tag("testcase") pnode = node.get_first_by_tag("testcase")
if junit_logging == "no": if junit_logging == "no":
assert not node.find_by_tag( assert not node.find_by_tag(
"system-err" "system-err"
), "system-err should not be generated" ), "system-err should not be generated"
if junit_logging == "system-err": if junit_logging == "system-err":
systemerr = pnode.find_first_by_tag("system-err") systemerr = pnode.get_first_by_tag("system-err")
assert ( assert (
"hello-stderr" in systemerr.toxml() "hello-stderr" in systemerr.toxml()
), "'hello-stderr' should be in system-err" ), "'hello-stderr' should be in system-err"
@ -917,14 +935,14 @@ class TestPython:
""" """
) )
result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") result, dom = run_and_parse("-o", f"junit_logging={junit_logging}")
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
pnode = node.find_first_by_tag("testcase") pnode = node.get_first_by_tag("testcase")
if junit_logging == "no": if junit_logging == "no":
assert not node.find_by_tag( assert not node.find_by_tag(
"system-out" "system-out"
), "system-out should not be generated" ), "system-out should not be generated"
if junit_logging == "system-out": 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 call" in systemout.toxml()
assert "hello-stdout teardown" 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: if TYPE_CHECKING:
workerinput = None workerinput = None
def __init__(self): def __init__(self) -> None:
self.pluginmanager = self self.pluginmanager = self
self.option = self self.option = self
self.stash = Stash() self.stash = Stash()
def getini(self, name): def getini(self, name: str) -> str:
return "pytest" return "pytest"
junitprefix = None junitprefix = None
@ -988,11 +1006,11 @@ class TestNonPython:
pytester.path.joinpath("myfile.xyz").write_text("hello", encoding="utf-8") pytester.path.joinpath("myfile.xyz").write_text("hello", encoding="utf-8")
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret 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) 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") 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") fnode.assert_attr(message="custom item runtest failed")
assert "custom item runtest failed" in fnode.toxml() assert "custom item runtest failed" in fnode.toxml()
@ -1133,7 +1151,7 @@ def test_escaped_parametrized_names_xml(
) )
result, dom = run_and_parse() result, dom = run_and_parse()
assert result.ret == 0 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]") node.assert_attr(name="test_func[\\x00]")
@ -1150,7 +1168,7 @@ def test_double_colon_split_function_issue469(
) )
result, dom = run_and_parse() result, dom = run_and_parse()
assert result.ret == 0 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(classname="test_double_colon_split_function_issue469")
node.assert_attr(name="test_func[double::colon]") node.assert_attr(name="test_func[double::colon]")
@ -1169,7 +1187,7 @@ def test_double_colon_split_method_issue469(
) )
result, dom = run_and_parse() result, dom = run_and_parse()
assert result.ret == 0 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(classname="test_double_colon_split_method_issue469.TestClass")
node.assert_attr(name="test_func[double::colon]") 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() result, dom = run_and_parse()
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase") tnode = node.get_first_by_tag("testcase")
psnode = tnode.find_first_by_tag("properties") psnode = tnode.get_first_by_tag("properties")
pnodes = psnode.find_by_tag("property") pnodes = psnode.find_by_tag("property")
pnodes[0].assert_attr(name="bar", value="1") pnodes[0].assert_attr(name="bar", value="1")
pnodes[1].assert_attr(name="foo", 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() 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") tnodes = node.find_by_tag("testcase")
for tnode in tnodes: 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}" assert psnode, f"testcase didn't had expected properties:\n{tnode}"
pnodes = psnode.find_by_tag("property") pnodes = psnode.find_by_tag("property")
pnodes[0].assert_attr(name="bar", value="1") pnodes[0].assert_attr(name="bar", value="1")
@ -1267,9 +1285,9 @@ def test_record_property_same_name(
""" """
) )
result, dom = run_and_parse() result, dom = run_and_parse()
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase") tnode = node.get_first_by_tag("testcase")
psnode = tnode.find_first_by_tag("properties") psnode = tnode.get_first_by_tag("properties")
pnodes = psnode.find_by_tag("property") pnodes = psnode.find_by_tag("property")
pnodes[0].assert_attr(name="foo", value="bar") pnodes[0].assert_attr(name="foo", value="bar")
pnodes[1].assert_attr(name="foo", value="baz") 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() result, dom = run_and_parse()
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase") tnode = node.get_first_by_tag("testcase")
tnode.assert_attr(bar="1") tnode.assert_attr(bar="1")
tnode.assert_attr(foo="<1") tnode.assert_attr(foo="<1")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
@ -1372,7 +1390,7 @@ def test_random_report_log_xdist(
""" """
) )
_, dom = run_and_parse("-n2") _, dom = run_and_parse("-n2")
suite_node = dom.find_first_by_tag("testsuite") suite_node = dom.get_first_by_tag("testsuite")
failed = [] failed = []
for case_node in suite_node.find_by_tag("testcase"): for case_node in suite_node.find_by_tag("testcase"):
if case_node.find_first_by_tag("failure"): 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*") result.stdout.no_fnmatch_line("*INTERNALERROR*")
items = sorted( 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. # dom is a DomNode not a mapping, it's not possible to ** it.
for x in dom.find_by_tag("testcase") 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) result, dom = run_and_parse(family=xunit_family)
assert result.ret == 0 assert result.ret == 0
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
properties_node = node.find_first_by_tag("properties") properties_node = node.get_first_by_tag("properties")
p1_node = properties_node.find_nth_by_tag("property", 0) p1_node, p2_node = properties_node.find_by_tag(
p2_node = properties_node.find_nth_by_tag("property", 1) "property",
)[:2]
p1_node.assert_attr(name="stats", value="all good") p1_node.assert_attr(name="stats", value="all good")
p2_node.assert_attr(name="stats", value="10") 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) result, dom = run_and_parse(family=xunit_family)
assert result.ret == 0 assert result.ret == 0
node = dom.find_first_by_tag("testsuite") node = dom.get_first_by_tag("testsuite")
node.assert_attr(name=expected) node.assert_attr(name=expected)
@ -1642,8 +1661,8 @@ def test_escaped_skipreason_issue3533(
""" """
) )
_, dom = run_and_parse() _, dom = run_and_parse()
node = dom.find_first_by_tag("testcase") node = dom.get_first_by_tag("testcase")
snode = node.find_first_by_tag("skipped") snode = node.get_first_by_tag("skipped")
assert "1 <> 2" in snode.text assert "1 <> 2" in snode.text
snode.assert_attr(message="1 <> 2") 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() _, dom = run_and_parse()
node = dom.find_first_by_tag("testcase") node = dom.get_first_by_tag("testcase")
snode = node.find_first_by_tag("skipped") snode = node.get_first_by_tag("skipped")
assert "#x1B[31;1mred#x1B[0m" in snode.text assert "#x1B[31;1mred#x1B[0m" in snode.text
snode.assert_attr(message="#x1B[31;1mred#x1B[0m") snode.assert_attr(message="#x1B[31;1mred#x1B[0m")
@ -1681,8 +1700,8 @@ def test_escaped_setup_teardown_error(
""" """
) )
_, dom = run_and_parse() _, dom = run_and_parse()
node = dom.find_first_by_tag("testcase") node = dom.get_first_by_tag("testcase")
snode = node.find_first_by_tag("error") snode = node.get_first_by_tag("error")
assert "#x1B[31mred#x1B[m" in snode["message"] assert "#x1B[31mred#x1B[m" in snode["message"]
assert "#x1B[31mred#x1B[m" in snode.text 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) result, dom = run_and_parse(family=xunit_family)
assert result.ret == 0 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-err")) == 0
assert len(node.find_by_tag("system-out")) == 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 "-o", f"junit_logging={junit_logging}", family=xunit_family
) )
assert result.ret == 1 assert result.ret == 1
node = dom.find_first_by_tag("testcase") node = dom.get_first_by_tag("testcase")
if junit_logging == "system-out": if junit_logging == "system-out":
assert len(node.find_by_tag("system-err")) == 0 assert len(node.find_by_tag("system-err")) == 0
assert len(node.find_by_tag("system-out")) == 1 assert len(node.find_by_tag("system-out")) == 1

View File

@ -602,13 +602,12 @@ class TestXFail:
self, expected, actual, matchline, pytester: Pytester self, expected, actual, matchline, pytester: Pytester
) -> None: ) -> None:
p = pytester.makepyfile( p = pytester.makepyfile(
""" f"""
import pytest import pytest
@pytest.mark.xfail(raises=%s) @pytest.mark.xfail(raises={expected})
def test_raises(): def test_raises():
raise %s() raise {actual}()
""" # noqa: UP031 (python syntax issues) """
% (expected, actual)
) )
result = pytester.runpytest(p) result = pytester.runpytest(p)
result.stdout.fnmatch_lines([matchline]) result.stdout.fnmatch_lines([matchline])
@ -900,13 +899,12 @@ class TestSkipif:
) )
def test_skipif_reporting(self, pytester: Pytester, params) -> None: def test_skipif_reporting(self, pytester: Pytester, params) -> None:
p = pytester.makepyfile( p = pytester.makepyfile(
test_foo=""" test_foo=f"""
import pytest import pytest
@pytest.mark.skipif(%(params)s) @pytest.mark.skipif({params})
def test_that(): def test_that():
assert 0 assert 0
""" # noqa: UP031 (python syntax issues) """
% dict(params=params)
) )
result = pytester.runpytest(p, "-s", "-rs") result = pytester.runpytest(p, "-s", "-rs")
result.stdout.fnmatch_lines(["*SKIP*1*test_foo.py*platform*", "*1 skipped*"]) result.stdout.fnmatch_lines(["*SKIP*1*test_foo.py*platform*", "*1 skipped*"])