Skip to content

Fix --export-json reporting a failed, skipped, or zero-match run as clean - #4732

Closed
ivmat wants to merge 5 commits into
model-checking:mainfrom
ivmat:export-json-soundness-clean
Closed

Fix --export-json reporting a failed, skipped, or zero-match run as clean#4732
ivmat wants to merge 5 commits into
model-checking:mainfrom
ivmat:export-json-soundness-clean

Conversation

@ivmat

@ivmat ivmat commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #4472. While using --export-json as a CI pass/fail oracle I hit several cases where the exported file did not faithfully represent the run — a run that failed, was skipped, matched nothing, or wasn't fully measured could be read as a clean, complete pass by a consumer that trusts the file (which is the point of the file). This addresses them:

  • Stale exports. verify_project now writes a run_state:"incomplete" marker to the target before verification, promoted to "complete" only when every selected harness produced a result — so a run that dies before finishing can't leave a previous run's clean file to be read as this run's result.
  • Zero / unmatched harnesses. A filter that matches nothing used to write a completed / 0-failed document (the "no harnesses matched" error is only raised after the export). It's now marked run_state:"no_harnesses_selected", with a harness_selection block recording the requested filters, matched count, and any unmatched filters.
  • --fail-fast. Harnesses skipped after the first failure kept their harness_metadata entry but had no error_details/property_details entry, so a consumer correlating the arrays could read absence as success. Every selected harness now gets an explicit entry, and run_state is "partial" when some selected harnesses didn't run.
  • Attribution. Per-harness details join on the unique mangled_name rather than pretty_name, which two crates in one workspace can share.
  • cbmc_stats. Parsing no longer clobbers a valid value with null on a later malformed line, tolerates harmless CBMC wording changes, rejects a duration reported in the wrong unit rather than mis-scaling it, and widens counts to u64.
  • Validator. scripts/validate_json_export.py previously checked structure only, so failed:-1 or successful:"yes" passed. It now checks leaf value types, count reconciliation, and the run_state enum and its invariants, with a validator-negative test suite; run_state/harness_selection are added to the schema template so their presence is required.

Rationale and the per-case scenarios are in #4731. One case from that report — a partial, non-timeout CBMC exit — is left as follow-up pending confirmation, and is not addressed here.

Testing: cargo test -p kani-driver (incl. new call_cbmc/validator unit tests), clippy -D warnings, and fmt are clean; the validator-negative suite rejects the malformed fixtures. The compiletest tests/json-handler/* integration tests (which require CBMC) were not run in my environment, so the new validator leaf-type checks have been exercised against synthetic and reference fixtures but not yet a live CBMC-generated export.

ivmat added 3 commits August 12, 2026 23:39
…lete

Close several ways a run that did not finish cleanly could still serialize as a clean, completed export:

- A run reusing an --export-json path that died before the final write (compile error, OOM, Ctrl-C, or a
  harness-level error propagating out) left the *previous* run's complete-looking file at the target.
  verify_project now writes a run_state:"incomplete" marker to the target before verification starts, so a
  stale file can never be read as this run's result; the marker is promoted to "complete" only when the
  run finished.
- A --harness filter that matched nothing wrote a completed / 0-failed document (the "no harnesses
  matched" error is only raised after the export). A zero-match run is now marked
  run_state:"no_harnesses_selected", and a harness_selection block records the requested filters, the
  matched count, and any unmatched filters.
- Under --fail-fast, harnesses skipped after the first failure kept their harness_metadata entry but had
  no error_details/property_details entry, so a consumer correlating the arrays could read absence as
  success. Every selected harness now gets an explicit entry, and the terminal run_state is "complete"
  only when every selected harness produced a result, "partial" otherwise.
- Per-harness details were joined to results on pretty_name, which two harnesses in different crates of a
  workspace can share, misattributing a result. The join now uses the unique mangled_name.

Follow-up to model-checking#4472.
…erflow-safe

The scraped CBMC statistics could be silently wrong:

- A later status message that matched a recognized label but failed to parse overwrote an
  already-recorded valid value with null (indistinguishable from "not measured"). Parsing now only
  assigns on success and never clobbers a valid value.
- Exact-suffix matching turned any harmless CBMC wording change into a silent null. Counts now parse the
  leading numeric token, tolerating trailing text.
- record_seconds extracted the leading number regardless of unit, so a duration reported as "5ms" would
  be recorded as 5 seconds. It now accepts a value only when the unit is exactly "s"; any other unit is a
  parse failure, never a mis-scaled number.
- The integer count fields were u32; a large enough run overflowed to null. Widened to u64.

Follow-up to model-checking#4472.
The shipped validator checked structure but not values or semantics, so a structurally well-formed
document that misreported -- failed:-1, successful:"yes", executed disagreeing with results, or
run_state:"complete" with empty results -- validated as OK. It now checks leaf value types, non-negative
and reconciled summary counts, and the run_state enum and its invariants, with a validator-negative test
suite covering each. run_state and harness_selection are added to the schema template so their presence
is required.

Follow-up to model-checking#4472.
@ivmat
ivmat requested a review from a team as a code owner August 12, 2026 22:12
@github-actions github-actions Bot added the Z-EndToEndBenchCI Tag a PR to run benchmark CI label Aug 12, 2026
ivmat added 2 commits August 13, 2026 00:56
A standalone (non-Cargo) run has no workspace root, so `project.workspace_root`
is legitimately null. The strengthened leaf-type validation rejected that null
as a type mismatch against the string in the schema template, failing the
json-handler exec tests on every standalone harness. Mark the field _nullable so
a null is accepted while a wrong non-null type is still rejected.
The strengthened leaf-type validation rejected the nulls that --export-json
emits on ordinary degraded runs: unmeasured per-harness property counts (a
timed-out or OOM'd harness reports them as null, not zero, so a degraded run
is not mistaken for "nothing failed"), partial cbmc_stats, an --smt2 run that
names no solver, and unavailable tool/CBMC versions. Mark those leaves
_nullable in the schema template so a null is accepted while a wrong non-null
type is still rejected, and extend the validator test with degraded-run
fixtures (and a null-in-a-required-field fixture that must still be rejected).

@feliperodri feliperodri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR contradicts its own RFC #4727... We shouldn't merge it until RFC 0015 lands with my requested changes. It's a one-way door and it's currently being decided in follow-up PRs rather than the RFC itself. I added all my concerns to #4731.

The PR adds run_state ∈ {incomplete, no_harnesses_selected, partial, complete} and a harness_selection block to the #4472 schema. #4727 specifies a different vocabulary for the same information: schema_version, outcome.kind, outcome.verdict, failure_kind... and a different mechanism for the stale-file problem: temp-file-plus-rename so that "file exists implies complete," plus the up-front delete. That's the paragraph I called the best part of the RFC... This PR implements an in-band "incomplete" marker instead, written non-atomically.

The real bugs are in the wrong layer. Three of the four behavioral fixes patch the JSON writer instead of the thing that's broken:

  1. Zero-match should fail in determine_targets, before codegen, before any export. And there's a bigger bug hiding behind it: print_final_summary early-returns on quiet before reaching the bail, so reading the code, kani --quiet --harness typo skips the "no harnesses matched" error entirely and exits 0. No test covers that combination. That affects every user and every CI that uses the exit code, not just JSON consumers and fixing it in determine_targets makes issue item 2 disappear for free. (I did not build Kani to execute this; it's a code-path reading of harness_runner.rs:265 and metadata.rs:112.)
  2. --fail-fast — root cause is collect::<Result<Vec<_>>>() short-circuiting and discarding already-completed Ok results (harness_runner.rs:105), see #4729. This PR doesn't fix it; it labels the lost harnesses has_errors: true, error_type: "not_reported" asserting an error for a harness that may have passed. That trades one false statement for another, and the text summary and SARIF still under-report. Fix #4729 and item 3 is moot.
  3. harness_id — the PR moves the join predicate to mangled_name but explicitly keeps emitting harness_id: pretty_name. The consumer-facing ambiguity it was filed to fix is still there.

ALSO... maintenance... 98 added comment lines to 125 added Rust lines. The comments narrate reasoning in first person in the tree. "I should note this was partly self-inflicted", "the honest, disjunctive truth", multi-paragraph justifications above three-line functions (likely AI slop). Some of that is already in main from #4472's rescue commits. It will rot the moment 0015 reshapes the schema, and it's the kind of prose a future contributor won't dare touch.

@ivmat

ivmat commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

closing this PR, we go with plan from #4731. you are right, this PR is incomplete, sloppy and not aligned with atomic write design.

@ivmat ivmat closed this Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Z-EndToEndBenchCI Tag a PR to run benchmark CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants