Repository navigation
fix(xtest): keep raw benchmark samples when the analysis raises - #686
Draft
dmihalcik-virtru wants to merge 1 commit into
Draft
dmihalcik-virtru wants to merge 1 commit into
dmihalcik-virtru wants to merge 1 commit into
Conversation
pytest_sessionfinish promised to write the benchmark artifacts first and unconditionally, but it computed the gate before writing anything, and write_json needs the gate too. Any exception from analyze(), the statistics or to_dict therefore ended the hook with nothing on disk, and a 30-minute run's per-round samples were lost exactly when someone needed them to debug the failure. The error itself was loud, so nothing went green; only the data was gone. The hook now catches that failure, writes <name>.raw.json, and re-raises with a note naming the file. The raw document holds the metadata, the config, the skipped cells, every CellResult field by field (samples included), and the error's type and message. It is serialized with dataclasses.asdict rather than through to_dict, which needs a gate and may be what raised, so it also follows CellResult when that changes shape. It goes under its own name rather than into <name>.json with a marker: nothing that opens <name>.json can take a run without statistics for a complete one, and the upload step's *.json glob still collects it. It has no schema key, so a reader of the normal artifact handed it anyway fails at its first check. A run that analyses cleanly is unchanged: the same <name>.json, byte for byte, the same step summary, and no extra file. The normal artifact stays at schema 1. The session still fails on the original exception. Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Found while reviewing #669. This is a pre-existing bug on
main, so this PR is based onmainand is independent of the stage 2/3 stacks.The bug
pytest_sessionfinishpromises "Artifacts are written first and unconditionally". In fact it computes the gate (recorder.gate()) before writing anything, andwrite_jsonneeds the gate too. Any exception fromanalyze(), the statistics orto_dicttherefore ended the hook with nothing on disk, and a 30-minute run's per-round samples were lost exactly when someone needed them to debug the failure. The error itself was loud, so nothing went green; only the data was gone.#669 makes the report index contrast keys directly, so a wrongly built key raises instead of being dropped. That makes this window wider.
What it does
The hook catches the failure, writes the raw samples, then re-raises. If computing the gate or the JSON raises, it writes
<name>.raw.json, adds a note naming that file, and re-raises the original exception. The session still fails on the same error.The raw document holds what re-analysis needs:
analysis_error(the exception's type and message);metadata;config;skipped;CellResultfield by field, samples included.It is serialized with
dataclasses.asdict, not throughto_dict, which needs a gate and may be what raised. It also followsCellResultwhen chore(xtest): measure benchmark cells over a tuple of arms #669 changes its shape.It goes under its own name rather than a marker, so nothing that opens
<name>.jsoncan take a run without statistics for a complete one. It has noschemakey, so a reader of the normal artifact handed it by mistake fails at its first check. The upload step's*.jsonglob already collects it.A run that analyses cleanly is unchanged: the same
<name>.jsonbyte for byte, the same step summary, and no extra file. The normal artifact stays atschema: 1.Docs: the hook docstring and the perf README (the artifact table, and "Cells record; the session gates") describe the new path.
Limits
Exceptionis caught, so otherBaseExceptions, Ctrl-C included, are not covered.--bench-outpersists between runs, a stale.raw.jsoncan sit next to a fresh.json, or the reverse. CI runners start clean.Validation
uv run pytest test_bench_*.py: 231 passed. ruff, ruff format and pyright are clean.TestSessionFinishruns the realconftest.pytest_sessionfinishwith stubbed metadata, so no platform is needed.A failed analysis still writes the raw samples. This is tested twice, with
analyzeraising and withto_dictraising. It checks:benchmarks.raw.jsonexists, with noschemakey;Both cases fail on
main.A clean analysis writes exactly what it always did. This pins
<name>.jsonand the step summary to the existing renderers.Interaction with #669
#669 was rebased onto this change in a scratch repository:
report.py,conftest.pyand the README.test_bench_report.py: conflicts only on its import lines and at end-of-file; keep both sides. Then replace this PR's_measuredhelper with chore(xtest): measure benchmark cells over a tuple of arms #669's_cell, and rebuild cells withCellResult(**c | {"arm_ids": tuple(c["arm_ids"])}).