Skip to content

Report test results broken down by project structure - #703

Open
danielfrankcom wants to merge 32 commits into
documentdb:mainfrom
danielfrankcom:pr/tag-reporting
Open

Report test results broken down by project structure#703
danielfrankcom wants to merge 32 commits into
documentdb:mainfrom
danielfrankcom:pr/tag-reporting

Conversation

@danielfrankcom

Copy link
Copy Markdown
Collaborator

This change reworks the CI test report to provide as much information as possible at a glance.

The main aims were to:

  • Quickly provide information about failures and what caused them
  • Provide a way to explore test results for arbitrary subsets of tests (based on repo structure, not pytest tags)

The new report format is easiest to look at in the form of real test scenarios I ran on CI during testing. These are contrived failures for the purpose of generating the specific report types.

Scenario Run Shows
Baseline, all passing 30304940350 all tests passing with a clear success marker, and optional breakdown of per-test folder structure
Test failures 30308231576 clear failure marker, with "needs attention" breakdown per failure
Large number of test failures 30310219663 too many errors are cut off, only a subset shown
Strict xpass 30314372700 if an xfail test starts unexpectedly passing, then it appears in the "needs attention" section

Analyzer correctness fixes along the way

  • Skip and xfail reasons are now mandatory and enforced by the validator, to provide better info in the report
  • The previous tag-based grouping is no longer necessary and were removed
  • xpass is now configured as strict, meaning xfail tests will now fail if they unexpectedly start to pass

Fixes #121

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
The feature tree (from test paths) replaced marker/by_tag grouping. Drop the pytest.ini marker parsing, extract_markers, the by_tag build, and the per-test tags field; ResultAnalyzer no longer needs a pytest.ini path.

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
The analyzer recomputed its own summary dict in parallel with reconciliation, which supersedes it (adds collected/deselected, uses the correct pass-rate denominator). Drop the recomputed summary; JSON output and the CLI exit code now use reconciliation. The exit code also fails on errors, not just failures.

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
A runtime pytest.skip() inside a test body records its reason in the call phase, but only setup was read, so those skips showed a blank reason in the report. Scan for the phase whose outcome is skipped, matching how failures already find their phase.

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
The sidecar is written at collection time, but the report's directory may not exist until pytest-json-report creates it at session end. In CI the write therefore failed and was swallowed, so the feature tree showed every area as 0 unsupported. Create the directory, and warn instead of failing silently.

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Known gaps, skipped, and the feature breakdown were bare collapsibles, so they
read as more entries in the needs-attention list rather than sections of their
own. Give each a heading carrying its count, leaving the detail behind a
Show all toggle.

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
@danielfrankcom
danielfrankcom requested a review from a team as a code owner July 27, 2026 23:59
@documentdb-triage-tool

Copy link
Copy Markdown

🤖 Auto-triaged by documentdb-triage-tool.

Applied: documentation, enhancement
Project fields suggested: Component result-analyzer · Priority P2 · Effort XL · Status Needs Review
Confidence: 0.82 (mixed)

Reasoning

component from path globs (result-analyzer, docs, test-framework, ci); effort from diff stats (1733+442 LOC, 18 files); LLM: Reworks CI test reporting and result-analyzer logic across multiple concerns (report format, xpass strictness, reason enforcement), touching multiple files in the result_analyzer component and CI integration.

If a label is wrong, remove it manually and ping @patty-chow so the rules can be tuned. The bot will not re-label items that already have component labels.

@documentdb-triage-tool documentdb-triage-tool Bot added documentation Improvements or additions to documentation enhancement New feature or request labels Jul 28, 2026
pytest records a skip as the repr of a ('<path>', <lineno>, 'Skipped: <reason>')
tuple, and repr switches the reason to double quotes whenever it contains an
apostrophe ("doesn't apply here"). The extraction regex only accepted a
single-quoted reason, so any reason with an apostrophe was silently dropped from
the report — while the new validator simultaneously forces authors to write one.
Accept either closing quote.

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Test IDs, reasons, and tracebacks embed arbitrary test inputs (parametrized IDs
and repr'd documents), but the renderer interpolated them raw into three markup
contexts where they can be markup themselves:

- a | in a table cell splits the row, so escape it
- a < in a <summary> can be eaten as an HTML tag, so html-escape the name
- a ``` inside a traceback closes the fixed 3-backtick fence early and
  corrupts everything after it, so size the fence past the longest backtick
  run in the content

No current test data hits these, so this is insurance against silent report
corruption when future data does.

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
_merge_json_reports summed only passed/failed/error/skipped/total, so a Phase 2
(no_parallel) xfail or xpass vanished from the merged summary while its entry
remained in the merged tests array. The report then contradicted itself: the
known-gaps section listed the test but the breakdown count excluded it, and a
Phase 2 strict-xpass would not fail the verdict. Sum every outcome bucket.

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
The sidecar was only written when this run deselected something, so a sidecar
left by a previous run survived a run with no deselection and the analyzer
credited phantom unsupported tests into the report. Write it unconditionally
(as {} when nothing was deselected) so it always reflects the current run.

Phase 2's subprocess also writes a sidecar next to its temp report; that one is
redundant (Phase 1 collects everything, so its sidecar covers all requires-based
deselections) and was never cleaned up. Remove it with the temp report.

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Under xdist every worker runs collection and writes the same sidecar path with
a plain truncate-and-write, which can tear the file. The analyzer degrades
gracefully on invalid JSON by returning no reasons, so the failure mode is a
silently reason-less report - the exact quiet incompleteness the write warning
was added to prevent. Write to a per-process temp file and os.replace it into
place, so readers only ever see a complete sidecar.

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
The exit code checked only failed/error counts, while determine_verdict also
fails a run for raw xpasses (strict xfail not applied, results untrustworthy)
and for runs where nothing executed. A consumer scripting on the exit code got
0 for a run the report itself declared FAIL. Reuse the verdict so the two can't
disagree.

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
feature_path located the taxonomy root with rfind("tests/"), a bare substring
match that also hits directories merely ending in "tests" - e.g.
"documentdb_tests/framework/test_x.py" resolved to ['framework', 'test_x.py']
instead of no path. Harmless today because only compatibility/tests nodeids
reach it, but the trap is invisible at the call site. Anchor the match to a
path-component boundary.

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
The validator matched any attribute call named skip/fail/xfail, so a
PyMongo cursor's .skip(n) was reported as a pytest.skip() missing its
message. A violation aborts collection for the whole suite, so one
idiomatic pagination test would have blocked every run.

Signed-off-by: Daniel Frankcom <frankcom@amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Derive test tags from file paths for metrics-by-feature reporting

1 participant