Repository navigation
Expand file tree
/
Copy pathconftest.py
More file actions
115 lines (98 loc) · 5.18 KB
/
Copy pathconftest.py
File metadata and controls
115 lines (98 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""Root-level pytest plugin: the #510 test-duration reporter.
`pytest` already prints `--durations` (wired via `addopts` in
`pyproject.toml`); nothing read it before this, so a single test dominating
a suite had no detector but a human scrolling a CI log by chance (#510's own
worked example: a test at 43.92s against ~6.5s for the next slowest, roughly
a fifth of the whole run, found only because a maintainer happened to read
that far).
`pytest_terminal_summary` fires at the end of every `pytest` invocation --
local or any leg of the CI matrix -- with no extra flag required, and reads
the same `TestReport` objects pytest's own summary is built from off
`terminalreporter.stats`, rather than re-parsing pytest's own formatted
`--durations` text. The actual duration math lives in
`scripts/report_test_durations.py`, kept separate so it can be unit-tested
without driving a nested pytest session.
This intentionally never raises and never touches `exitstatus`: a reporter
bug must not turn a green run red, and #510 is explicit that this is a
report a human reads, never a gate. That includes the import of
`scripts/report_test_durations.py` itself -- it happens inside
`pytest_terminal_summary`'s own `try`, not at module scope, so a defect in
that module degrades to the `could-not-measure` state instead of aborting
collection for the whole session (a module-scope import failure is a
`conftest.py` collection error, which pytest treats as fatal: zero tests
run, non-zero exit, no report at all -- exactly the total, silent failure
this feature exists to prevent, just promoted from "one test" to "every
test"). See `tests/test_conftest_import_failure_510.py` for the case this
guards against.
"""
from __future__ import annotations
import os
import sys
import time
sys.path.insert(0, os.path.dirname(__file__))
def pytest_sessionstart(session):
# Recorded here rather than trusted off `terminalreporter` -- pytest does
# not guarantee that object carries a session-start timestamp under any
# stable name across versions, and a `getattr(..., None)` against a name
# that silently stopped existing would turn "measured" into
# "could-not-measure" on some future pytest with nothing pointing at why.
session.config._report_test_durations_510_start = time.time()
def pytest_terminal_summary(terminalreporter, exitstatus, config):
try:
from scripts.report_test_durations import (
DEFAULT_BASELINE_PATH,
analyze,
collect_from_reports,
format_report,
load_baseline,
)
reports = [
report
for report_list in terminalreporter.stats.values()
for report in report_list
if hasattr(report, "duration")
]
durations = collect_from_reports(reports)
start = getattr(config, "_report_test_durations_510_start", None)
total_seconds = (time.time() - start) if start is not None else None
baseline, baseline_error = load_baseline(DEFAULT_BASELINE_PATH)
result = analyze(durations, total_seconds, baseline)
result.baseline_error = baseline_error
terminalreporter.write_line(format_report(result))
except Exception as exc: # noqa: BLE001 -- pragma: no cover - defensive; see module docstring
terminalreporter.write_line(
f"-- test duration report (#510) --\nstate: could-not-measure (reporter raised {exc!r})"
)
# #507: a separate try so a defect in either reporter cannot silence the
# other -- the same isolation the #510 block above already relies on.
try:
from scripts import report_windows_skip_floor as skipfloor
# Counted by DISTINCT nodeid, not by summing each category's report
# count: a test with a failing/erroring teardown gets a second report
# (outcome "error", `when="teardown"`) alongside its own call-phase
# "passed"/"failed"/"skipped" report -- summing list lengths would
# count that one test twice in `total` while `skipped` still counted
# it once, silently diluting the reported ratio below the true one.
report_categories = ("passed", "failed", "skipped", "error", "xfailed", "xpassed")
skipped_nodeids = {
report.nodeid
for report in terminalreporter.stats.get("skipped", [])
if getattr(report, "nodeid", None) is not None
}
all_nodeids = {
report.nodeid
for key in report_categories
for report in terminalreporter.stats.get(key, [])
if getattr(report, "nodeid", None) is not None
}
skipped = len(skipped_nodeids)
total = len(all_nodeids)
skip_result = skipfloor.analyze(skipped, total, is_windows=(sys.platform == "win32"))
terminalreporter.write_line(skipfloor.format_report(skip_result))
warning = skipfloor.github_actions_warning(skip_result)
if warning:
terminalreporter.write_line(warning)
except Exception as exc: # noqa: BLE001 -- pragma: no cover - defensive; see #510's identical reasoning above
terminalreporter.write_line(
f"-- windows skip floor report (#507) --\nstate: could-not-measure (reporter raised {exc!r})"
)