The Gaudi Framework  master (37c0b60a)
ctest_measurements_reporter.py
Go to the documentation of this file.
1 
11 import os
12 import re
13 import xml.sax.saxutils as XSS
14 from collections import defaultdict
15 
16 # This plugin integrates with pytest to capture and report test results
17 # in a format compatible with CTest using DartMeasurement tags
18 # Key functions and hooks include:
19 # - sanitize_for_xml: Sanitize a string by escaping non-XML characters.
20 # - pytest_report_header: Add strings to the pytest header.
21 # - pytest_runtest_logreport: Collect test results and durations.
22 # - pytest_sessionfinish: Output the collected test information in a format
23 # suitable for CTest.
24 
25 results = {}
26 
27 
28 def sanitize_for_xml(data):
29  bad_chars = re.compile("[\x00-\x08\x0b\x0c\x0e-\x1f\ud800-\udfff\ufffe\uffff]")
30 
31  def quote(match):
32  "helper function"
33  return "".join("[NON-XML-CHAR-0x%2X]" % ord(c) for c in match.group())
34 
35  return bad_chars.sub(quote, data)
36 
37 
38 def pytest_report_header(config, start_path, startdir):
39  # make sure CTest does not drop output lines on successful tests
40  return "CTEST_FULL_OUTPUT"
41 
42 
44  # collect user properties
45  head_line = report.head_line
46  results.update(
47  {
48  f"{head_line}.{k}": v
49  for k, v in report.user_properties
50  if f"{head_line}.{k}" not in results
51  }
52  )
53 
54  # collect test outcome
55  if not report.passed:
56  results[f"{report.head_line}.outcome"] = report.outcome
57  else:
58  results.setdefault(f"{report.head_line}.outcome", "passed")
59 
60  # collect test duration
61  if report.when == "call":
62  results[f"{report.head_line}.duration"] = round(report.duration, 2)
63 
64 
65 def pytest_sessionfinish(session, exitstatus):
66  if not hasattr(session, "items"):
67  # no test run, nothing to report
68  return
69  if os.environ.get("DISABLE_CTEST_MEASUREMENTS") == "1":
70  # user requested to disable CTest measurements printouts
71  return
72 
73  outcomes = defaultdict(list)
74  for key in results:
75  if key.endswith(".outcome"):
76  outcomes[results[key]].append(key[:-8])
77  results.update(
78  (f"outcome.{outcome}", sorted(tests)) for outcome, tests in outcomes.items()
79  )
80 
81  ignore_keys = {"test_fixture_setup.completed_process"}
82  template = (
83  '<DartMeasurement type="text/string" name="{name}">{value}</DartMeasurement>'
84  )
85 
86  to_print = [
87  (key, value)
88  for key, value in results.items()
89  if not any(key.endswith(ignore_key) for ignore_key in ignore_keys) and value
90  ]
91  to_print.sort()
92  for key, value in to_print:
93  sanitized_value = XSS.escape(sanitize_for_xml(str(value)))
94  # workaround for a limitation of CTestXML2HTML
95  key = key.replace("/", "_")
96  print(template.format(name=key, value=sanitized_value), end="")
ctest_measurements_reporter.pytest_sessionfinish
def pytest_sessionfinish(session, exitstatus)
Definition: ctest_measurements_reporter.py:65
ctest_measurements_reporter.sanitize_for_xml
def sanitize_for_xml(data)
Definition: ctest_measurements_reporter.py:28
ctest_measurements_reporter.pytest_runtest_logreport
def pytest_runtest_logreport(report)
Definition: ctest_measurements_reporter.py:43
ctest_measurements_reporter.pytest_report_header
def pytest_report_header(config, start_path, startdir)
Definition: ctest_measurements_reporter.py:38