[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
This commit is contained in:
pre-commit-ci[bot] 2024-04-13 00:00:18 +00:00
parent 72ad6dc953
commit f4cd1ba0d6
813 changed files with 66015 additions and 58839 deletions

View file

@ -7,11 +7,14 @@ Based on initial code from Ross Lawley.
Output conforms to
https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
"""
from datetime import datetime
from __future__ import annotations
import functools
import os
import platform
import re
import xml.etree.ElementTree as ET
from datetime import datetime
from typing import Callable
from typing import Dict
from typing import List
@ -19,8 +22,8 @@ from typing import Match
from typing import Optional
from typing import Tuple
from typing import Union
import xml.etree.ElementTree as ET
import pytest
from _pytest import nodes
from _pytest import timing
from _pytest._code.code import ExceptionRepr
@ -32,10 +35,9 @@ from _pytest.fixtures import FixtureRequest
from _pytest.reports import TestReport
from _pytest.stash import StashKey
from _pytest.terminal import TerminalReporter
import pytest
xml_key = StashKey["LogXML"]()
xml_key = StashKey['LogXML']()
def bin_xml_escape(arg: object) -> str:
@ -52,15 +54,15 @@ def bin_xml_escape(arg: object) -> str:
def repl(matchobj: Match[str]) -> str:
i = ord(matchobj.group())
if i <= 0xFF:
return "#x%02X" % i
return '#x%02X' % i
else:
return "#x%04X" % i
return '#x%04X' % i
# The spec range of valid chars is:
# Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
# For an unknown(?) reason, we disallow #x7F (DEL) as well.
illegal_xml_re = (
"[^\u0009\u000A\u000D\u0020-\u007E\u0080-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]"
'[^\u0009\u000A\u000D\u0020-\u007E\u0080-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]'
)
return re.sub(illegal_xml_re, repl, str(arg))
@ -76,27 +78,27 @@ def merge_family(left, right) -> None:
families = {}
families["_base"] = {"testcase": ["classname", "name"]}
families["_base_legacy"] = {"testcase": ["file", "line", "url"]}
families['_base'] = {'testcase': ['classname', 'name']}
families['_base_legacy'] = {'testcase': ['file', 'line', 'url']}
# xUnit 1.x inherits legacy attributes.
families["xunit1"] = families["_base"].copy()
merge_family(families["xunit1"], families["_base_legacy"])
families['xunit1'] = families['_base'].copy()
merge_family(families['xunit1'], families['_base_legacy'])
# xUnit 2.x uses strict base attributes.
families["xunit2"] = families["_base"]
families['xunit2'] = families['_base']
class _NodeReporter:
def __init__(self, nodeid: Union[str, TestReport], xml: "LogXML") -> None:
def __init__(self, nodeid: str | TestReport, xml: LogXML) -> None:
self.id = nodeid
self.xml = xml
self.add_stats = self.xml.add_stats
self.family = self.xml.family
self.duration = 0.0
self.properties: List[Tuple[str, str]] = []
self.nodes: List[ET.Element] = []
self.attrs: Dict[str, str] = {}
self.properties: list[tuple[str, str]] = []
self.nodes: list[ET.Element] = []
self.attrs: dict[str, str] = {}
def append(self, node: ET.Element) -> None:
self.xml.add_stats(node.tag)
@ -108,12 +110,12 @@ class _NodeReporter:
def add_attribute(self, name: str, value: object) -> None:
self.attrs[str(name)] = bin_xml_escape(value)
def make_properties_node(self) -> Optional[ET.Element]:
def make_properties_node(self) -> ET.Element | None:
"""Return a Junit node containing custom properties, if any."""
if self.properties:
properties = ET.Element("properties")
properties = ET.Element('properties')
for name, value in self.properties:
properties.append(ET.Element("property", name=name, value=value))
properties.append(ET.Element('property', name=name, value=value))
return properties
return None
@ -123,39 +125,39 @@ class _NodeReporter:
classnames = names[:-1]
if self.xml.prefix:
classnames.insert(0, self.xml.prefix)
attrs: Dict[str, str] = {
"classname": ".".join(classnames),
"name": bin_xml_escape(names[-1]),
"file": testreport.location[0],
attrs: dict[str, str] = {
'classname': '.'.join(classnames),
'name': bin_xml_escape(names[-1]),
'file': testreport.location[0],
}
if testreport.location[1] is not None:
attrs["line"] = str(testreport.location[1])
if hasattr(testreport, "url"):
attrs["url"] = testreport.url
attrs['line'] = str(testreport.location[1])
if hasattr(testreport, 'url'):
attrs['url'] = testreport.url
self.attrs = attrs
self.attrs.update(existing_attrs) # Restore any user-defined attributes.
# Preserve legacy testcase behavior.
if self.family == "xunit1":
if self.family == 'xunit1':
return
# Filter out attributes not permitted by this test family.
# Including custom attributes because they are not valid here.
temp_attrs = {}
for key in self.attrs.keys():
if key in families[self.family]["testcase"]:
if key in families[self.family]['testcase']:
temp_attrs[key] = self.attrs[key]
self.attrs = temp_attrs
def to_xml(self) -> ET.Element:
testcase = ET.Element("testcase", self.attrs, time="%.3f" % self.duration)
testcase = ET.Element('testcase', self.attrs, time='%.3f' % self.duration)
properties = self.make_properties_node()
if properties is not None:
testcase.append(properties)
testcase.extend(self.nodes)
return testcase
def _add_simple(self, tag: str, message: str, data: Optional[str] = None) -> None:
def _add_simple(self, tag: str, message: str, data: str | None = None) -> None:
node = ET.Element(tag, message=message)
node.text = bin_xml_escape(data)
self.append(node)
@ -167,24 +169,24 @@ class _NodeReporter:
content_out = report.capstdout
content_log = report.caplog
content_err = report.capstderr
if self.xml.logging == "no":
if self.xml.logging == 'no':
return
content_all = ""
if self.xml.logging in ["log", "all"]:
content_all = self._prepare_content(content_log, " Captured Log ")
if self.xml.logging in ["system-out", "out-err", "all"]:
content_all += self._prepare_content(content_out, " Captured Out ")
self._write_content(report, content_all, "system-out")
content_all = ""
if self.xml.logging in ["system-err", "out-err", "all"]:
content_all += self._prepare_content(content_err, " Captured Err ")
self._write_content(report, content_all, "system-err")
content_all = ""
content_all = ''
if self.xml.logging in ['log', 'all']:
content_all = self._prepare_content(content_log, ' Captured Log ')
if self.xml.logging in ['system-out', 'out-err', 'all']:
content_all += self._prepare_content(content_out, ' Captured Out ')
self._write_content(report, content_all, 'system-out')
content_all = ''
if self.xml.logging in ['system-err', 'out-err', 'all']:
content_all += self._prepare_content(content_err, ' Captured Err ')
self._write_content(report, content_all, 'system-err')
content_all = ''
if content_all:
self._write_content(report, content_all, "system-out")
self._write_content(report, content_all, 'system-out')
def _prepare_content(self, content: str, header: str) -> str:
return "\n".join([header.center(80, "-"), content, ""])
return '\n'.join([header.center(80, '-'), content, ''])
def _write_content(self, report: TestReport, content: str, jheader: str) -> None:
tag = ET.Element(jheader)
@ -192,65 +194,65 @@ class _NodeReporter:
self.append(tag)
def append_pass(self, report: TestReport) -> None:
self.add_stats("passed")
self.add_stats('passed')
def append_failure(self, report: TestReport) -> None:
# msg = str(report.longrepr.reprtraceback.extraline)
if hasattr(report, "wasxfail"):
self._add_simple("skipped", "xfail-marked test passes unexpectedly")
if hasattr(report, 'wasxfail'):
self._add_simple('skipped', 'xfail-marked test passes unexpectedly')
else:
assert report.longrepr is not None
reprcrash: Optional[ReprFileLocation] = getattr(
report.longrepr, "reprcrash", None
reprcrash: ReprFileLocation | None = getattr(
report.longrepr, 'reprcrash', None,
)
if reprcrash is not None:
message = reprcrash.message
else:
message = str(report.longrepr)
message = bin_xml_escape(message)
self._add_simple("failure", message, str(report.longrepr))
self._add_simple('failure', message, str(report.longrepr))
def append_collect_error(self, report: TestReport) -> None:
# msg = str(report.longrepr.reprtraceback.extraline)
assert report.longrepr is not None
self._add_simple("error", "collection failure", str(report.longrepr))
self._add_simple('error', 'collection failure', str(report.longrepr))
def append_collect_skipped(self, report: TestReport) -> None:
self._add_simple("skipped", "collection skipped", str(report.longrepr))
self._add_simple('skipped', 'collection skipped', str(report.longrepr))
def append_error(self, report: TestReport) -> None:
assert report.longrepr is not None
reprcrash: Optional[ReprFileLocation] = getattr(
report.longrepr, "reprcrash", None
reprcrash: ReprFileLocation | None = getattr(
report.longrepr, 'reprcrash', None,
)
if reprcrash is not None:
reason = reprcrash.message
else:
reason = str(report.longrepr)
if report.when == "teardown":
if report.when == 'teardown':
msg = f'failed on teardown with "{reason}"'
else:
msg = f'failed on setup with "{reason}"'
self._add_simple("error", bin_xml_escape(msg), str(report.longrepr))
self._add_simple('error', bin_xml_escape(msg), str(report.longrepr))
def append_skipped(self, report: TestReport) -> None:
if hasattr(report, "wasxfail"):
if hasattr(report, 'wasxfail'):
xfailreason = report.wasxfail
if xfailreason.startswith("reason: "):
if xfailreason.startswith('reason: '):
xfailreason = xfailreason[8:]
xfailreason = bin_xml_escape(xfailreason)
skipped = ET.Element("skipped", type="pytest.xfail", message=xfailreason)
skipped = ET.Element('skipped', type='pytest.xfail', message=xfailreason)
self.append(skipped)
else:
assert isinstance(report.longrepr, tuple)
filename, lineno, skipreason = report.longrepr
if skipreason.startswith("Skipped: "):
if skipreason.startswith('Skipped: '):
skipreason = skipreason[9:]
details = f"{filename}:{lineno}: {skipreason}"
details = f'{filename}:{lineno}: {skipreason}'
skipped = ET.Element(
"skipped", type="pytest.skip", message=bin_xml_escape(skipreason)
'skipped', type='pytest.skip', message=bin_xml_escape(skipreason),
)
skipped.text = bin_xml_escape(details)
self.append(skipped)
@ -265,17 +267,17 @@ class _NodeReporter:
def _warn_incompatibility_with_xunit2(
request: FixtureRequest, fixture_name: str
request: FixtureRequest, fixture_name: str,
) -> None:
"""Emit a PytestWarning about the given fixture being incompatible with newer xunit revisions."""
from _pytest.warning_types import PytestWarning
xml = request.config.stash.get(xml_key, None)
if xml is not None and xml.family not in ("xunit1", "legacy"):
if xml is not None and xml.family not in ('xunit1', 'legacy'):
request.node.warn(
PytestWarning(
f"{fixture_name} is incompatible with junit_family '{xml.family}' (use 'legacy' or 'xunit1')"
)
f"{fixture_name} is incompatible with junit_family '{xml.family}' (use 'legacy' or 'xunit1')",
),
)
@ -294,7 +296,7 @@ def record_property(request: FixtureRequest) -> Callable[[str, object], None]:
def test_function(record_property):
record_property("example_key", 1)
"""
_warn_incompatibility_with_xunit2(request, "record_property")
_warn_incompatibility_with_xunit2(request, 'record_property')
def append_property(name: str, value: object) -> None:
request.node.user_properties.append((name, value))
@ -312,10 +314,10 @@ def record_xml_attribute(request: FixtureRequest) -> Callable[[str, object], Non
from _pytest.warning_types import PytestExperimentalApiWarning
request.node.warn(
PytestExperimentalApiWarning("record_xml_attribute is an experimental feature")
PytestExperimentalApiWarning('record_xml_attribute is an experimental feature'),
)
_warn_incompatibility_with_xunit2(request, "record_xml_attribute")
_warn_incompatibility_with_xunit2(request, 'record_xml_attribute')
# Declare noop
def add_attr_noop(name: str, value: object) -> None:
@ -336,11 +338,11 @@ def _check_record_param_type(param: str, v: str) -> None:
type."""
__tracebackhide__ = True
if not isinstance(v, str):
msg = "{param} parameter needs to be a string, but {g} given" # type: ignore[unreachable]
msg = '{param} parameter needs to be a string, but {g} given' # type: ignore[unreachable]
raise TypeError(msg.format(param=param, g=type(v).__name__))
@pytest.fixture(scope="session")
@pytest.fixture(scope='session')
def record_testsuite_property(request: FixtureRequest) -> Callable[[str, object], None]:
"""Record a new ``<property>`` tag as child of the root ``<testsuite>``.
@ -371,7 +373,7 @@ def record_testsuite_property(request: FixtureRequest) -> Callable[[str, object]
def record_func(name: str, value: object) -> None:
"""No-op function in case --junit-xml was not passed in the command-line."""
__tracebackhide__ = True
_check_record_param_type("name", name)
_check_record_param_type('name', name)
xml = request.config.stash.get(xml_key, None)
if xml is not None:
@ -380,65 +382,65 @@ def record_testsuite_property(request: FixtureRequest) -> Callable[[str, object]
def pytest_addoption(parser: Parser) -> None:
group = parser.getgroup("terminal reporting")
group = parser.getgroup('terminal reporting')
group.addoption(
"--junitxml",
"--junit-xml",
action="store",
dest="xmlpath",
metavar="path",
type=functools.partial(filename_arg, optname="--junitxml"),
'--junitxml',
'--junit-xml',
action='store',
dest='xmlpath',
metavar='path',
type=functools.partial(filename_arg, optname='--junitxml'),
default=None,
help="Create junit-xml style report file at given path",
help='Create junit-xml style report file at given path',
)
group.addoption(
"--junitprefix",
"--junit-prefix",
action="store",
metavar="str",
'--junitprefix',
'--junit-prefix',
action='store',
metavar='str',
default=None,
help="Prepend prefix to classnames in junit-xml output",
help='Prepend prefix to classnames in junit-xml output',
)
parser.addini(
"junit_suite_name", "Test suite name for JUnit report", default="pytest"
'junit_suite_name', 'Test suite name for JUnit report', default='pytest',
)
parser.addini(
"junit_logging",
"Write captured log messages to JUnit report: "
"one of no|log|system-out|system-err|out-err|all",
default="no",
'junit_logging',
'Write captured log messages to JUnit report: '
'one of no|log|system-out|system-err|out-err|all',
default='no',
)
parser.addini(
"junit_log_passing_tests",
"Capture log information for passing tests to JUnit report: ",
type="bool",
'junit_log_passing_tests',
'Capture log information for passing tests to JUnit report: ',
type='bool',
default=True,
)
parser.addini(
"junit_duration_report",
"Duration time to report: one of total|call",
default="total",
'junit_duration_report',
'Duration time to report: one of total|call',
default='total',
) # choices=['total', 'call'])
parser.addini(
"junit_family",
"Emit XML for schema: one of legacy|xunit1|xunit2",
default="xunit2",
'junit_family',
'Emit XML for schema: one of legacy|xunit1|xunit2',
default='xunit2',
)
def pytest_configure(config: Config) -> None:
xmlpath = config.option.xmlpath
# Prevent opening xmllog on worker nodes (xdist).
if xmlpath and not hasattr(config, "workerinput"):
junit_family = config.getini("junit_family")
if xmlpath and not hasattr(config, 'workerinput'):
junit_family = config.getini('junit_family')
config.stash[xml_key] = LogXML(
xmlpath,
config.option.junitprefix,
config.getini("junit_suite_name"),
config.getini("junit_logging"),
config.getini("junit_duration_report"),
config.getini('junit_suite_name'),
config.getini('junit_logging'),
config.getini('junit_duration_report'),
junit_family,
config.getini("junit_log_passing_tests"),
config.getini('junit_log_passing_tests'),
)
config.pluginmanager.register(config.stash[xml_key])
@ -450,12 +452,12 @@ def pytest_unconfigure(config: Config) -> None:
config.pluginmanager.unregister(xml)
def mangle_test_address(address: str) -> List[str]:
path, possible_open_bracket, params = address.partition("[")
names = path.split("::")
def mangle_test_address(address: str) -> list[str]:
path, possible_open_bracket, params = address.partition('[')
names = path.split('::')
# Convert file path to dotted path.
names[0] = names[0].replace(nodes.SEP, ".")
names[0] = re.sub(r"\.py$", "", names[0])
names[0] = names[0].replace(nodes.SEP, '.')
names[0] = re.sub(r'\.py$', '', names[0])
# Put any params back.
names[-1] += possible_open_bracket + params
return names
@ -465,11 +467,11 @@ class LogXML:
def __init__(
self,
logfile,
prefix: Optional[str],
suite_name: str = "pytest",
logging: str = "no",
report_duration: str = "total",
family="xunit1",
prefix: str | None,
suite_name: str = 'pytest',
logging: str = 'no',
report_duration: str = 'total',
family='xunit1',
log_passing_tests: bool = True,
) -> None:
logfile = os.path.expanduser(os.path.expandvars(logfile))
@ -480,27 +482,27 @@ class LogXML:
self.log_passing_tests = log_passing_tests
self.report_duration = report_duration
self.family = family
self.stats: Dict[str, int] = dict.fromkeys(
["error", "passed", "failure", "skipped"], 0
self.stats: dict[str, int] = dict.fromkeys(
['error', 'passed', 'failure', 'skipped'], 0,
)
self.node_reporters: Dict[
Tuple[Union[str, TestReport], object], _NodeReporter
self.node_reporters: dict[
tuple[str | TestReport, object], _NodeReporter,
] = {}
self.node_reporters_ordered: List[_NodeReporter] = []
self.global_properties: List[Tuple[str, str]] = []
self.node_reporters_ordered: list[_NodeReporter] = []
self.global_properties: list[tuple[str, str]] = []
# List of reports that failed on call but teardown is pending.
self.open_reports: List[TestReport] = []
self.open_reports: list[TestReport] = []
self.cnt_double_fail_tests = 0
# Replaces convenience family with real family.
if self.family == "legacy":
self.family = "xunit1"
if self.family == 'legacy':
self.family = 'xunit1'
def finalize(self, report: TestReport) -> None:
nodeid = getattr(report, "nodeid", report)
nodeid = getattr(report, 'nodeid', report)
# Local hack to handle xdist report order.
workernode = getattr(report, "node", None)
workernode = getattr(report, 'node', None)
reporter = self.node_reporters.pop((nodeid, workernode))
for propname, propvalue in report.user_properties:
@ -509,10 +511,10 @@ class LogXML:
if reporter is not None:
reporter.finalize()
def node_reporter(self, report: Union[TestReport, str]) -> _NodeReporter:
nodeid: Union[str, TestReport] = getattr(report, "nodeid", report)
def node_reporter(self, report: TestReport | str) -> _NodeReporter:
nodeid: str | TestReport = getattr(report, 'nodeid', report)
# Local hack to handle xdist report order.
workernode = getattr(report, "node", None)
workernode = getattr(report, 'node', None)
key = nodeid, workernode
@ -561,22 +563,22 @@ class LogXML:
"""
close_report = None
if report.passed:
if report.when == "call": # ignore setup/teardown
if report.when == 'call': # ignore setup/teardown
reporter = self._opentestcase(report)
reporter.append_pass(report)
elif report.failed:
if report.when == "teardown":
if report.when == 'teardown':
# The following vars are needed when xdist plugin is used.
report_wid = getattr(report, "worker_id", None)
report_ii = getattr(report, "item_index", None)
report_wid = getattr(report, 'worker_id', None)
report_ii = getattr(report, 'item_index', None)
close_report = next(
(
rep
for rep in self.open_reports
if (
rep.nodeid == report.nodeid
and getattr(rep, "item_index", None) == report_ii
and getattr(rep, "worker_id", None) == report_wid
rep.nodeid == report.nodeid and
getattr(rep, 'item_index', None) == report_ii and
getattr(rep, 'worker_id', None) == report_wid
)
),
None,
@ -588,7 +590,7 @@ class LogXML:
self.finalize(close_report)
self.cnt_double_fail_tests += 1
reporter = self._opentestcase(report)
if report.when == "call":
if report.when == 'call':
reporter.append_failure(report)
self.open_reports.append(report)
if not self.log_passing_tests:
@ -599,21 +601,21 @@ class LogXML:
reporter = self._opentestcase(report)
reporter.append_skipped(report)
self.update_testcase_duration(report)
if report.when == "teardown":
if report.when == 'teardown':
reporter = self._opentestcase(report)
reporter.write_captured_output(report)
self.finalize(report)
report_wid = getattr(report, "worker_id", None)
report_ii = getattr(report, "item_index", None)
report_wid = getattr(report, 'worker_id', None)
report_ii = getattr(report, 'item_index', None)
close_report = next(
(
rep
for rep in self.open_reports
if (
rep.nodeid == report.nodeid
and getattr(rep, "item_index", None) == report_ii
and getattr(rep, "worker_id", None) == report_wid
rep.nodeid == report.nodeid and
getattr(rep, 'item_index', None) == report_ii and
getattr(rep, 'worker_id', None) == report_wid
)
),
None,
@ -624,9 +626,9 @@ class LogXML:
def update_testcase_duration(self, report: TestReport) -> None:
"""Accumulate total duration for nodeid from given report and update
the Junit.testcase with the new total if already created."""
if self.report_duration in {"total", report.when}:
if self.report_duration in {'total', report.when}:
reporter = self.node_reporter(report)
reporter.duration += getattr(report, "duration", 0.0)
reporter.duration += getattr(report, 'duration', 0.0)
def pytest_collectreport(self, report: TestReport) -> None:
if not report.passed:
@ -637,9 +639,9 @@ class LogXML:
reporter.append_collect_skipped(report)
def pytest_internalerror(self, excrepr: ExceptionRepr) -> None:
reporter = self.node_reporter("internal")
reporter.attrs.update(classname="pytest", name="internal")
reporter._add_simple("error", "internal error", str(excrepr))
reporter = self.node_reporter('internal')
reporter.attrs.update(classname='pytest', name='internal')
reporter._add_simple('error', 'internal error', str(excrepr))
def pytest_sessionstart(self) -> None:
self.suite_start_time = timing.time()
@ -649,27 +651,27 @@ class LogXML:
# exist_ok avoids filesystem race conditions between checking path existence and requesting creation
os.makedirs(dirname, exist_ok=True)
with open(self.logfile, "w", encoding="utf-8") as logfile:
with open(self.logfile, 'w', encoding='utf-8') as logfile:
suite_stop_time = timing.time()
suite_time_delta = suite_stop_time - self.suite_start_time
numtests = (
self.stats["passed"]
+ self.stats["failure"]
+ self.stats["skipped"]
+ self.stats["error"]
- self.cnt_double_fail_tests
self.stats['passed'] +
self.stats['failure'] +
self.stats['skipped'] +
self.stats['error'] -
self.cnt_double_fail_tests
)
logfile.write('<?xml version="1.0" encoding="utf-8"?>')
suite_node = ET.Element(
"testsuite",
'testsuite',
name=self.suite_name,
errors=str(self.stats["error"]),
failures=str(self.stats["failure"]),
skipped=str(self.stats["skipped"]),
errors=str(self.stats['error']),
failures=str(self.stats['failure']),
skipped=str(self.stats['skipped']),
tests=str(numtests),
time="%.3f" % suite_time_delta,
time='%.3f' % suite_time_delta,
timestamp=datetime.fromtimestamp(self.suite_start_time).isoformat(),
hostname=platform.node(),
)
@ -678,23 +680,23 @@ class LogXML:
suite_node.append(global_properties)
for node_reporter in self.node_reporters_ordered:
suite_node.append(node_reporter.to_xml())
testsuites = ET.Element("testsuites")
testsuites = ET.Element('testsuites')
testsuites.append(suite_node)
logfile.write(ET.tostring(testsuites, encoding="unicode"))
logfile.write(ET.tostring(testsuites, encoding='unicode'))
def pytest_terminal_summary(self, terminalreporter: TerminalReporter) -> None:
terminalreporter.write_sep("-", f"generated xml file: {self.logfile}")
terminalreporter.write_sep('-', f'generated xml file: {self.logfile}')
def add_global_property(self, name: str, value: object) -> None:
__tracebackhide__ = True
_check_record_param_type("name", name)
_check_record_param_type('name', name)
self.global_properties.append((name, bin_xml_escape(value)))
def _get_global_properties_node(self) -> Optional[ET.Element]:
def _get_global_properties_node(self) -> ET.Element | None:
"""Return a Junit node containing custom properties, if any."""
if self.global_properties:
properties = ET.Element("properties")
properties = ET.Element('properties')
for name, value in self.global_properties:
properties.append(ET.Element("property", name=name, value=value))
properties.append(ET.Element('property', name=name, value=value))
return properties
return None