Skip to main content

supercov_engine/
coverage_report.rs

1//! Language-neutral reconstruction of coverage views from frozen obligations
2//! and per-attempt evidence.
3//!
4//! This module deliberately knows nothing about JavaScript or any test runner.
5//! Language frontends provide the manifest and normalized evidence records;
6//! Rust owns merging, attempt outcomes, attribution confidence, filtering and
7//! every structural coverage verdict.
8
9use std::collections::{BTreeMap, BTreeSet, HashMap};
10use std::path::{Path, PathBuf};
11
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use supercov_contracts::{COVERAGE_MODEL_SCHEMA_VERSION, FrontendRunDeclaration};
15
16use crate::coverage_analysis::{
17    AnalysisError, BranchCoverage, CoverageCoreInput, CoverageSummary, DecisionCoverage,
18    McdcVector, PointCoverage, PointKind, analyze_core, find_witnesses_for_conditions,
19};
20use crate::evidence_archive::{EvidenceArchiveEntry, read_archive};
21use crate::interned::{FastMap, Id, Interner};
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "camelCase", deny_unknown_fields)]
25pub struct DecisionMeta {
26    pub id: String,
27    pub file: String,
28    pub line: usize,
29    pub column: usize,
30    pub source: String,
31    pub conditions: Vec<String>,
32    pub kind: String,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase", deny_unknown_fields)]
37pub struct PointMeta {
38    pub id: String,
39    pub kind: PointKind,
40    pub file: String,
41    pub line: usize,
42    pub column: usize,
43    pub source: String,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub label: Option<String>,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(rename_all = "camelCase", deny_unknown_fields)]
50pub struct BranchAlternativeMeta {
51    pub id: String,
52    pub label: String,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(rename_all = "camelCase", deny_unknown_fields)]
57pub struct BranchMeta {
58    pub id: String,
59    pub kind: String,
60    pub file: String,
61    pub line: usize,
62    pub column: usize,
63    pub source: String,
64    pub alternatives: Vec<BranchAlternativeMeta>,
65}
66
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase", deny_unknown_fields)]
69pub struct CoverageManifest {
70    pub decisions: Vec<DecisionMeta>,
71    pub points: Vec<PointMeta>,
72    pub branches: Vec<BranchMeta>,
73    #[serde(default, skip_serializing_if = "Vec::is_empty")]
74    pub limitations: Vec<Value>,
75    /// Obligation IDs the frontend declined to measure exactly. They stay in
76    /// the manifest so the report can say what was not measured, but they must
77    /// never be counted as uncovered.
78    #[serde(default, skip_serializing_if = "Vec::is_empty")]
79    pub unmeasured: Vec<String>,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub scope: Option<Value>,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase", deny_unknown_fields)]
86pub struct DecisionSnapshot {
87    pub meta: DecisionMeta,
88    pub vectors: Vec<McdcVector>,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92#[serde(rename_all = "camelCase", deny_unknown_fields)]
93pub struct RuntimeEvent {
94    #[serde(rename = "type")]
95    pub event_type: String,
96    pub id: String,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub vector: Option<McdcVector>,
99    pub timestamp_ms: i64,
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub phase_id: Option<String>,
102    /// Legacy test-statement location retained for reading historical archives.
103    /// The assertion-map workflow does not emit test-statement markers.
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub statement_id: Option<String>,
106    pub environment: String,
107}
108
109/// One observed (side selected, result truthy) pair of a value-position logical expression.
110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
111#[serde(rename_all = "camelCase", deny_unknown_fields)]
112pub struct LogicalVector {
113    /// The right operand was evaluated (the left did not short-circuit).
114    pub right: bool,
115    /// The selected result was truthy.
116    pub truthy: bool,
117}
118
119/// Distinct outcomes of one `logical-value` branch within a test; gives per-operand outcomes
120/// once combined with the operator recorded in the manifest.
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase", deny_unknown_fields)]
123pub struct LogicalSnapshot {
124    pub id: String,
125    pub vectors: Vec<LogicalVector>,
126}
127
128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
129#[serde(rename_all = "camelCase", deny_unknown_fields)]
130pub struct RuntimeSnapshot {
131    #[serde(default)]
132    pub decisions: Vec<DecisionSnapshot>,
133    #[serde(default)]
134    pub hits: Vec<String>,
135    #[serde(default)]
136    pub events: Vec<RuntimeEvent>,
137    #[serde(default, skip_serializing_if = "Vec::is_empty")]
138    pub logicals: Vec<LogicalSnapshot>,
139    /// Every hit and decision vector of this snapshot is an explicit event of
140    /// this phase. A frontend that observes a whole phase at once -- Python
141    /// reads a phase's slot -- names the phase here instead of writing an
142    /// event per observation, which on a 3,900-test run was three quarters
143    /// of the evidence. The analysis reads it exactly as those events.
144    #[serde(default, skip_serializing_if = "Option::is_none")]
145    pub phase_id: Option<String>,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
149#[serde(rename_all = "camelCase", deny_unknown_fields)]
150pub struct ExecutionScope {
151    pub version: usize,
152    pub run_id: String,
153    pub worker_id: String,
154    pub test_id: String,
155    pub test_key: String,
156    pub retry: usize,
157    pub attempt_id: String,
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161#[serde(rename_all = "camelCase", deny_unknown_fields)]
162pub struct ServerRecord {
163    #[serde(rename = "type")]
164    pub record_type: String,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub meta: Option<DecisionMeta>,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub vector: Option<McdcVector>,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub id: Option<String>,
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub timestamp_ms: Option<i64>,
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub phase_id: Option<String>,
175    /// The producing test statement, under this record's exact execution scope.
176    #[serde(default, skip_serializing_if = "Option::is_none")]
177    pub statement_id: Option<String>,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub scope: Option<ExecutionScope>,
180}
181
182#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
183#[serde(rename_all = "camelCase", deny_unknown_fields)]
184pub struct CoveragePhase {
185    pub id: String,
186    pub kind: String,
187    pub operation: String,
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub source: Option<String>,
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub caused_by_phase_id: Option<String>,
192    pub started_at_ms: i64,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub ended_at_ms: Option<i64>,
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub status: Option<String>,
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub error: Option<String>,
199}
200
201#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202#[serde(rename_all = "camelCase", deny_unknown_fields)]
203pub struct TestProvenance {
204    pub runner: String,
205    pub kind: String,
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub project: Option<String>,
208    pub source: String,
209}
210
211impl Default for TestProvenance {
212    fn default() -> Self {
213        Self {
214            runner: "unknown".into(),
215            kind: "unknown".into(),
216            project: None,
217            source: "unknown".into(),
218        }
219    }
220}
221
222#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
223#[serde(rename_all = "camelCase", deny_unknown_fields)]
224pub struct RawTestResult {
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub test_id: Option<String>,
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub scope: Option<ExecutionScope>,
229    pub test: String,
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub test_file: Option<String>,
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub title: Option<String>,
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub retry: Option<usize>,
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub status: Option<String>,
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub expected_status: Option<String>,
240    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
241    pub flaky: bool,
242    #[serde(default)]
243    pub provenance: TestProvenance,
244    #[serde(default = "default_test_role")]
245    pub role: String,
246    /// How exactly this test's coverage could be credited to it.
247    ///
248    /// `exact` is the ordinary case and the default, so a frontend that has
249    /// not been taught the difference keeps its behaviour. `run-wide` says the
250    /// test ran and reached code, and that nothing can say which code: a Go
251    /// test that called `t.Parallel()` stores into the same probe array as the
252    /// tests running beside it.
253    ///
254    /// The distinction is the whole point of the field. A test that reached
255    /// nothing and a test whose reach cannot be narrowed both have an empty
256    /// hit set, and reporting the second as the first is a wrong number
257    /// wearing the shape of a right one.
258    #[serde(default = "default_attribution")]
259    pub attribution: String,
260    #[serde(default)]
261    pub phases: Vec<CoveragePhase>,
262    #[serde(default)]
263    pub runtime: Vec<RuntimeSnapshot>,
264    #[serde(default)]
265    pub browser: Vec<RuntimeSnapshot>,
266    #[serde(default)]
267    pub server: Vec<ServerRecord>,
268}
269
270fn default_test_role() -> String {
271    "test".into()
272}
273
274fn default_attribution() -> String {
275    ATTRIBUTION_EXACT.into()
276}
277
278/// This test's coverage is its own: what it reached was credited to it.
279pub const ATTRIBUTION_EXACT: &str = "exact";
280
281/// This test ran, and what it reached was recorded against the run rather than
282/// against it. Its own reach is unknown, and bounded above by the run's.
283pub const ATTRIBUTION_RUN_WIDE: &str = "run-wide";
284
285/// What this test is recorded as reaching is really its own, and is not all of
286/// it: a lower bound, with the run's coverage as the upper one.
287///
288/// Ruby records a line against the first test that reaches it and never again,
289/// which is what makes its collection cheap. Every later test that runs the
290/// same line is recorded as having reached nothing there -- so a test's hits
291/// prove what it ran and its silence proves nothing.
292pub const ATTRIBUTION_PARTIAL: &str = "partial";
293
294/// Whether what this test is recorded as reaching is its own.
295///
296/// True for `exact` and for `partial`: both record real coverage of this
297/// test's, and a union over them is a union of things that happened. False
298/// only where the coverage went to the run instead, and counting the test in a
299/// percentage would divide by a test that contributes nothing.
300pub fn coverage_is_its_own(attribution: &str) -> bool {
301    attribution != ATTRIBUTION_RUN_WIDE
302}
303
304/// Whether this test's silence means it did not run the code.
305///
306/// Only `exact` earns that. Under `partial` a test that is recorded as
307/// reaching nothing may have run the same lines as the test that was credited
308/// with them, and under `run-wide` nothing was recorded at all -- so reading
309/// an absence as proof is how a change to code a test exercised comes back as
310/// "this test is unaffected".
311pub fn coverage_is_complete(attribution: &str) -> bool {
312    attribution == ATTRIBUTION_EXACT || attribution.is_empty()
313}
314
315#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
316#[serde(rename_all = "camelCase")]
317pub struct TestAttempt {
318    pub retry: usize,
319    pub status: String,
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub expected_status: Option<String>,
322}
323
324#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
325#[serde(rename_all = "camelCase")]
326pub struct CoverageConfidence {
327    pub level: String,
328    pub setup_only: bool,
329    pub background_only: bool,
330    pub asserted: bool,
331    pub tests: Vec<Id>,
332    pub asserted_tests: Vec<Id>,
333    pub runners: Vec<String>,
334    pub kinds: Vec<String>,
335    pub e2e: bool,
336}
337
338#[derive(Debug, Clone, PartialEq, Serialize)]
339#[serde(rename_all = "camelCase")]
340pub struct VectorObservation {
341    pub vector: McdcVector,
342    pub tests: Vec<Id>,
343    #[serde(skip_serializing_if = "Vec::is_empty")]
344    pub phases: Vec<Id>,
345    #[serde(skip_serializing_if = "Vec::is_empty")]
346    pub explicit_phases: Vec<Id>,
347    pub confidence: CoverageConfidence,
348}
349
350#[derive(Debug, Clone, PartialEq, Serialize)]
351#[serde(rename_all = "camelCase")]
352pub struct ConditionResult {
353    pub index: usize,
354    pub source: String,
355    pub covered: bool,
356    pub assertion_covered: bool,
357    #[serde(skip_serializing_if = "Option::is_none")]
358    pub witness: Option<[McdcVector; 2]>,
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub witness_tests: Option<[Vec<Id>; 2]>,
361}
362
363#[derive(Debug, Clone, PartialEq, Serialize)]
364#[serde(rename_all = "camelCase")]
365pub struct DecisionResult {
366    pub meta: DecisionMeta,
367    pub executed: bool,
368    pub covered: bool,
369    pub vectors: Vec<McdcVector>,
370    pub vector_observations: Vec<VectorObservation>,
371    pub conditions: Vec<ConditionResult>,
372    pub tests: Vec<Id>,
373    pub confidence: CoverageConfidence,
374}
375
376#[derive(Debug, Clone, PartialEq, Serialize)]
377#[serde(rename_all = "camelCase")]
378pub struct PointResult {
379    pub meta: PointMeta,
380    pub covered: bool,
381    #[serde(skip)]
382    pub measured: bool,
383    pub tests: Vec<Id>,
384    pub phases: Vec<Id>,
385    pub confidence: CoverageConfidence,
386}
387
388#[derive(Debug, Clone, PartialEq, Serialize)]
389#[serde(rename_all = "camelCase")]
390pub struct AlternativeResult {
391    pub id: String,
392    pub label: String,
393    pub covered: bool,
394    pub tests: Vec<Id>,
395    pub phases: Vec<Id>,
396    pub confidence: CoverageConfidence,
397}
398
399#[derive(Debug, Clone, PartialEq, Serialize)]
400#[serde(rename_all = "camelCase")]
401pub struct BranchResult {
402    pub meta: BranchMeta,
403    pub covered: bool,
404    pub alternatives: Vec<AlternativeResult>,
405}
406
407#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
408pub struct SourceLine {
409    pub file: Id,
410    pub line: usize,
411}
412
413/// One line's share of the points that sit on it, while the view is built.
414#[derive(Debug, Default)]
415struct LineAggregate {
416    covered: bool,
417    measured: bool,
418    /// Numbers of the relation each is drawn from, in no order.
419    tests: Vec<u32>,
420    phases: Vec<u32>,
421    explicit_phases: Vec<u32>,
422}
423
424#[derive(Debug, Clone, PartialEq, Serialize)]
425#[serde(rename_all = "camelCase")]
426pub struct LineResult {
427    pub file: String,
428    pub line: usize,
429    pub covered: bool,
430    /// False when every obligation on this line was declined, which keeps the
431    /// line addressable while leaving it out of the covered/total counts. Not
432    /// serialized: it decides a total, it is not a fact about the line worth
433    /// publishing, and the limitation records already say why.
434    #[serde(skip)]
435    pub measured: bool,
436    pub tests: Vec<Id>,
437    pub runners: Vec<String>,
438    pub kinds: Vec<String>,
439    #[serde(skip_serializing_if = "Option::is_none")]
440    pub exclusive_kind: Option<String>,
441    pub phases: Vec<Id>,
442    pub confidence: CoverageConfidence,
443}
444
445#[derive(Debug, Clone, PartialEq, Serialize)]
446#[serde(rename_all = "camelCase")]
447pub struct TestDecisionResult {
448    pub id: String,
449    pub vectors: Vec<McdcVector>,
450}
451
452#[derive(Debug, Clone, PartialEq, Serialize)]
453#[serde(rename_all = "camelCase")]
454pub struct TestCoverageResult {
455    pub id: String,
456    pub name: String,
457    #[serde(skip_serializing_if = "Option::is_none")]
458    pub file: Option<String>,
459    #[serde(skip_serializing_if = "Option::is_none")]
460    pub title: Option<String>,
461    pub retries: Vec<usize>,
462    pub attempts: Vec<TestAttempt>,
463    pub outcome: String,
464    pub provenance: TestProvenance,
465    pub role: String,
466    /// `exact`, or `run-wide` when the test ran but nothing can say what it
467    /// reached. An empty `hits` means "reached nothing" only under `exact`.
468    pub attribution: String,
469    pub hits: Vec<Id>,
470    pub decisions: Vec<TestDecisionResult>,
471    pub lines: Vec<SourceLine>,
472}
473
474#[derive(Debug, Clone, PartialEq, Serialize)]
475#[serde(rename_all = "camelCase")]
476pub struct TestFileResult {
477    pub file: String,
478    pub tests: Vec<String>,
479    pub runners: Vec<String>,
480    pub kinds: Vec<String>,
481    pub lines: Vec<SourceLine>,
482}
483
484#[derive(Debug, Clone, PartialEq, Serialize)]
485#[serde(rename_all = "camelCase")]
486pub struct PhaseResult {
487    #[serde(flatten)]
488    pub phase: CoveragePhase,
489    pub test: Id,
490    pub hits: Vec<Id>,
491    pub decisions: Vec<TestDecisionResult>,
492    pub lines: Vec<SourceLine>,
493    pub browser_events: usize,
494    pub server_events: usize,
495    pub explicit_events: usize,
496    pub inferred_events: usize,
497    pub explicit_browser_events: usize,
498    pub inferred_browser_events: usize,
499    pub explicit_server_events: usize,
500    pub inferred_server_events: usize,
501}
502
503#[derive(Debug, Clone, PartialEq, Serialize)]
504#[serde(rename_all = "camelCase")]
505pub struct DimensionCoverage {
506    #[serde(skip_serializing_if = "Option::is_none")]
507    pub kind: Option<String>,
508    #[serde(skip_serializing_if = "Option::is_none")]
509    pub runner: Option<String>,
510    pub tests: usize,
511    pub setups: usize,
512    /// How many of `tests` have coverage of their own. The summary below is
513    /// computed over exactly these: a test whose reach nothing can narrow
514    /// contributes no hits, and counting it would report a suite that covers
515    /// everything as covering nothing.
516    pub attributed: usize,
517    pub summary: CoverageSummary,
518}
519
520#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
521#[serde(rename_all = "camelCase")]
522pub struct CoverageModel {
523    pub language: String,
524    pub name: String,
525    pub completeness_meaning: String,
526    pub measured: Vec<String>,
527    pub not_measured: Vec<String>,
528}
529
530#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
531#[serde(rename_all = "camelCase", deny_unknown_fields)]
532pub struct CoverageModelDeclaration {
533    pub language: String,
534    pub variant: String,
535    pub name: String,
536    pub completeness_meaning: String,
537    pub measured: Vec<String>,
538    pub not_measured: Vec<String>,
539}
540
541#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
542#[serde(rename_all = "camelCase", deny_unknown_fields)]
543pub struct PersistedCoverageModel {
544    pub schema_version: u32,
545    pub language: String,
546    pub variant: String,
547    pub name: String,
548    pub completeness_meaning: String,
549    pub measured: Vec<String>,
550    pub not_measured: Vec<String>,
551}
552
553impl PersistedCoverageModel {
554    pub fn from_declaration(value: &CoverageModelDeclaration) -> Result<Self, &'static str> {
555        let persisted = Self {
556            schema_version: COVERAGE_MODEL_SCHEMA_VERSION,
557            language: value.language.clone(),
558            variant: value.variant.clone(),
559            name: value.name.clone(),
560            completeness_meaning: value.completeness_meaning.clone(),
561            measured: value.measured.clone(),
562            not_measured: value.not_measured.clone(),
563        };
564        persisted.clone().into_declaration()?;
565        Ok(persisted)
566    }
567
568    fn into_declaration(self) -> Result<CoverageModelDeclaration, &'static str> {
569        if self.schema_version != COVERAGE_MODEL_SCHEMA_VERSION {
570            return Err("unsupported coverage model schema");
571        }
572        let valid_identifier = |value: &str| {
573            (1..=supercov_contracts::COVERAGE_MODEL_MAX_IDENTIFIER_BYTES).contains(&value.len())
574                && value.as_bytes()[0].is_ascii_lowercase()
575                && value.bytes().all(|byte| {
576                    byte.is_ascii_lowercase()
577                        || byte.is_ascii_digit()
578                        || matches!(byte, b'.' | b'+' | b'-')
579                })
580        };
581        let valid_description = |value: &str| {
582            !value.is_empty()
583                && value.trim().len() == value.len()
584                && value.len() <= supercov_contracts::COVERAGE_MODEL_MAX_DESCRIPTION_BYTES
585                && !value.chars().any(char::is_control)
586        };
587        if !valid_identifier(&self.language)
588            || !valid_identifier(&self.variant)
589            || !valid_description(&self.name)
590            || !valid_description(&self.completeness_meaning)
591            || self.measured.is_empty()
592            || self.measured.len() > supercov_contracts::COVERAGE_MODEL_MAX_SURFACES_PER_LIST
593            || self.not_measured.len() > supercov_contracts::COVERAGE_MODEL_MAX_SURFACES_PER_LIST
594            || self
595                .measured
596                .iter()
597                .chain(&self.not_measured)
598                .any(|item| !valid_description(item))
599        {
600            return Err("invalid coverage model declaration");
601        }
602        let measured = self.measured.iter().collect::<BTreeSet<_>>();
603        let not_measured = self.not_measured.iter().collect::<BTreeSet<_>>();
604        if measured.len() != self.measured.len()
605            || not_measured.len() != self.not_measured.len()
606            || !measured.is_disjoint(&not_measured)
607        {
608            return Err("invalid coverage model surface partition");
609        }
610        Ok(CoverageModelDeclaration {
611            language: self.language,
612            variant: self.variant,
613            name: self.name,
614            completeness_meaning: self.completeness_meaning,
615            measured: self.measured,
616            not_measured: self.not_measured,
617        })
618    }
619}
620
621#[derive(Debug, Clone, PartialEq, Serialize)]
622#[serde(rename_all = "camelCase")]
623pub struct CoverageView {
624    pub generated_at: String,
625    pub variant: String,
626    #[serde(skip_serializing_if = "Option::is_none")]
627    pub scope: Option<Value>,
628    pub model: CoverageModel,
629    #[serde(skip_serializing_if = "Option::is_none")]
630    pub integrity: Option<Value>,
631    pub limitations: Vec<Value>,
632    #[serde(skip_serializing_if = "Option::is_none")]
633    pub transport: Option<TransportStats>,
634    pub summary: CoverageSummary,
635    pub coverage_by_kind: Vec<DimensionCoverage>,
636    pub coverage_by_runner: Vec<DimensionCoverage>,
637    pub decisions: Vec<DecisionResult>,
638    pub points: Vec<PointResult>,
639    pub branches: Vec<BranchResult>,
640    pub tests: Vec<TestCoverageResult>,
641    pub test_files: Vec<TestFileResult>,
642    pub phases: Vec<PhaseResult>,
643    pub lines: Vec<LineResult>,
644}
645
646#[derive(Debug, Clone, PartialEq, Serialize)]
647#[serde(rename_all = "camelCase")]
648pub struct CoverageFilters {
649    pub passed: CoverageView,
650    pub failed: CoverageView,
651}
652
653#[derive(Debug, Clone, PartialEq, Serialize)]
654#[serde(rename_all = "camelCase")]
655pub struct CoverageReport {
656    #[serde(flatten)]
657    pub view: CoverageView,
658    #[serde(skip_serializing_if = "Option::is_none")]
659    pub execution: Option<ExecutionResult>,
660    pub filters: CoverageFilters,
661}
662
663#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
664#[serde(rename_all = "camelCase")]
665pub struct ExecutionResult {
666    pub test_exit_code: Option<i32>,
667    pub valid: bool,
668}
669
670#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
671#[serde(rename_all = "camelCase")]
672pub struct TransportStats {
673    pub processes: usize,
674    pub child_launches: usize,
675    pub remote_launches: usize,
676    pub workspace_capabilities: usize,
677    pub scoped_server_records: usize,
678    pub background_server_records: usize,
679    pub corrupt_records: usize,
680    pub corrupt_files: usize,
681}
682
683#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
684#[serde(untagged)]
685enum ExecutionSafeArgument {
686    Text(String),
687    Digest(ExecutionArgumentDigest),
688}
689
690#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
691#[serde(rename_all = "camelCase", deny_unknown_fields)]
692struct ExecutionArgumentDigest {
693    bytes: usize,
694    sha256: String,
695}
696
697#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
698#[serde(rename_all = "camelCase", deny_unknown_fields)]
699struct ExecutionCommandSummary {
700    #[serde(default)]
701    executable: Option<ExecutionSafeArgument>,
702    arguments: Vec<ExecutionSafeArgument>,
703    argument_count: usize,
704}
705
706#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
707#[serde(
708    tag = "event",
709    rename_all = "kebab-case",
710    rename_all_fields = "camelCase",
711    deny_unknown_fields
712)]
713enum ExecutionTraceEvent {
714    Process {
715        at: String,
716        pid: u32,
717        ppid: u32,
718        cwd: String,
719        command: ExecutionCommandSummary,
720        #[serde(default)]
721        entrypoint: Option<String>,
722    },
723    ChildLaunch {
724        at: String,
725        pid: u32,
726        ppid: u32,
727        method: String,
728        command: ExecutionSafeArgument,
729    },
730    RemoteLaunch {
731        at: String,
732        pid: u32,
733        ppid: u32,
734        command: ExecutionCommandSummary,
735        guest_root: String,
736    },
737    WorkspaceCapability {
738        at: String,
739        pid: u32,
740        ppid: u32,
741        host_root: String,
742        guest_root: String,
743        cache_identities: Vec<String>,
744    },
745}
746
747impl ExecutionTraceEvent {
748    fn kind(&self) -> &'static str {
749        match self {
750            Self::Process { .. } => "process",
751            Self::ChildLaunch { .. } => "child-launch",
752            Self::RemoteLaunch { .. } => "remote-launch",
753            Self::WorkspaceCapability { .. } => "workspace-capability",
754        }
755    }
756}
757
758#[derive(Debug, Clone, PartialEq, Deserialize)]
759#[serde(rename_all = "camelCase", deny_unknown_fields)]
760pub struct CoverageReportRequest {
761    pub run_id: String,
762    pub manifest: CoverageManifest,
763    pub raw_results: Vec<RawTestResult>,
764    pub generated_at: String,
765    #[serde(default, skip_serializing_if = "Option::is_none")]
766    pub coverage_model: Option<CoverageModelDeclaration>,
767    #[serde(skip_serializing_if = "Option::is_none")]
768    pub integrity: Option<Value>,
769    #[serde(default, deserialize_with = "deserialize_exit_code")]
770    pub test_exit_code: ExitCodeInput,
771}
772
773#[derive(Debug, Clone, PartialEq, Deserialize)]
774#[serde(rename_all = "camelCase", deny_unknown_fields)]
775pub struct ArchiveReportRequest {
776    pub archive_path: PathBuf,
777    pub run_id: String,
778    pub generated_at: String,
779    #[serde(skip_serializing_if = "Option::is_none")]
780    pub integrity: Option<Value>,
781    #[serde(default, deserialize_with = "deserialize_exit_code")]
782    pub test_exit_code: ExitCodeInput,
783}
784
785#[derive(Debug, Clone, PartialEq, Eq, Default)]
786pub enum ExitCodeInput {
787    #[default]
788    Missing,
789    Present(Option<i32>),
790}
791
792fn deserialize_exit_code<'de, D>(deserializer: D) -> Result<ExitCodeInput, D::Error>
793where
794    D: serde::Deserializer<'de>,
795{
796    Option::<i32>::deserialize(deserializer).map(ExitCodeInput::Present)
797}
798
799#[derive(Debug, Clone, PartialEq, Eq)]
800pub enum ReportError {
801    Analysis(AnalysisError),
802    DecisionAnalysis {
803        decision_id: String,
804        error: AnalysisError,
805    },
806    InvalidEvent(String),
807    InvalidServerRecord(String),
808    InvalidArchive(String),
809    MissingManifest,
810    InvalidJson {
811        path: String,
812        reason: String,
813    },
814    ScopeMismatch {
815        expected: String,
816        actual: String,
817    },
818    NoEvidence(String),
819}
820
821impl From<AnalysisError> for ReportError {
822    fn from(value: AnalysisError) -> Self {
823        Self::Analysis(value)
824    }
825}
826
827#[derive(Clone, Default)]
828struct OrderedVectors {
829    values: Vec<McdcVector>,
830    indexes: HashMap<String, usize>,
831}
832
833impl OrderedVectors {
834    fn insert(&mut self, vector: &McdcVector) -> usize {
835        let key = vector_key(vector);
836        if let Some(index) = self.indexes.get(&key) {
837            return *index;
838        }
839        let index = self.values.len();
840        self.values.push(vector.clone());
841        self.indexes.insert(key, index);
842        index
843    }
844}
845
846#[derive(Clone)]
847struct MutableObservation {
848    vector: McdcVector,
849    /// Numbers of the view's test, phase and explicit phase relations, as
850    /// they arrive: sorted and made unique when the view is written.
851    tests: Vec<u32>,
852    phases: Vec<u32>,
853    explicit_phases: Vec<u32>,
854}
855
856#[derive(Clone)]
857struct MutableTest {
858    id: String,
859    name: String,
860    file: Option<String>,
861    title: Option<String>,
862    retries: BTreeSet<usize>,
863    attempts: BTreeMap<usize, TestAttempt>,
864    unstarted: bool,
865    runner_reported_flaky: bool,
866    provenance: TestProvenance,
867    role: String,
868    attribution: String,
869    /// Hit numbers as they arrive, repeats included: sorted and made unique
870    /// once, when the view is written.
871    hits: Vec<usize>,
872    decisions: BTreeMap<String, OrderedVectors>,
873}
874
875#[derive(Clone)]
876struct MutablePhase {
877    phase: CoveragePhase,
878    test: Id,
879    hits: Vec<usize>,
880    decisions: BTreeMap<String, OrderedVectors>,
881    browser_events: usize,
882    server_events: usize,
883    explicit_events: usize,
884    inferred_events: usize,
885    explicit_browser_events: usize,
886    inferred_browser_events: usize,
887    explicit_server_events: usize,
888    inferred_server_events: usize,
889}
890
891fn vector_key(vector: &McdcVector) -> String {
892    let mut key = String::with_capacity(vector.values.len() + 2);
893    for value in &vector.values {
894        key.push(match value {
895            None => '-',
896            Some(false) => 'F',
897            Some(true) => 'T',
898        });
899    }
900    key.push(':');
901    key.push(if vector.outcome { 'T' } else { 'F' });
902    key
903}
904
905fn sorted<T: Clone + Ord>(values: &BTreeSet<T>) -> Vec<T> {
906    values.iter().cloned().collect()
907}
908
909fn record_attempt(test: &mut MutableTest, raw: &RawTestResult) {
910    let (Some(retry), Some(raw_status)) = (raw.retry, raw.status.as_ref()) else {
911        return;
912    };
913    let previous = test.attempts.get(&retry);
914    let status = if raw_status == "unknown" {
915        previous.map_or_else(|| raw_status.clone(), |attempt| attempt.status.clone())
916    } else {
917        raw_status.clone()
918    };
919    let expected_status = raw
920        .expected_status
921        .clone()
922        .or_else(|| previous.and_then(|attempt| attempt.expected_status.clone()));
923    test.attempts.insert(
924        retry,
925        TestAttempt {
926            retry,
927            status,
928            expected_status,
929        },
930    );
931}
932
933fn effective_attempt_status(attempt: &TestAttempt) -> &str {
934    if attempt.expected_status.as_deref() == Some("failed") {
935        match attempt.status.as_str() {
936            "failed" => "passed",
937            "passed" => "failed",
938            status => status,
939        }
940    } else {
941        attempt.status.as_str()
942    }
943}
944
945fn test_outcome(test: &MutableTest) -> String {
946    let Some(terminal) = test.attempts.values().next_back() else {
947        return if test.unstarted {
948            "unstarted".into()
949        } else {
950            "unknown".into()
951        };
952    };
953    let terminal_status = effective_attempt_status(terminal);
954    if terminal_status == "passed"
955        && (test.runner_reported_flaky
956            || test
957                .attempts
958                .values()
959                .take(test.attempts.len().saturating_sub(1))
960                .any(|attempt| effective_attempt_status(attempt) != "passed"))
961    {
962        "flaky".into()
963    } else {
964        terminal_status.into()
965    }
966}
967
968fn raw_test_id(raw: &RawTestResult) -> &str {
969    raw.test_id.as_deref().unwrap_or(&raw.test)
970}
971
972/// Borrowed rather than copied: a view only reads its results, and copying
973/// every result of a 3,900-test run to build the passing view cost a sixth of
974/// the analysis.
975pub fn passing_coverage_results(raw_results: &[RawTestResult]) -> Vec<&RawTestResult> {
976    let mut attempts: BTreeMap<(String, usize), (BTreeSet<String>, bool)> = BTreeMap::new();
977    for raw in raw_results {
978        let Some(retry) = raw.retry else {
979            continue;
980        };
981        let entry = attempts
982            .entry((raw_test_id(raw).into(), retry))
983            .or_default();
984        if let Some(status) = &raw.status {
985            entry.0.insert(status.clone());
986        }
987        entry.1 |= raw.expected_status.as_deref() == Some("failed");
988    }
989    let mut terminal_retries = BTreeMap::<String, usize>::new();
990    for (test, retry) in attempts.keys() {
991        terminal_retries
992            .entry(test.clone())
993            .and_modify(|value| *value = (*value).max(*retry))
994            .or_insert(*retry);
995    }
996    let accepted = terminal_retries
997        .into_iter()
998        .filter_map(|(test, retry)| {
999            let (statuses, expected_failure) = attempts.get(&(test.clone(), retry))?;
1000            (statuses.contains("passed") && !expected_failure).then_some((test, retry))
1001        })
1002        .collect::<BTreeSet<_>>();
1003    raw_results
1004        .iter()
1005        .filter(|raw| {
1006            raw.retry
1007                .is_some_and(|retry| accepted.contains(&(raw_test_id(raw).into(), retry)))
1008        })
1009        .collect()
1010}
1011
1012pub fn failed_coverage_results(raw_results: &[RawTestResult]) -> Vec<&RawTestResult> {
1013    let mut attempts = BTreeMap::<(String, usize), Vec<&RawTestResult>>::new();
1014    for raw in raw_results {
1015        if let Some(retry) = raw.retry {
1016            attempts
1017                .entry((raw_test_id(raw).to_owned(), retry))
1018                .or_default()
1019                .push(raw);
1020        }
1021    }
1022    let failed = attempts
1023        .into_iter()
1024        .filter_map(|(identity, records)| {
1025            // Runner reporters carry expected-status semantics while hook
1026            // evidence records often do not. Treat reporter records as the
1027            // authority for the attempt outcome, then retain every companion
1028            // evidence record only after the attempt is classified.
1029            let authoritative = records
1030                .iter()
1031                .copied()
1032                .filter(|raw| raw.expected_status.is_some())
1033                .collect::<Vec<_>>();
1034            let authoritative = if authoritative.is_empty() {
1035                records
1036            } else {
1037                authoritative
1038            };
1039            authoritative
1040                .iter()
1041                .any(
1042                    |raw| match (raw.status.as_deref(), raw.expected_status.as_deref()) {
1043                        (Some("failed"), Some("failed")) => false,
1044                        (Some("passed"), Some("failed")) => true,
1045                        (Some("failed"), _) => true,
1046                        _ => false,
1047                    },
1048                )
1049                .then_some(identity)
1050        })
1051        .collect::<BTreeSet<_>>();
1052    raw_results
1053        .iter()
1054        .filter(|raw| {
1055            raw.retry
1056                .is_some_and(|retry| failed.contains(&(raw_test_id(raw).into(), retry)))
1057        })
1058        .collect()
1059}
1060
1061pub(crate) fn javascript_coverage_model() -> CoverageModelDeclaration {
1062    CoverageModelDeclaration {
1063        language: "javascript".into(),
1064        variant: "masking-short-circuit".into(),
1065        name: "coverage-completeness-v2".into(),
1066        completeness_meaning: "Every obligation in the measured model was observed by at least one existing test; test assertions and product correctness are separate assumptions.".into(),
1067        measured: [
1068            "executable source lines",
1069            "executable statements",
1070            "function entries",
1071            "true and false outcomes of if, ternary, while, do/while, and classic for decisions",
1072            "true and false outcomes of every atomic condition in those decisions",
1073            "masking MC/DC independence for every atomic condition in those decisions",
1074            "short-circuit and right-evaluated selections for &&, ||, and ?? value expressions, including JSX",
1075            "short-circuit and evaluated alternatives for logical assignments and optional chains",
1076            "provided and default-evaluated parameter and destructuring values",
1077            "try success and catch entry",
1078            "zero and entered for-in/for-of loops",
1079            "entered switch cases, defaults, and implicit no-match alternatives",
1080        ]
1081        .into_iter()
1082        .map(str::to_owned)
1083        .collect(),
1084        not_measured: [
1085            "all input values or semantic input partitions",
1086            "all execution paths or ordering/concurrency interleavings",
1087            "destructuring defaults in classic for initializers (reported as blockers when discovered)",
1088            "the internal statements and decisions of runtime-generated eval/Function source",
1089            "mutation score or assertion fault-detection strength",
1090        ]
1091        .into_iter()
1092        .map(str::to_owned)
1093        .collect(),
1094    }
1095}
1096
1097/// One file's counts, derived by the same function that produced the run's
1098/// totals.
1099///
1100/// Every feature that judges a run -- a threshold gate, a changed-line check,
1101/// an export, an HTML report -- has to agree with the summary it sits beside.
1102/// Re-deriving a file's denominator independently is how a gate and a report
1103/// come to disagree about the same run, so this filters the same results and
1104/// calls the same core.
1105pub fn coverage_summary_for_file(
1106    view: &CoverageView,
1107    file: &str,
1108) -> Result<CoverageSummary, ReportError> {
1109    let decisions = view
1110        .decisions
1111        .iter()
1112        .filter(|decision| decision.meta.file == file)
1113        .cloned()
1114        .collect::<Vec<_>>();
1115    let points = view
1116        .points
1117        .iter()
1118        .filter(|point| point.meta.file == file)
1119        .cloned()
1120        .collect::<Vec<_>>();
1121    let branches = view
1122        .branches
1123        .iter()
1124        .filter(|branch| branch.meta.file == file)
1125        .cloned()
1126        .collect::<Vec<_>>();
1127    let lines = view
1128        .lines
1129        .iter()
1130        .filter(|line| line.file == file)
1131        .cloned()
1132        .collect::<Vec<_>>();
1133    summary_for_results(&decisions, &points, &branches, &lines, None)
1134}
1135
1136fn summary_for_results(
1137    decisions: &[DecisionResult],
1138    points: &[PointResult],
1139    branches: &[BranchResult],
1140    lines: &[LineResult],
1141    test_ids: Option<&BTreeSet<String>>,
1142) -> Result<CoverageSummary, ReportError> {
1143    // Hashed once: every obligation asks it about each of its tests.
1144    let test_ids = test_ids.map(|ids| {
1145        ids.iter()
1146            .map(String::as_str)
1147            .collect::<crate::interned::FastSet<&str>>()
1148    });
1149    let includes = |tests: &[Id], covered: bool| {
1150        test_ids.as_ref().map_or(covered, |selected| {
1151            tests.iter().any(|test| selected.contains(test.as_str()))
1152        })
1153    };
1154    let input = CoverageCoreInput {
1155        decisions: decisions
1156            .iter()
1157            .map(|decision| DecisionCoverage {
1158                condition_count: decision.meta.conditions.len(),
1159                vectors: decision
1160                    .vector_observations
1161                    .iter()
1162                    .filter(|observation| includes(&observation.tests, true))
1163                    .map(|observation| observation.vector.clone())
1164                    .collect(),
1165            })
1166            .collect(),
1167        points: points
1168            .iter()
1169            .map(|point| PointCoverage {
1170                kind: point.meta.kind.clone(),
1171                covered: includes(&point.tests, point.covered),
1172            })
1173            .collect(),
1174        branches: branches
1175            .iter()
1176            .map(|branch| BranchCoverage {
1177                kind: branch.meta.kind.clone(),
1178                alternatives: branch
1179                    .alternatives
1180                    .iter()
1181                    .map(|alternative| includes(&alternative.tests, alternative.covered))
1182                    .collect(),
1183            })
1184            .collect(),
1185        // A line every frontend declined carries no coverage question, so it
1186        // is absent from the total rather than counted as uncovered.
1187        lines: lines
1188            .iter()
1189            .filter(|line| line.measured)
1190            .map(|line| includes(&line.tests, line.covered))
1191            .collect(),
1192    };
1193    Ok(analyze_core(&input)?.summary)
1194}
1195
1196/// Recompute every structural coverage metric for an arbitrary set of test,
1197/// setup, and background evidence identities. MC/DC witnesses are rebuilt
1198/// from the selected observations; existing aggregate verdicts are never
1199/// reused.
1200pub fn coverage_summary_for_tests(
1201    view: &CoverageView,
1202    test_ids: &BTreeSet<String>,
1203) -> Result<CoverageSummary, ReportError> {
1204    summary_for_results(
1205        &view.decisions,
1206        &view.points,
1207        &view.branches,
1208        &view.lines,
1209        Some(test_ids),
1210    )
1211}
1212
1213/// Whether a manifest limitation blocks measurement of the denominator, as
1214/// opposed to declaring a boundary of it. Absent means blocking.
1215pub fn blocking_limitation(limitation: &Value) -> bool {
1216    limitation
1217        .get("blocking")
1218        .and_then(Value::as_bool)
1219        .unwrap_or(true)
1220}
1221
1222fn hash_slot<'m, V: Default, S: std::hash::BuildHasher>(
1223    map: &'m mut HashMap<String, V, S>,
1224    key: &str,
1225) -> &'m mut V {
1226    if !map.contains_key(key) {
1227        map.insert(key.to_owned(), V::default());
1228    }
1229    map.get_mut(key).expect("slot was inserted")
1230}
1231
1232fn tree_slot<'m, V: Default>(map: &'m mut BTreeMap<String, V>, key: &str) -> &'m mut V {
1233    if !map.contains_key(key) {
1234        map.insert(key.to_owned(), V::default());
1235    }
1236    map.get_mut(key).expect("slot was inserted")
1237}
1238
1239/// The phase an event belongs to: its own, or the last to start before it.
1240fn correlate_event<'a>(
1241    event: &'a RuntimeEvent,
1242    ordered_phases: &'a [CoveragePhase],
1243) -> Option<&'a str> {
1244    event.phase_id.as_deref().or_else(|| {
1245        ordered_phases
1246            .iter()
1247            .take_while(|phase| phase.started_at_ms <= event.timestamp_ms)
1248            .last()
1249            .map(|phase| phase.id.as_str())
1250    })
1251}
1252
1253/// One lookup per hit while ingesting evidence. All hit relations share these
1254/// dense numbers, avoiding repeated string hashing and tree comparisons. Even
1255/// IDs absent from the manifest are retained; output restores lexical ordering.
1256#[derive(Default)]
1257struct HitIds {
1258    names: Vec<Id>,
1259    numbers: FastMap<String, usize>,
1260}
1261
1262impl HitIds {
1263    fn number(&mut self, name: &str, ids: &mut Interner) -> usize {
1264        if let Some(number) = self.numbers.get(name) {
1265            return *number;
1266        }
1267        let number = self.names.len();
1268        self.names.push(ids.id(name));
1269        self.numbers.insert(name.to_owned(), number);
1270        number
1271    }
1272
1273    /// Each hit number's position in id order: hits sort by these numbers
1274    /// instead of by comparing their text, per test and per phase.
1275    fn ranks(&self) -> Vec<u32> {
1276        let mut order = (0..self.names.len()).collect::<Vec<_>>();
1277        order.sort_unstable_by(|left, right| self.names[*left].cmp(&self.names[*right]));
1278        let mut rank = vec![0_u32; self.names.len()];
1279        for (position, number) in order.into_iter().enumerate() {
1280            rank[number] = position as u32;
1281        }
1282        rank
1283    }
1284
1285    /// `sorted`, by precomputed rank.
1286    fn sorted_by(&self, mut numbers: Vec<usize>, rank: &[u32]) -> Vec<Id> {
1287        numbers.sort_unstable_by_key(|number| rank[*number]);
1288        numbers.dedup();
1289        numbers
1290            .into_iter()
1291            .map(|number| self.names[number].clone())
1292            .collect()
1293    }
1294
1295    #[cfg(test)]
1296    fn sorted(&self, numbers: BTreeSet<usize>) -> Vec<Id> {
1297        let mut names = numbers
1298            .into_iter()
1299            .map(|n| self.names[n].clone())
1300            .collect::<Vec<_>>();
1301        names.sort_unstable();
1302        names
1303    }
1304}
1305
1306/// Which tests or phases reached each obligation. Both sides are numbered
1307/// while ingesting evidence, then restored to sorted ID sets for the report.
1308#[derive(Default)]
1309struct References {
1310    names: Vec<Id>,
1311    numbers: FastMap<Id, u32>,
1312    last: Option<u32>,
1313    by_obligation: Vec<Vec<u32>>,
1314}
1315
1316impl References {
1317    /// The number standing for a test or phase id. The same id usually
1318    /// arrives many times in a row, so the last one is checked first.
1319    fn number(&mut self, name: &str, ids: &mut Interner) -> u32 {
1320        if let Some(number) = self.last
1321            && self.names[number as usize] == name
1322        {
1323            return number;
1324        }
1325        let number = match self.numbers.get(name) {
1326            Some(number) => *number,
1327            None => {
1328                let number = self.names.len() as u32;
1329                let id = ids.id(name);
1330                self.names.push(id.clone());
1331                self.numbers.insert(id, number);
1332                number
1333            }
1334        };
1335        self.last = Some(number);
1336        number
1337    }
1338
1339    fn add(&mut self, obligation: usize, name: &str, ids: &mut Interner) {
1340        let number = self.number(name, ids);
1341        if self.by_obligation.len() <= obligation {
1342            self.by_obligation.resize_with(obligation + 1, Vec::new);
1343        }
1344        let reached = &mut self.by_obligation[obligation];
1345        if reached.last() != Some(&number) {
1346            reached.push(number);
1347        }
1348    }
1349
1350    fn into_relation(self, hits: &HitIds) -> Relation {
1351        let mut order = (0..self.names.len()).collect::<Vec<_>>();
1352        order.sort_unstable_by(|left, right| self.names[*left].cmp(&self.names[*right]));
1353        let mut rank = vec![0_u32; self.names.len()];
1354        for (position, number) in order.into_iter().enumerate() {
1355            rank[number] = position as u32;
1356        }
1357        let mut relation = Relation {
1358            names: self.names,
1359            rank,
1360            by_hit: HashMap::new(),
1361            none: Reached::default(),
1362        };
1363        for (obligation, reached) in self.by_obligation.into_iter().enumerate() {
1364            if !reached.is_empty() {
1365                let reached = relation.reached(reached);
1366                relation
1367                    .by_hit
1368                    .insert(hits.names[obligation].to_string(), reached);
1369            }
1370        }
1371        relation
1372    }
1373}
1374
1375/// A `References` restored for the report: what reached each obligation,
1376/// and the table its numbers index.
1377struct Relation {
1378    names: Vec<Id>,
1379    /// Each number's position in id order.
1380    rank: Vec<u32>,
1381    by_hit: HashMap<String, Reached>,
1382    none: Reached,
1383}
1384
1385impl Relation {
1386    /// What reached obligation `id`: nothing, for one no test reached.
1387    fn of(&self, id: &str) -> &Reached {
1388        self.by_hit.get(id).unwrap_or(&self.none)
1389    }
1390
1391    /// Numbers of this relation as the ids they stand for, in id order and
1392    /// once each: the set the report writes.
1393    fn reached(&self, mut numbers: Vec<u32>) -> Reached {
1394        numbers.sort_unstable_by_key(|number| self.rank[*number as usize]);
1395        numbers.dedup();
1396        Reached {
1397            names: numbers
1398                .iter()
1399                .map(|number| self.names[*number as usize].clone())
1400                .collect(),
1401            numbers,
1402        }
1403    }
1404}
1405
1406/// Which tests or phases reached one obligation: their ids in id order, and
1407/// beside each the number its relation gave it.
1408#[derive(Default)]
1409struct Reached {
1410    names: Vec<Id>,
1411    numbers: Vec<u32>,
1412}
1413
1414/// What confidence reads of one test.
1415struct TestTraits<'t> {
1416    setup: bool,
1417    background: bool,
1418    runner: &'t str,
1419    kind: &'t str,
1420}
1421
1422impl<'t> TestTraits<'t> {
1423    fn of(test: &'t MutableTest) -> Self {
1424        Self {
1425            setup: test.role == "setup",
1426            background: test.role == "background",
1427            runner: &test.provenance.runner,
1428            kind: &test.provenance.kind,
1429        }
1430    }
1431}
1432
1433/// How a point, alternative, line or vector was reached, from the tests
1434/// and phases that reached it: read from per-number tables, since a run
1435/// mentions its tests millions of times and a lookup per mention hashed an
1436/// id each time.
1437fn confidence_from(
1438    tests: &Reached,
1439    phases: &Reached,
1440    explicit: &Reached,
1441    test_traits: &[Option<TestTraits<'_>>],
1442    phase_kinds: &[Option<&str>],
1443    explicit_kinds: &[Option<&str>],
1444) -> CoverageConfidence {
1445    let mut has_test = false;
1446    let mut setup_roles = true;
1447    let mut background_roles = true;
1448    let mut runners = BTreeSet::new();
1449    let mut kinds = BTreeSet::new();
1450    for number in &tests.numbers {
1451        if let Some(test) = &test_traits[*number as usize] {
1452            has_test = true;
1453            setup_roles &= test.setup;
1454            background_roles &= test.background;
1455            runners.insert(test.runner);
1456            kinds.insert(test.kind);
1457        }
1458    }
1459    let mut has_phase = false;
1460    let mut setup_phases = true;
1461    let mut background_phases = true;
1462    for number in &phases.numbers {
1463        if let Some(kind) = phase_kinds[*number as usize] {
1464            has_phase = true;
1465            setup_phases &= kind == "setup";
1466            background_phases &= kind == "background";
1467        }
1468    }
1469    let has_action = explicit
1470        .numbers
1471        .iter()
1472        .any(|number| explicit_kinds[*number as usize] == Some("action"));
1473    let level = if tests.names.is_empty() {
1474        "unexecuted"
1475    } else if has_action {
1476        "action"
1477    } else {
1478        "executed"
1479    };
1480    CoverageConfidence {
1481        level: level.into(),
1482        setup_only: if has_phase {
1483            setup_phases
1484        } else {
1485            has_test && setup_roles
1486        },
1487        background_only: if has_phase {
1488            background_phases
1489        } else {
1490            has_test && background_roles
1491        },
1492        asserted: false,
1493        tests: tests.names.clone(),
1494        asserted_tests: vec![],
1495        runners: runners.into_iter().map(str::to_owned).collect(),
1496        e2e: kinds.contains("e2e"),
1497        kinds: kinds.into_iter().map(str::to_owned).collect(),
1498    }
1499}
1500
1501pub fn create_coverage_view(
1502    manifest: &CoverageManifest,
1503    raw_results: &[RawTestResult],
1504    generated_at: &str,
1505) -> Result<CoverageView, ReportError> {
1506    create_coverage_view_with_model(
1507        manifest,
1508        &raw_results.iter().collect::<Vec<_>>(),
1509        generated_at,
1510        &javascript_coverage_model(),
1511        &mut Interner::default(),
1512    )
1513}
1514
1515fn create_coverage_view_with_model(
1516    manifest: &CoverageManifest,
1517    raw_results: &[&RawTestResult],
1518    generated_at: &str,
1519    coverage_model: &CoverageModelDeclaration,
1520    ids: &mut Interner,
1521) -> Result<CoverageView, ReportError> {
1522    let mut decision_metadata = manifest.decisions.clone();
1523    let decision_indexes = decision_metadata
1524        .iter()
1525        .enumerate()
1526        .map(|(index, meta)| (meta.id.clone(), index))
1527        .collect::<HashMap<_, _>>();
1528    let manifest_files = manifest
1529        .decisions
1530        .iter()
1531        .map(|meta| meta.file.clone())
1532        .chain(manifest.points.iter().map(|meta| meta.file.clone()))
1533        .chain(manifest.branches.iter().map(|meta| meta.file.clone()))
1534        .chain(
1535            manifest
1536                .scope
1537                .as_ref()
1538                .and_then(|scope| scope.get("entries"))
1539                .and_then(Value::as_array)
1540                .into_iter()
1541                .flatten()
1542                .filter(|entry| entry.get("status").and_then(Value::as_str) == Some("included"))
1543                .filter_map(|entry| entry.get("file").and_then(Value::as_str).map(str::to_owned)),
1544        )
1545        .collect::<BTreeSet<_>>();
1546    let mut vectors_by_decision = FastMap::<String, Vec<MutableObservation>>::default();
1547    let mut vector_indexes = FastMap::<String, FastMap<String, usize>>::default();
1548    // Numbers of `tests_by_hit`'s tests, as they arrive.
1549    let mut tests_by_decision = FastMap::<String, Vec<u32>>::default();
1550    let mut tests_by_hit = References::default();
1551    let mut hit_ids = HitIds::default();
1552    let mut tests_by_id = FastMap::<Id, MutableTest>::default();
1553    let mut test_order = Vec::<Id>::new();
1554    let mut phases_by_id = FastMap::<Id, MutablePhase>::default();
1555    let mut phases_by_hit = References::default();
1556    let mut explicit_phases_by_hit = References::default();
1557
1558    for raw in raw_results {
1559        let id = ids.id(raw_test_id(raw));
1560        if !tests_by_id.contains_key(&id) {
1561            test_order.push(id.clone());
1562            tests_by_id.insert(
1563                id.clone(),
1564                MutableTest {
1565                    id: id.to_string(),
1566                    name: raw.test.clone(),
1567                    file: raw.test_file.clone(),
1568                    title: raw.title.clone(),
1569                    retries: raw.retry.into_iter().collect(),
1570                    attempts: BTreeMap::new(),
1571                    unstarted: raw.status.as_deref() == Some("unstarted"),
1572                    runner_reported_flaky: raw.flaky,
1573                    provenance: raw.provenance.clone(),
1574                    role: raw.role.clone(),
1575                    attribution: raw.attribution.clone(),
1576                    hits: Vec::new(),
1577                    decisions: BTreeMap::new(),
1578                },
1579            );
1580        }
1581        let test = tests_by_id.get_mut(&id).expect("test was inserted");
1582        test.unstarted |= raw.status.as_deref() == Some("unstarted");
1583        if let Some(retry) = raw.retry {
1584            test.retries.insert(retry);
1585        }
1586        test.runner_reported_flaky |= raw.flaky;
1587        record_attempt(test, raw);
1588
1589        let mut ordered_phases = raw.phases.clone();
1590        ordered_phases.sort_by_key(|phase| phase.started_at_ms);
1591        for phase in &ordered_phases {
1592            phases_by_id.insert(
1593                ids.id(&phase.id),
1594                MutablePhase {
1595                    phase: phase.clone(),
1596                    test: id.clone(),
1597                    hits: Vec::new(),
1598                    decisions: BTreeMap::new(),
1599                    browser_events: 0,
1600                    server_events: 0,
1601                    explicit_events: 0,
1602                    inferred_events: 0,
1603                    explicit_browser_events: 0,
1604                    inferred_browser_events: 0,
1605                    explicit_server_events: 0,
1606                    inferred_server_events: 0,
1607                },
1608            );
1609        }
1610
1611        let correlate = |event: &RuntimeEvent| {
1612            event.phase_id.clone().or_else(|| {
1613                ordered_phases
1614                    .iter()
1615                    .take_while(|phase| phase.started_at_ms <= event.timestamp_ms)
1616                    .last()
1617                    .map(|phase| phase.id.clone())
1618            })
1619        };
1620
1621        let snapshots = raw
1622            .runtime
1623            .iter()
1624            .map(|snapshot| (snapshot, false))
1625            .chain(raw.browser.iter().map(|snapshot| (snapshot, true)));
1626        for (snapshot, browser) in snapshots {
1627            for decision in &snapshot.decisions {
1628                let Some(index) = decision_indexes.get(&decision.meta.id).copied() else {
1629                    if manifest_files.contains(&decision.meta.file) {
1630                        return Err(ReportError::InvalidServerRecord(format!(
1631                            "decision {} is absent from the frozen manifest",
1632                            decision.meta.id
1633                        )));
1634                    }
1635                    continue;
1636                };
1637                if decision_metadata[index] != decision.meta {
1638                    return Err(ReportError::InvalidServerRecord(format!(
1639                        "decision {} metadata differs from the frozen manifest",
1640                        decision.meta.id
1641                    )));
1642                }
1643                for vector in &decision.vectors {
1644                    let key = vector_key(vector);
1645                    let indexes = hash_slot(&mut vector_indexes, &decision.meta.id);
1646                    let observations = hash_slot(&mut vectors_by_decision, &decision.meta.id);
1647                    let observation_index = *indexes.entry(key).or_insert_with(|| {
1648                        observations.push(MutableObservation {
1649                            vector: vector.clone(),
1650                            tests: Vec::new(),
1651                            phases: Vec::new(),
1652                            explicit_phases: Vec::new(),
1653                        });
1654                        observations.len() - 1
1655                    });
1656                    observations[observation_index]
1657                        .tests
1658                        .push(tests_by_hit.number(&id, ids));
1659                    tree_slot(
1660                        &mut tests_by_id.get_mut(&id).expect("registered test").decisions,
1661                        &decision.meta.id,
1662                    )
1663                    .insert(vector);
1664                }
1665                if !decision.vectors.is_empty() {
1666                    hash_slot(&mut tests_by_decision, &decision.meta.id)
1667                        .push(tests_by_hit.number(&id, ids));
1668                }
1669            }
1670            let test_hits = &mut tests_by_id.get_mut(&id).expect("registered test").hits;
1671            for hit in &snapshot.hits {
1672                let hit = hit_ids.number(hit, ids);
1673                tests_by_hit.add(hit, &id, ids);
1674                test_hits.push(hit);
1675            }
1676            for event in &snapshot.events {
1677                let explicit = event.phase_id.is_some();
1678                let Some(phase_id) = correlate_event(event, &ordered_phases) else {
1679                    continue;
1680                };
1681                let Some(phase) = phases_by_id.get_mut(phase_id) else {
1682                    continue;
1683                };
1684                if event.environment == "browser" {
1685                    phase.browser_events += 1;
1686                    if explicit {
1687                        phase.explicit_browser_events += 1;
1688                    } else {
1689                        phase.inferred_browser_events += 1;
1690                    }
1691                } else {
1692                    phase.server_events += 1;
1693                    if explicit {
1694                        phase.explicit_server_events += 1;
1695                    } else {
1696                        phase.inferred_server_events += 1;
1697                    }
1698                }
1699                if explicit {
1700                    phase.explicit_events += 1;
1701                } else {
1702                    phase.inferred_events += 1;
1703                }
1704                if event.event_type == "hit" {
1705                    let hit = hit_ids.number(&event.id, ids);
1706                    phase.hits.push(hit);
1707                    phases_by_hit.add(hit, phase_id, ids);
1708                    if explicit {
1709                        explicit_phases_by_hit.add(hit, phase_id, ids);
1710                    }
1711                } else if event.event_type == "decision" {
1712                    let vector = event
1713                        .vector
1714                        .as_ref()
1715                        .ok_or_else(|| ReportError::InvalidEvent(event.id.clone()))?;
1716                    tree_slot(&mut phase.decisions, &event.id).insert(vector);
1717                    if let Some(index) = vector_indexes
1718                        .get(&event.id)
1719                        .and_then(|indexes| indexes.get(&vector_key(vector)))
1720                        .copied()
1721                        && let Some(observation) = vectors_by_decision
1722                            .get_mut(&event.id)
1723                            .and_then(|observations| observations.get_mut(index))
1724                    {
1725                        observation.phases.push(phases_by_hit.number(phase_id, ids));
1726                        if explicit {
1727                            observation
1728                                .explicit_phases
1729                                .push(explicit_phases_by_hit.number(phase_id, ids));
1730                        }
1731                    }
1732                } else {
1733                    return Err(ReportError::InvalidEvent(event.event_type.clone()));
1734                }
1735            }
1736            // A snapshot that names its phase: each hit and each decision
1737            // vector is an explicit event of it, counted and related exactly
1738            // as the event the frontend no longer writes would have been.
1739            if let Some(phase_id) = snapshot.phase_id.as_deref()
1740                && let Some(phase) = phases_by_id.get_mut(phase_id)
1741            {
1742                let observed = snapshot.hits.len()
1743                    + snapshot
1744                        .decisions
1745                        .iter()
1746                        .map(|decision| decision.vectors.len())
1747                        .sum::<usize>();
1748                if browser {
1749                    phase.browser_events += observed;
1750                    phase.explicit_browser_events += observed;
1751                } else {
1752                    phase.server_events += observed;
1753                    phase.explicit_server_events += observed;
1754                }
1755                phase.explicit_events += observed;
1756                for hit in &snapshot.hits {
1757                    let hit = hit_ids.number(hit, ids);
1758                    phase.hits.push(hit);
1759                    phases_by_hit.add(hit, phase_id, ids);
1760                    explicit_phases_by_hit.add(hit, phase_id, ids);
1761                }
1762                for decision in &snapshot.decisions {
1763                    let id = &decision.meta.id;
1764                    for vector in &decision.vectors {
1765                        tree_slot(&mut phase.decisions, id).insert(vector);
1766                        if let Some(index) = vector_indexes
1767                            .get(id)
1768                            .and_then(|indexes| indexes.get(&vector_key(vector)))
1769                            .copied()
1770                            && let Some(observation) = vectors_by_decision
1771                                .get_mut(id)
1772                                .and_then(|observations| observations.get_mut(index))
1773                        {
1774                            observation.phases.push(phases_by_hit.number(phase_id, ids));
1775                            observation
1776                                .explicit_phases
1777                                .push(explicit_phases_by_hit.number(phase_id, ids));
1778                        }
1779                    }
1780                }
1781            }
1782        }
1783
1784        for record in &raw.server {
1785            let (record_id, decision) = if record.record_type == "decision" {
1786                let meta = record
1787                    .meta
1788                    .as_ref()
1789                    .ok_or_else(|| ReportError::InvalidServerRecord("missing meta".into()))?;
1790                let vector = record
1791                    .vector
1792                    .as_ref()
1793                    .ok_or_else(|| ReportError::InvalidServerRecord("missing vector".into()))?;
1794                let Some(index) = decision_indexes.get(&meta.id).copied() else {
1795                    if manifest_files.contains(&meta.file) {
1796                        return Err(ReportError::InvalidServerRecord(format!(
1797                            "decision {} is absent from the frozen manifest",
1798                            meta.id
1799                        )));
1800                    }
1801                    continue;
1802                };
1803                if decision_metadata[index] != *meta {
1804                    return Err(ReportError::InvalidServerRecord(format!(
1805                        "decision {} metadata differs from the frozen manifest",
1806                        meta.id
1807                    )));
1808                }
1809                let key = vector_key(vector);
1810                let indexes = vector_indexes.entry(meta.id.clone()).or_default();
1811                let observations = vectors_by_decision.entry(meta.id.clone()).or_default();
1812                let index = *indexes.entry(key).or_insert_with(|| {
1813                    observations.push(MutableObservation {
1814                        vector: vector.clone(),
1815                        tests: Vec::new(),
1816                        phases: Vec::new(),
1817                        explicit_phases: Vec::new(),
1818                    });
1819                    observations.len() - 1
1820                });
1821                observations[index]
1822                    .tests
1823                    .push(tests_by_hit.number(&id, ids));
1824                tests_by_id
1825                    .get_mut(&id)
1826                    .expect("registered test")
1827                    .decisions
1828                    .entry(meta.id.clone())
1829                    .or_default()
1830                    .insert(vector);
1831                hash_slot(&mut tests_by_decision, &meta.id).push(tests_by_hit.number(&id, ids));
1832                (meta.id.clone(), Some(vector.clone()))
1833            } else if record.record_type == "hit" {
1834                let hit = record
1835                    .id
1836                    .as_ref()
1837                    .ok_or_else(|| ReportError::InvalidServerRecord("missing hit id".into()))?;
1838                let number = hit_ids.number(hit, ids);
1839                tests_by_hit.add(number, &id, ids);
1840                tests_by_id
1841                    .get_mut(&id)
1842                    .expect("registered test")
1843                    .hits
1844                    .push(number);
1845                (hit.clone(), None)
1846            } else {
1847                return Err(ReportError::InvalidServerRecord(record.record_type.clone()));
1848            };
1849            let Some(timestamp_ms) = record.timestamp_ms else {
1850                continue;
1851            };
1852            let event = RuntimeEvent {
1853                event_type: record.record_type.clone(),
1854                id: record_id,
1855                vector: decision,
1856                timestamp_ms,
1857                statement_id: record.statement_id.clone(),
1858                phase_id: record.phase_id.clone(),
1859                environment: "server".into(),
1860            };
1861            let explicit = event.phase_id.is_some();
1862            let phase_id = correlate(&event);
1863            let Some(phase_id) = phase_id else { continue };
1864            let Some(phase) = phases_by_id.get_mut(phase_id.as_str()) else {
1865                continue;
1866            };
1867            phase.server_events += 1;
1868            if explicit {
1869                phase.explicit_events += 1;
1870                phase.explicit_server_events += 1;
1871            } else {
1872                phase.inferred_events += 1;
1873                phase.inferred_server_events += 1;
1874            }
1875            if event.event_type == "hit" {
1876                let hit = hit_ids.number(&event.id, ids);
1877                phase.hits.push(hit);
1878                phases_by_hit.add(hit, &phase_id, ids);
1879                if explicit {
1880                    explicit_phases_by_hit.add(hit, &phase_id, ids);
1881                }
1882            } else if let Some(vector) = &event.vector {
1883                phase
1884                    .decisions
1885                    .entry(event.id.clone())
1886                    .or_default()
1887                    .insert(vector);
1888                if let Some(index) = vector_indexes
1889                    .get(&event.id)
1890                    .and_then(|indexes| indexes.get(&vector_key(vector)))
1891                    .copied()
1892                    && let Some(observation) = vectors_by_decision
1893                        .get_mut(&event.id)
1894                        .and_then(|observations| observations.get_mut(index))
1895                {
1896                    observation
1897                        .phases
1898                        .push(phases_by_hit.number(&phase_id, ids));
1899                    if explicit {
1900                        observation
1901                            .explicit_phases
1902                            .push(explicit_phases_by_hit.number(&phase_id, ids));
1903                    }
1904                }
1905            }
1906        }
1907    }
1908
1909    // Legacy confidence fields remain readable for archive compatibility.
1910    // Execution inside/before a passing assertion does not establish that
1911    // an assertion checks a statement. Only the agent-authored map awards it.
1912
1913    decision_metadata.sort_by(|left, right| {
1914        left.file
1915            .cmp(&right.file)
1916            .then(left.line.cmp(&right.line))
1917            .then(left.column.cmp(&right.column))
1918    });
1919    let tests_by_hit = tests_by_hit.into_relation(&hit_ids);
1920    let phases_by_hit = phases_by_hit.into_relation(&hit_ids);
1921    let explicit_phases_by_hit = explicit_phases_by_hit.into_relation(&hit_ids);
1922    let declined = manifest.unmeasured.iter().collect::<BTreeSet<_>>();
1923    // What confidence asks of each test and phase a relation numbers, looked
1924    // up once per number rather than once per mention: a run's points and
1925    // lines mention tests millions of times, and each lookup hashed an id.
1926    let test_traits = tests_by_hit
1927        .names
1928        .iter()
1929        .map(|name| tests_by_id.get(name).map(TestTraits::of))
1930        .collect::<Vec<_>>();
1931    let phase_kind = |relation: &Relation| {
1932        relation
1933            .names
1934            .iter()
1935            .map(|name| {
1936                phases_by_id
1937                    .get(name)
1938                    .map(|phase| phase.phase.kind.as_str())
1939            })
1940            .collect::<Vec<_>>()
1941    };
1942    let phase_kinds = phase_kind(&phases_by_hit);
1943    let explicit_kinds = phase_kind(&explicit_phases_by_hit);
1944    let confidence = |tests: &Reached, phases: &Reached, explicit: &Reached| {
1945        confidence_from(
1946            tests,
1947            phases,
1948            explicit,
1949            &test_traits,
1950            &phase_kinds,
1951            &explicit_kinds,
1952        )
1953    };
1954    let mut decisions = Vec::with_capacity(decision_metadata.len());
1955    for meta in decision_metadata {
1956        let mutable = vectors_by_decision.remove(&meta.id).unwrap_or_default();
1957        let mut observations = Vec::with_capacity(mutable.len());
1958        let mut observed_phases = Vec::new();
1959        let mut observed_explicit = Vec::new();
1960        for observation in mutable {
1961            let tests = tests_by_hit.reached(observation.tests);
1962            let phases = phases_by_hit.reached(observation.phases);
1963            let explicit = explicit_phases_by_hit.reached(observation.explicit_phases);
1964            observed_phases.extend(&phases.numbers);
1965            observed_explicit.extend(&explicit.numbers);
1966            observations.push(VectorObservation {
1967                vector: observation.vector,
1968                confidence: confidence(&tests, &phases, &explicit),
1969                tests: tests.names,
1970                phases: phases.names,
1971                explicit_phases: explicit.names,
1972            });
1973        }
1974        let vectors = observations
1975            .iter()
1976            .map(|observation| observation.vector.clone())
1977            .collect::<Vec<_>>();
1978        let witnesses =
1979            find_witnesses_for_conditions(&vectors, meta.conditions.len()).map_err(|error| {
1980                ReportError::DecisionAnalysis {
1981                    decision_id: meta.id.clone(),
1982                    error,
1983                }
1984            })?;
1985        let mut conditions = Vec::with_capacity(meta.conditions.len());
1986        for (index, source) in meta.conditions.iter().enumerate() {
1987            let witness = witnesses[index].map(|witness| {
1988                [
1989                    vectors[witness.first].clone(),
1990                    vectors[witness.second].clone(),
1991                ]
1992            });
1993            let witness_tests = witnesses[index].map(|witness| {
1994                [
1995                    observations[witness.first].tests.clone(),
1996                    observations[witness.second].tests.clone(),
1997                ]
1998            });
1999            let assertion_covered = false;
2000            conditions.push(ConditionResult {
2001                index,
2002                source: source.clone(),
2003                covered: witness.is_some(),
2004                assertion_covered,
2005                witness,
2006                witness_tests,
2007            });
2008        }
2009        let decision_tests =
2010            tests_by_hit.reached(tests_by_decision.remove(&meta.id).unwrap_or_default());
2011        let decision_confidence = confidence(
2012            &decision_tests,
2013            &phases_by_hit.reached(observed_phases),
2014            &explicit_phases_by_hit.reached(observed_explicit),
2015        );
2016        decisions.push(DecisionResult {
2017            executed: !vectors.is_empty(),
2018            covered: conditions.iter().all(|condition| condition.covered),
2019            meta,
2020            vectors,
2021            vector_observations: observations,
2022            conditions,
2023            tests: decision_tests.names,
2024            confidence: decision_confidence,
2025        });
2026    }
2027
2028    let points = manifest
2029        .points
2030        .iter()
2031        .cloned()
2032        .map(|meta| {
2033            let tests = tests_by_hit.of(&meta.id);
2034            let phases = phases_by_hit.of(&meta.id);
2035            let explicit = explicit_phases_by_hit.of(&meta.id);
2036            PointResult {
2037                measured: !declined.contains(&meta.id),
2038                covered: tests_by_hit.by_hit.contains_key(&meta.id),
2039                confidence: confidence(tests, phases, explicit),
2040                meta,
2041                tests: tests.names.clone(),
2042                phases: phases.names.clone(),
2043            }
2044        })
2045        .collect::<Vec<_>>();
2046
2047    let branches = manifest
2048        .branches
2049        .iter()
2050        .cloned()
2051        .map(|meta| {
2052            let alternatives = meta
2053                .alternatives
2054                .iter()
2055                .map(|alternative| {
2056                    let tests = tests_by_hit.of(&alternative.id);
2057                    let phases = phases_by_hit.of(&alternative.id);
2058                    let explicit = explicit_phases_by_hit.of(&alternative.id);
2059                    AlternativeResult {
2060                        id: alternative.id.clone(),
2061                        label: alternative.label.clone(),
2062                        covered: tests_by_hit.by_hit.contains_key(&alternative.id),
2063                        tests: tests.names.clone(),
2064                        phases: phases.names.clone(),
2065                        confidence: confidence(tests, phases, explicit),
2066                    }
2067                })
2068                .collect::<Vec<_>>();
2069            BranchResult {
2070                covered: alternatives.iter().all(|alternative| alternative.covered),
2071                meta,
2072                alternatives,
2073            }
2074        })
2075        .collect::<Vec<_>>();
2076
2077    // Obligations the frontend declined to measure leave the covered/uncovered
2078    // denominator entirely. Counting them as uncovered would report a
2079    // measurement gap as a coverage gap — a wrong number, and wrong numbers get
2080    // trusted. They are reported separately instead, alongside the share of
2081    // obligations that were measured exactly.
2082    // Lines are folded from every point, declined ones included, so a file
2083    // Supercov could not measure still has addressable lines to report a
2084    // limitation against. Only what was measured decides the line's state and
2085    // whether it counts. A line gathers its points' test and phase numbers,
2086    // which sort and deduplicate as numbers.
2087    let mut line_aggregates = BTreeMap::<SourceLine, LineAggregate>::new();
2088    for point in &points {
2089        let aggregate = line_aggregates
2090            .entry(SourceLine {
2091                file: ids.id(&point.meta.file),
2092                line: point.meta.line,
2093            })
2094            .or_default();
2095        if declined.contains(&point.meta.id) {
2096            continue;
2097        }
2098        aggregate.measured = true;
2099        aggregate.covered |= point.covered;
2100        aggregate
2101            .tests
2102            .extend(&tests_by_hit.of(&point.meta.id).numbers);
2103        aggregate
2104            .phases
2105            .extend(&phases_by_hit.of(&point.meta.id).numbers);
2106        aggregate
2107            .explicit_phases
2108            .extend(&explicit_phases_by_hit.of(&point.meta.id).numbers);
2109    }
2110    let lines = line_aggregates
2111        .into_iter()
2112        .map(|(location, aggregate)| {
2113            let LineAggregate {
2114                covered,
2115                measured,
2116                tests,
2117                phases,
2118                explicit_phases,
2119            } = aggregate;
2120            let tests = tests_by_hit.reached(tests);
2121            let phases = phases_by_hit.reached(phases);
2122            let explicit = explicit_phases_by_hit.reached(explicit_phases);
2123            let traits = tests
2124                .numbers
2125                .iter()
2126                .filter_map(|number| test_traits[*number as usize].as_ref())
2127                .collect::<Vec<_>>();
2128            let runners = traits
2129                .iter()
2130                .map(|traits| traits.runner)
2131                .collect::<BTreeSet<_>>();
2132            let kinds = traits
2133                .iter()
2134                .map(|traits| traits.kind)
2135                .collect::<BTreeSet<_>>();
2136            LineResult {
2137                file: location.file.to_string(),
2138                line: location.line,
2139                covered,
2140                measured,
2141                confidence: confidence(&tests, &phases, &explicit),
2142                tests: tests.names,
2143                runners: runners.iter().map(|runner| (*runner).to_owned()).collect(),
2144                exclusive_kind: (kinds.len() == 1).then(|| (*kinds.first().unwrap()).to_owned()),
2145                phases: phases.names,
2146                kinds: kinds.iter().map(|kind| (*kind).to_owned()).collect(),
2147            }
2148        })
2149        .collect::<Vec<_>>();
2150    drop(test_traits);
2151
2152    // Where each hit sits, by hit number, as (file rank, line): a test's or
2153    // phase's lines sort and deduplicate as numbers, in the order the
2154    // `SourceLine`s they stand for sort in.
2155    let hit_ranks = hit_ids.ranks();
2156    let mut point_files = manifest
2157        .points
2158        .iter()
2159        .map(|point| ids.id(&point.file))
2160        .collect::<Vec<_>>();
2161    point_files.sort_unstable();
2162    point_files.dedup();
2163    let mut hit_locations = vec![None; hit_ids.names.len()];
2164    for point in &manifest.points {
2165        if let Some(number) = hit_ids.numbers.get(point.id.as_str()) {
2166            let file = point_files
2167                .binary_search_by(|file| file.as_str().cmp(&point.file))
2168                .expect("every point file is listed") as u32;
2169            hit_locations[*number] = Some((file, point.line));
2170        }
2171    }
2172    let lines_of = |hits: &[usize]| {
2173        let mut located = hits
2174            .iter()
2175            .filter_map(|hit| hit_locations[*hit])
2176            .collect::<Vec<_>>();
2177        located.sort_unstable();
2178        located.dedup();
2179        located
2180            .into_iter()
2181            .map(|(file, line)| SourceLine {
2182                file: point_files[file as usize].clone(),
2183                line,
2184            })
2185            .collect::<Vec<_>>()
2186    };
2187    test_order.sort_by(|left, right| tests_by_id[left].name.cmp(&tests_by_id[right].name));
2188    let tests = test_order
2189        .into_iter()
2190        .map(|id| {
2191            let test = tests_by_id.remove(&id).expect("test order references test");
2192            let outcome = test_outcome(&test);
2193            let lines = lines_of(&test.hits);
2194            let hits = hit_ids.sorted_by(test.hits, &hit_ranks);
2195            TestCoverageResult {
2196                id: test.id,
2197                name: test.name,
2198                file: test.file,
2199                title: test.title,
2200                retries: test.retries.into_iter().collect(),
2201                attempts: test.attempts.into_values().collect(),
2202                outcome,
2203                provenance: test.provenance,
2204                role: test.role,
2205                attribution: test.attribution,
2206                hits,
2207                decisions: test
2208                    .decisions
2209                    .into_iter()
2210                    .map(|(id, vectors)| TestDecisionResult {
2211                        id,
2212                        vectors: vectors.values,
2213                    })
2214                    .collect(),
2215                lines,
2216            }
2217        })
2218        .collect::<Vec<_>>();
2219
2220    let mut test_files = BTreeMap::<
2221        String,
2222        (
2223            BTreeSet<String>,
2224            BTreeSet<String>,
2225            BTreeSet<String>,
2226            BTreeSet<SourceLine>,
2227        ),
2228    >::new();
2229    for test in &tests {
2230        let aggregate = test_files
2231            .entry(
2232                test.file
2233                    .clone()
2234                    .unwrap_or_else(|| "(unknown test file)".into()),
2235            )
2236            .or_default();
2237        aggregate.0.insert(test.id.clone());
2238        aggregate.1.insert(test.provenance.runner.clone());
2239        aggregate.2.insert(test.provenance.kind.clone());
2240        aggregate.3.extend(test.lines.iter().cloned());
2241    }
2242    let test_files = test_files
2243        .into_iter()
2244        .map(|(file, (tests, runners, kinds, lines))| TestFileResult {
2245            file,
2246            tests: sorted(&tests),
2247            runners: sorted(&runners),
2248            kinds: sorted(&kinds),
2249            lines: lines.into_iter().collect(),
2250        })
2251        .collect::<Vec<_>>();
2252
2253    let mut phases = phases_by_id.into_values().collect::<Vec<_>>();
2254    phases.sort_by(|left, right| {
2255        left.phase
2256            .started_at_ms
2257            .cmp(&right.phase.started_at_ms)
2258            .then(left.phase.id.cmp(&right.phase.id))
2259    });
2260    let phases = phases
2261        .into_iter()
2262        .map(|phase| {
2263            let lines = lines_of(&phase.hits);
2264            let hits = hit_ids.sorted_by(phase.hits, &hit_ranks);
2265            PhaseResult {
2266                phase: phase.phase,
2267                test: phase.test,
2268                hits,
2269                decisions: phase
2270                    .decisions
2271                    .into_iter()
2272                    .map(|(id, vectors)| TestDecisionResult {
2273                        id,
2274                        vectors: vectors.values,
2275                    })
2276                    .collect(),
2277                lines,
2278                browser_events: phase.browser_events,
2279                server_events: phase.server_events,
2280                explicit_events: phase.explicit_events,
2281                inferred_events: phase.inferred_events,
2282                explicit_browser_events: phase.explicit_browser_events,
2283                inferred_browser_events: phase.inferred_browser_events,
2284                explicit_server_events: phase.explicit_server_events,
2285                inferred_server_events: phase.inferred_server_events,
2286            }
2287        })
2288        .collect::<Vec<_>>();
2289
2290    let total_obligations = decisions.len() + points.len() + branches.len();
2291    let (decisions, points, branches) = if declined.is_empty() {
2292        (decisions, points, branches)
2293    } else {
2294        (
2295            decisions
2296                .into_iter()
2297                .filter(|result| !declined.contains(&result.meta.id))
2298                .collect::<Vec<_>>(),
2299            points
2300                .into_iter()
2301                .filter(|result| !declined.contains(&result.meta.id))
2302                .collect::<Vec<_>>(),
2303            branches
2304                .into_iter()
2305                .filter(|result| !declined.contains(&result.meta.id))
2306                .collect::<Vec<_>>(),
2307        )
2308    };
2309    let measured_obligations = decisions.len() + points.len() + branches.len();
2310    let mut summary = summary_for_results(&decisions, &points, &branches, &lines, None)?;
2311    if total_obligations > measured_obligations {
2312        summary.unmeasured_obligations = Some(total_obligations - measured_obligations);
2313        summary.exact_fraction_pct = Some(if total_obligations == 0 {
2314            100.0
2315        } else {
2316            (measured_obligations as f64) * 100.0 / (total_obligations as f64)
2317        });
2318    }
2319    // A limitation that says `"blocking": false` is a declared boundary of
2320    // the denominator -- a macro the compiler expands, a const context no
2321    // probe can run in -- not a failure to measure what is inside it. Only a
2322    // blocking one makes the run incomplete. A limitation that says nothing
2323    // is blocking, so a frontend that has not been taught the difference
2324    // keeps its behaviour.
2325    if manifest.limitations.iter().any(blocking_limitation) {
2326        summary.coverage_complete = false;
2327        summary.completeness_blocked = Some(true);
2328    }
2329
2330    let dimension_coverage = |field: &str| -> Result<Vec<DimensionCoverage>, ReportError> {
2331        let values = tests
2332            .iter()
2333            .map(|test| {
2334                if field == "kind" {
2335                    test.provenance.kind.clone()
2336                } else {
2337                    test.provenance.runner.clone()
2338                }
2339            })
2340            .collect::<BTreeSet<_>>();
2341        values
2342            .into_iter()
2343            .map(|value| {
2344                let in_dimension = |test: &&TestCoverageResult| {
2345                    if field == "kind" {
2346                        test.provenance.kind == value
2347                    } else {
2348                        test.provenance.runner == value
2349                    }
2350                };
2351                // Coverage is summarised over the tests that have coverage of
2352                // their own. A Go test that called `t.Parallel()` reached real
2353                // code and contributes no hits, because nothing can say which
2354                // code -- so counting it here reported a package covered
2355                // entirely by parallel tests as covered 0%, which is a wrong
2356                // number rather than a missing one.
2357                let selected = tests
2358                    .iter()
2359                    .filter(in_dimension)
2360                    .filter(|test| coverage_is_its_own(&test.attribution))
2361                    .map(|test| test.id.clone())
2362                    .collect::<BTreeSet<_>>();
2363                let counted = tests
2364                    .iter()
2365                    .filter(in_dimension)
2366                    .map(|test| test.id.clone())
2367                    .collect::<BTreeSet<_>>();
2368                Ok(DimensionCoverage {
2369                    kind: (field == "kind").then(|| value.clone()),
2370                    runner: (field == "runner").then(|| value.clone()),
2371                    // Counted whether or not anything could be attributed to
2372                    // them: they ran.
2373                    tests: tests
2374                        .iter()
2375                        .filter(|test| counted.contains(&test.id) && test.role == "test")
2376                        .count(),
2377                    setups: tests
2378                        .iter()
2379                        .filter(|test| counted.contains(&test.id) && test.role == "setup")
2380                        .count(),
2381                    attributed: tests
2382                        .iter()
2383                        .filter(|test| selected.contains(&test.id) && test.role == "test")
2384                        .count(),
2385                    summary: summary_for_results(
2386                        &decisions,
2387                        &points,
2388                        &branches,
2389                        &lines,
2390                        Some(&selected),
2391                    )?,
2392                })
2393            })
2394            .collect()
2395    };
2396
2397    Ok(CoverageView {
2398        generated_at: generated_at.into(),
2399        variant: coverage_model.variant.clone(),
2400        scope: manifest.scope.clone(),
2401        model: CoverageModel {
2402            language: coverage_model.language.clone(),
2403            name: coverage_model.name.clone(),
2404            completeness_meaning: coverage_model.completeness_meaning.clone(),
2405            measured: coverage_model.measured.clone(),
2406            not_measured: coverage_model.not_measured.clone(),
2407        },
2408        integrity: None,
2409        limitations: manifest.limitations.clone(),
2410        transport: None,
2411        summary,
2412        coverage_by_kind: dimension_coverage("kind")?,
2413        coverage_by_runner: dimension_coverage("runner")?,
2414        decisions,
2415        points,
2416        branches,
2417        tests,
2418        test_files,
2419        phases,
2420        lines,
2421    })
2422}
2423
2424pub fn analyze_coverage_results(
2425    request: &CoverageReportRequest,
2426) -> Result<CoverageReport, ReportError> {
2427    if let Some(scope) = request
2428        .raw_results
2429        .iter()
2430        .filter_map(|raw| raw.scope.as_ref())
2431        .find(|scope| scope.run_id != request.run_id)
2432    {
2433        return Err(ReportError::ScopeMismatch {
2434            expected: request.run_id.clone(),
2435            actual: scope.run_id.clone(),
2436        });
2437    }
2438    if request.raw_results.is_empty() {
2439        return Err(ReportError::NoEvidence(request.run_id.clone()));
2440    }
2441    let default_model = javascript_coverage_model();
2442    let coverage_model = request.coverage_model.as_ref().unwrap_or(&default_model);
2443    PersistedCoverageModel::from_declaration(coverage_model).map_err(|reason| {
2444        ReportError::InvalidJson {
2445            path: "coverage-model.json".into(),
2446            reason: reason.into(),
2447        }
2448    })?;
2449    // The three views are built side by side: each reads the same evidence
2450    // and nothing the others write. Each holds one copy of each id it names.
2451    // They do not share copies: two threads cloning the same ids contend on
2452    // their counts, which made each view twice as slow as building it alone.
2453    let all = request.raw_results.iter().collect::<Vec<_>>();
2454    let passing = passing_coverage_results(&request.raw_results);
2455    let failing = failed_coverage_results(&request.raw_results);
2456    let build = |results: &[&RawTestResult]| {
2457        create_coverage_view_with_model(
2458            &request.manifest,
2459            results,
2460            &request.generated_at,
2461            coverage_model,
2462            &mut Interner::default(),
2463        )
2464    };
2465    let (view, passed, failed) = std::thread::scope(|scope| {
2466        let passed = scope.spawn(|| build(&passing));
2467        let failed = scope.spawn(|| build(&failing));
2468        let view = build(&all);
2469        (
2470            view,
2471            passed
2472                .join()
2473                .unwrap_or_else(|panic| std::panic::resume_unwind(panic)),
2474            failed
2475                .join()
2476                .unwrap_or_else(|panic| std::panic::resume_unwind(panic)),
2477        )
2478    });
2479    let (view, passed, failed) = (view?, passed?, failed?);
2480    let execution = match request.test_exit_code {
2481        ExitCodeInput::Missing => None,
2482        ExitCodeInput::Present(test_exit_code) => Some(ExecutionResult {
2483            valid: test_exit_code == Some(0),
2484            test_exit_code,
2485        }),
2486    };
2487    let mut view = view;
2488    let mut passed = passed;
2489    let mut failed = failed;
2490    if let Some(integrity) = &request.integrity {
2491        view.integrity = Some(integrity.clone());
2492        passed.integrity = Some(integrity.clone());
2493        failed.integrity = Some(integrity.clone());
2494    }
2495    Ok(CoverageReport {
2496        view,
2497        execution,
2498        filters: CoverageFilters { passed, failed },
2499    })
2500}
2501
2502fn parse_entry<T: for<'de> Deserialize<'de>>(
2503    entry: &EvidenceArchiveEntry,
2504) -> Result<T, ReportError> {
2505    serde_json::from_slice(&entry.contents).map_err(|error| ReportError::InvalidJson {
2506        path: entry.path.clone(),
2507        reason: error.to_string(),
2508    })
2509}
2510
2511fn parse_json_lines<'a, T: for<'de> Deserialize<'de>>(
2512    entries: impl Iterator<Item = &'a EvidenceArchiveEntry>,
2513) -> Result<Vec<T>, ReportError> {
2514    let mut records = Vec::new();
2515    for entry in entries {
2516        let Some(contents) = entry.contents.strip_suffix(b"\n") else {
2517            return Err(ReportError::InvalidJson {
2518                path: entry.path.clone(),
2519                reason: "recognized JSONL evidence must end with a newline".into(),
2520            });
2521        };
2522        if contents.is_empty() {
2523            return Err(ReportError::InvalidJson {
2524                path: entry.path.clone(),
2525                reason: "recognized JSONL evidence must contain at least one record".into(),
2526            });
2527        }
2528        for (index, line) in contents.split(|byte| *byte == b'\n').enumerate() {
2529            if line.is_empty() {
2530                return Err(ReportError::InvalidJson {
2531                    path: entry.path.clone(),
2532                    reason: format!("blank JSONL record at line {}", index + 1),
2533                });
2534            }
2535            records.push(serde_json::from_slice(line).map_err(|error| {
2536                ReportError::InvalidJson {
2537                    path: entry.path.clone(),
2538                    reason: format!("invalid JSONL record at line {}: {error}", index + 1),
2539                }
2540            })?);
2541        }
2542    }
2543    Ok(records)
2544}
2545
2546/// Server evidence is appended by application processes Supercov does not
2547/// control — including pool VMs restored from one snapshot, whose clones can
2548/// tear a shared shard. A torn line is one lost record, not a lost run: it is
2549/// skipped and counted, and the report says so through the existing
2550/// CORRUPT_EVIDENCE_RECORDS diagnostic and the blocking-limitation total.
2551struct TolerantJsonLines<T> {
2552    records: Vec<T>,
2553    corrupt_records: usize,
2554    corrupt_files: usize,
2555}
2556
2557fn parse_server_json_lines<'a, T: for<'de> Deserialize<'de>>(
2558    entries: impl Iterator<Item = &'a EvidenceArchiveEntry>,
2559) -> TolerantJsonLines<T> {
2560    let mut parsed = TolerantJsonLines {
2561        records: Vec::new(),
2562        corrupt_records: 0,
2563        corrupt_files: 0,
2564    };
2565    for entry in entries {
2566        let mut corrupt_here = 0;
2567        for line in entry.contents.split(|byte| *byte == b'\n') {
2568            if line.is_empty() {
2569                continue;
2570            }
2571            match serde_json::from_slice(line) {
2572                Ok(record) => parsed.records.push(record),
2573                Err(_) => corrupt_here += 1,
2574            }
2575        }
2576        if corrupt_here > 0 {
2577            parsed.corrupt_records += corrupt_here;
2578            parsed.corrupt_files += 1;
2579        }
2580    }
2581    parsed
2582}
2583
2584fn is_mcdc_result(path: &str) -> bool {
2585    path == "mcdc.json" || path.ends_with("/mcdc.json")
2586}
2587
2588fn is_mcdc_journal(path: &str) -> bool {
2589    path == "mcdc.jsonl" || path.ends_with(".mcdc.jsonl")
2590}
2591
2592fn validate_rust_compiler_scope(manifest: &CoverageManifest) -> Result<(), ReportError> {
2593    let scope = manifest
2594        .scope
2595        .as_ref()
2596        .and_then(Value::as_object)
2597        .ok_or_else(|| ReportError::InvalidArchive("missing Rust compiler source scope".into()))?;
2598    let mut expected = BTreeSet::from([
2599        "crate",
2600        "language",
2601        "measurementComplete",
2602        "model",
2603        "sourceFingerprint",
2604    ]);
2605    // Historical experimental metadata is ignored, never used for credit.
2606    if scope.contains_key("assertionIdentities") {
2607        expected.insert("assertionIdentities");
2608    }
2609    if scope.keys().map(String::as_str).collect::<BTreeSet<_>>() != expected
2610        || scope.get("language").and_then(Value::as_str) != Some("rust")
2611        || scope.get("model").and_then(Value::as_str) != Some("rust-source-v1")
2612        || scope
2613            .get("crate")
2614            .and_then(Value::as_str)
2615            .is_none_or(str::is_empty)
2616        || !scope
2617            .get("measurementComplete")
2618            .is_some_and(Value::is_boolean)
2619    {
2620        return Err(ReportError::InvalidArchive(
2621            "malformed Rust compiler source scope".into(),
2622        ));
2623    }
2624    let fingerprint = scope
2625        .get("sourceFingerprint")
2626        .and_then(Value::as_object)
2627        .ok_or_else(|| {
2628            ReportError::InvalidArchive("missing Rust compiler source fingerprint".into())
2629        })?;
2630    let expected_fingerprint = BTreeSet::from(["algorithm", "digest", "files", "generatedFiles"]);
2631    let digest = fingerprint.get("digest").and_then(Value::as_str);
2632    let files = fingerprint.get("files").and_then(Value::as_u64);
2633    let generated = fingerprint.get("generatedFiles").and_then(Value::as_u64);
2634    if fingerprint
2635        .keys()
2636        .map(String::as_str)
2637        .collect::<BTreeSet<_>>()
2638        != expected_fingerprint
2639        || fingerprint.get("algorithm").and_then(Value::as_str) != Some("sha256")
2640        || !digest.is_some_and(|digest| {
2641            digest.len() == 64
2642                && digest
2643                    .bytes()
2644                    .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
2645        })
2646        || files.is_none_or(|files| files == 0)
2647        || generated
2648            .zip(files)
2649            .is_none_or(|(generated, files)| generated > files)
2650    {
2651        return Err(ReportError::InvalidArchive(
2652            "malformed Rust compiler source fingerprint".into(),
2653        ));
2654    }
2655    Ok(())
2656}
2657
2658pub fn analyze_coverage_archive(
2659    request: &ArchiveReportRequest,
2660) -> Result<CoverageReport, ReportError> {
2661    let entries = read_archive(Path::new(&request.archive_path))
2662        .map_err(|error| ReportError::InvalidArchive(error.to_string()))?;
2663    let manifest = entries
2664        .iter()
2665        .find(|entry| entry.path == "manifest.json")
2666        .ok_or(ReportError::MissingManifest)
2667        .and_then(parse_entry::<CoverageManifest>)?;
2668    let mut raw_results = entries
2669        .iter()
2670        .filter(|entry| is_mcdc_result(&entry.path))
2671        .map(parse_entry::<RawTestResult>)
2672        .collect::<Result<Vec<_>, _>>()?;
2673    let journal_results = parse_json_lines::<RawTestResult>(
2674        entries.iter().filter(|entry| is_mcdc_journal(&entry.path)),
2675    )?;
2676    raw_results.extend(journal_results);
2677
2678    let scoped = parse_server_json_lines::<ServerRecord>(entries.iter().filter(|entry| {
2679        entry.path.starts_with("server/")
2680            && !entry.path.starts_with("server/background/")
2681            && entry.path.ends_with(".jsonl")
2682    }));
2683    let scoped_records = scoped.records;
2684    for record in &scoped_records {
2685        let Some(scope) = &record.scope else { continue };
2686        let Some(raw) = raw_results
2687            .iter_mut()
2688            .find(|raw| raw.scope.as_ref() == Some(scope))
2689        else {
2690            continue;
2691        };
2692        if !raw.server.contains(record) {
2693            raw.server.push(record.clone());
2694        }
2695    }
2696
2697    let background = parse_server_json_lines::<ServerRecord>(entries.iter().filter(|entry| {
2698        entry.path.starts_with("server/background/") && entry.path.ends_with(".jsonl")
2699    }));
2700    let background_records = background.records;
2701    if !background_records.is_empty() {
2702        raw_results.push(RawTestResult {
2703            test_id: Some(format!("background:{}", request.run_id)),
2704            scope: None,
2705            test: "Background / unattributed".into(),
2706            test_file: None,
2707            title: Some("Background / unattributed".into()),
2708            retry: None,
2709            status: Some("unknown".into()),
2710            expected_status: None,
2711            flaky: false,
2712            provenance: TestProvenance {
2713                runner: "background".into(),
2714                kind: "background".into(),
2715                project: None,
2716                source: "explicit".into(),
2717            },
2718            role: "background".into(),
2719            attribution: ATTRIBUTION_EXACT.into(),
2720            phases: vec![],
2721            runtime: vec![],
2722            browser: vec![],
2723            server: background_records.clone(),
2724        });
2725    }
2726
2727    // Execution traces are appended by the launch observer inside application
2728    // processes, so they share the clone hazard of server evidence.
2729    let execution =
2730        parse_server_json_lines::<ExecutionTraceEvent>(entries.iter().filter(|entry| {
2731            entry.path.starts_with("execution.") && entry.path.ends_with(".jsonl")
2732        }));
2733    let execution_events = execution.records;
2734    let count_event = |name: &str| {
2735        execution_events
2736            .iter()
2737            .filter(|event| event.kind() == name)
2738            .count()
2739    };
2740    let transport = TransportStats {
2741        processes: count_event("process"),
2742        child_launches: count_event("child-launch"),
2743        remote_launches: count_event("remote-launch"),
2744        workspace_capabilities: count_event("workspace-capability"),
2745        scoped_server_records: scoped_records.len(),
2746        background_server_records: background_records.len(),
2747        corrupt_records: scoped.corrupt_records
2748            + background.corrupt_records
2749            + execution.corrupt_records,
2750        corrupt_files: scoped.corrupt_files + background.corrupt_files + execution.corrupt_files,
2751    };
2752    let frontend = entries
2753        .iter()
2754        .find(|entry| entry.path == "frontend.json")
2755        .ok_or_else(|| ReportError::InvalidArchive("missing frontend.json".into()))
2756        .and_then(parse_entry::<FrontendRunDeclaration>)?;
2757    let persisted = entries
2758        .iter()
2759        .find(|entry| entry.path == "coverage-model.json")
2760        .ok_or_else(|| ReportError::InvalidArchive("missing coverage-model.json".into()))
2761        .and_then(parse_entry::<PersistedCoverageModel>)?;
2762    let coverage_model =
2763        persisted
2764            .into_declaration()
2765            .map_err(|reason| ReportError::InvalidJson {
2766                path: "coverage-model.json".into(),
2767                reason: reason.into(),
2768            })?;
2769    if frontend.language != coverage_model.language {
2770        return Err(ReportError::InvalidArchive(format!(
2771            "frontend language {} differs from coverage model language {}",
2772            frontend.language, coverage_model.language
2773        )));
2774    }
2775    if frontend.frontend_version == "rust-compiler-v1" {
2776        validate_rust_compiler_scope(&manifest)?;
2777    }
2778    let normalized = CoverageReportRequest {
2779        run_id: request.run_id.clone(),
2780        manifest,
2781        raw_results,
2782        generated_at: request.generated_at.clone(),
2783        coverage_model: Some(coverage_model),
2784        integrity: request.integrity.clone(),
2785        test_exit_code: request.test_exit_code.clone(),
2786    };
2787    // Everything is parsed out of the archive's bytes -- 385 MB on a
2788    // 3,900-test run -- and nothing reads them again, so they need not stay
2789    // resident while the views are built beside what was parsed from them.
2790    drop(entries);
2791    analyze_frontend_request(&frontend, &normalized, transport)
2792}
2793
2794/// The analysis of a frontend run, from its request as an archive holds it:
2795/// what `analyze_coverage_archive` does once it has parsed the archive, and
2796/// what a run that still holds the request it archived calls instead of
2797/// reading the archive back.
2798pub fn analyze_frontend_request(
2799    frontend: &FrontendRunDeclaration,
2800    normalized: &CoverageReportRequest,
2801    transport: TransportStats,
2802) -> Result<CoverageReport, ReportError> {
2803    let mut report = crate::frontend_protocol::analyze_frontend_results(frontend, normalized)
2804        .map_err(|error| ReportError::InvalidArchive(error.to_string()))?;
2805    report.view.transport = Some(transport.clone());
2806    report.filters.passed.transport = Some(transport.clone());
2807    report.filters.failed.transport = Some(transport);
2808    Ok(report)
2809}
2810
2811impl TransportStats {
2812    /// An archive with no launch-observer or server evidence: every frontend
2813    /// but JavaScript's.
2814    pub fn none() -> Self {
2815        Self {
2816            processes: 0,
2817            child_launches: 0,
2818            remote_launches: 0,
2819            workspace_capabilities: 0,
2820            scoped_server_records: 0,
2821            background_server_records: 0,
2822            corrupt_records: 0,
2823            corrupt_files: 0,
2824        }
2825    }
2826}
2827
2828#[cfg(test)]
2829mod tests {
2830    use std::{
2831        fs,
2832        sync::atomic::{AtomicU64, Ordering},
2833        time::SystemTime,
2834    };
2835
2836    use crate::evidence_archive::{EvidenceArchiveEntry, write_archive};
2837
2838    use super::*;
2839
2840    #[test]
2841    fn a_view_holds_one_copy_of_each_id_it_names() {
2842        // What made analysis fit in memory: a view names each test at every
2843        // point, line and confidence it reached, and must share one copy of
2844        // the name rather than hold one per mention. A change that copies ids
2845        // again fails here before it shows up as gigabytes. The full and the
2846        // passing view are built side by side, each with its own copy.
2847        let root = std::env::temp_dir().join(format!(
2848            "supercov-shared-ids-{}-{}",
2849            std::process::id(),
2850            std::time::SystemTime::now()
2851                .duration_since(std::time::UNIX_EPOCH)
2852                .unwrap()
2853                .as_nanos()
2854        ));
2855        let directory = crate::run_store::create_analyzable_test_run(&root, "shared");
2856        let run = crate::run_store::discover_runs(&root)
2857            .unwrap()
2858            .runs
2859            .remove(0);
2860        let report = crate::run_store::analyze_stored_run(&run).unwrap();
2861        let _ = directory;
2862        for view in [&report.view, &report.filters.passed] {
2863            let mentions = view
2864                .points
2865                .iter()
2866                .flat_map(|point| point.tests.iter().chain(&point.confidence.tests))
2867                .chain(view.lines.iter().flat_map(|line| &line.tests))
2868                .collect::<Vec<_>>();
2869            assert!(
2870                mentions.len() >= 2,
2871                "the fixture names its test in several places"
2872            );
2873            let first = mentions[0];
2874            for mention in &mentions {
2875                assert_eq!(*mention, first);
2876                assert!(
2877                    mention.same_allocation(first),
2878                    "every mention shares one copy"
2879                );
2880            }
2881        }
2882        fs::remove_dir_all(root).unwrap();
2883    }
2884
2885    #[test]
2886    fn references_numbered_as_they_arrive_come_out_as_the_sets_inserted_directly() {
2887        // Out of order, repeated, interleaved across obligations, and sharing
2888        // long prefixes, as test and phase ids do.
2889        let arrivals = [
2890            ("py:statement:b", "h11/tests/test_x.py::test_b[2-50]"),
2891            ("py:statement:a", "h11/tests/test_x.py::test_b[2-50]"),
2892            ("py:statement:b", "h11/tests/test_x.py::test_a[1-50]"),
2893            ("py:statement:b", "h11/tests/test_x.py::test_b[2-50]"),
2894            ("py:statement:a", "h11/tests/test_x.py::test_b[10-50]"),
2895            ("py:statement:b", "h11/tests/test_x.py::test_a[1-50]"),
2896            ("py:statement:c", "python-phase:9"),
2897            ("py:statement:c", "python-phase:10"),
2898            ("py:statement:b", "h11/tests/test_x.py::test_b[2-50]"),
2899        ];
2900        let mut ids = Interner::default();
2901        let mut hits = HitIds::default();
2902        let mut numbered = References::default();
2903        // A hit known to another relation must not become covered here.
2904        hits.number("unused", &mut ids);
2905        let mut direct = HashMap::<String, BTreeSet<String>>::new();
2906        for (obligation, name) in arrivals {
2907            let hit = hits.number(obligation, &mut ids);
2908            numbered.add(hit, name, &mut ids);
2909            direct
2910                .entry(obligation.to_owned())
2911                .or_default()
2912                .insert(name.to_owned());
2913        }
2914        let numbered = numbered
2915            .into_relation(&hits)
2916            .by_hit
2917            .into_iter()
2918            .map(|(obligation, reached)| {
2919                let names = reached
2920                    .names
2921                    .into_iter()
2922                    .map(String::from)
2923                    .collect::<Vec<_>>();
2924                let mut sorted_names = names.clone();
2925                sorted_names.sort();
2926                sorted_names.dedup();
2927                assert_eq!(
2928                    names, sorted_names,
2929                    "a relation lists its ids once, in order"
2930                );
2931                (obligation, names.into_iter().collect())
2932            })
2933            .collect::<HashMap<_, BTreeSet<String>>>();
2934        assert_eq!(numbered, direct);
2935        assert_eq!(
2936            hits.sorted((0..hits.names.len()).collect()),
2937            [
2938                "py:statement:a",
2939                "py:statement:b",
2940                "py:statement:c",
2941                "unused"
2942            ]
2943            .map(Id::from)
2944        );
2945    }
2946
2947    static ARCHIVE_ID: AtomicU64 = AtomicU64::new(0);
2948
2949    fn point(id: &str, line: usize) -> PointMeta {
2950        PointMeta {
2951            id: id.into(),
2952            kind: PointKind::Statement,
2953            file: "src/app.js".into(),
2954            line,
2955            column: 0,
2956            source: "work();".into(),
2957            label: None,
2958        }
2959    }
2960
2961    fn raw(id: &str, retry: usize, status: &str, hits: &[&str]) -> RawTestResult {
2962        RawTestResult {
2963            test_id: Some(id.into()),
2964            scope: None,
2965            test: id.into(),
2966            test_file: Some("tests/app.test.js".into()),
2967            title: None,
2968            retry: Some(retry),
2969            status: Some(status.into()),
2970            expected_status: None,
2971            flaky: false,
2972            provenance: TestProvenance {
2973                runner: "node:test".into(),
2974                kind: "unit".into(),
2975                project: None,
2976                source: "runner-default".into(),
2977            },
2978            role: "test".into(),
2979            attribution: crate::coverage_report::ATTRIBUTION_EXACT.into(),
2980            phases: vec![],
2981            runtime: vec![RuntimeSnapshot {
2982                decisions: vec![],
2983                hits: hits.iter().map(|hit| (*hit).into()).collect(),
2984                events: vec![],
2985                logicals: vec![],
2986                phase_id: None,
2987            }],
2988            browser: vec![],
2989            server: vec![],
2990        }
2991    }
2992
2993    #[test]
2994    fn declined_obligations_are_unmeasured_never_uncovered() {
2995        // Two statements, neither executed. One of them Supercov declined to
2996        // measure. The declined one must not appear as a coverage gap: it
2997        // leaves the denominator and is reported as unmeasured instead.
2998        let point = |id: &str, line: usize| PointMeta {
2999            id: id.into(),
3000            kind: PointKind::Statement,
3001            file: "src/app.js".into(),
3002            line,
3003            column: 0,
3004            source: "work();".into(),
3005            label: None,
3006        };
3007        let measured_only = CoverageManifest {
3008            decisions: vec![],
3009            points: vec![point("measured", 1)],
3010            branches: vec![],
3011            limitations: vec![],
3012            unmeasured: Vec::new(),
3013            scope: None,
3014        };
3015        let with_declined = CoverageManifest {
3016            decisions: vec![],
3017            points: vec![point("measured", 1), point("declined", 2)],
3018            branches: vec![],
3019            limitations: vec![],
3020            unmeasured: vec!["declined".into()],
3021            scope: None,
3022        };
3023
3024        let baseline =
3025            create_coverage_view(&measured_only, &[raw("test", 0, "passed", &[])], "time").unwrap();
3026        let view =
3027            create_coverage_view(&with_declined, &[raw("test", 0, "passed", &[])], "time").unwrap();
3028
3029        // The declined statement never inflates the uncovered count.
3030        assert_eq!(
3031            view.summary.statements.total, baseline.summary.statements.total,
3032            "a declined obligation stayed in the covered/uncovered denominator"
3033        );
3034        assert_eq!(view.summary.unmeasured_obligations, Some(1));
3035        assert_eq!(view.summary.exact_fraction_pct, Some(50.0));
3036
3037        // Its line leaves the line total too, but not the report: the line is
3038        // still there to hang a limitation on, and it is neither covered nor
3039        // uncovered.
3040        assert_eq!(
3041            view.summary.lines.total, baseline.summary.lines.total,
3042            "a declined obligation kept its line in the line denominator"
3043        );
3044        let declined_line = view
3045            .lines
3046            .iter()
3047            .find(|line| line.line == 2)
3048            .expect("the declined line stays addressable");
3049        assert!(!declined_line.measured);
3050        assert!(!declined_line.covered);
3051
3052        // A fully measured run keeps its previous output exactly: the new
3053        // fields are absent, not zero, so existing consumers see no change.
3054        assert_eq!(baseline.summary.unmeasured_obligations, None);
3055        assert_eq!(baseline.summary.exact_fraction_pct, None);
3056        let encoded = serde_json::to_string(&baseline.summary).unwrap();
3057        assert!(
3058            !encoded.contains("unmeasured") && !encoded.contains("exactFraction"),
3059            "a fully measured summary must serialize unchanged: {encoded}"
3060        );
3061    }
3062
3063    #[test]
3064    fn a_declined_line_that_executed_is_neither_covered_nor_uncovered() {
3065        // The subtle case: the statement ran, but Supercov could not measure
3066        // it, so its line has no coverage question to answer. Counting it as
3067        // covered would inflate the ratio with a line nothing verified.
3068        let point = |id: &str| PointMeta {
3069            id: id.into(),
3070            kind: PointKind::Statement,
3071            file: "src/app.js".into(),
3072            line: 1,
3073            column: 0,
3074            source: "work();".into(),
3075            label: None,
3076        };
3077        let manifest = CoverageManifest {
3078            decisions: vec![],
3079            points: vec![point("declined")],
3080            branches: vec![],
3081            limitations: vec![],
3082            unmeasured: vec!["declined".into()],
3083            scope: None,
3084        };
3085        let view = create_coverage_view(
3086            &manifest,
3087            &[raw("test", 0, "passed", &["declined"])],
3088            "time",
3089        )
3090        .unwrap();
3091
3092        assert_eq!(view.summary.lines.total, 0);
3093        assert_eq!(view.summary.lines.covered, 0);
3094        let line = view.lines.first().expect("the line stays addressable");
3095        assert!(!line.measured);
3096        assert!(!line.covered, "an unmeasured line is not covered either");
3097    }
3098
3099    #[test]
3100    fn report_retains_unexecuted_manifest_conditions() {
3101        let manifest = CoverageManifest {
3102            unmeasured: Vec::new(),
3103            decisions: vec![DecisionMeta {
3104                id: "decision".into(),
3105                file: "src/app.js".into(),
3106                line: 1,
3107                column: 0,
3108                source: "left && right".into(),
3109                conditions: vec!["left".into(), "right".into()],
3110                kind: "if".into(),
3111            }],
3112            points: vec![],
3113            branches: vec![],
3114            limitations: vec![],
3115            scope: None,
3116        };
3117        let view =
3118            create_coverage_view(&manifest, &[raw("test", 0, "passed", &[])], "time").unwrap();
3119        assert_eq!(view.summary.conditions, 2);
3120        assert_eq!(view.summary.covered_conditions, 0);
3121        assert_eq!(view.decisions[0].conditions.len(), 2);
3122    }
3123
3124    #[test]
3125    fn frozen_manifest_ignores_out_of_scope_synthetic_decisions() {
3126        let manifest = CoverageManifest {
3127            unmeasured: Vec::new(),
3128            decisions: vec![DecisionMeta {
3129                id: "application-decision".into(),
3130                file: "src/app.js".into(),
3131                line: 1,
3132                column: 0,
3133                source: "left && right".into(),
3134                conditions: vec!["left".into(), "right".into()],
3135                kind: "if".into(),
3136            }],
3137            points: vec![],
3138            branches: vec![],
3139            limitations: vec![],
3140            scope: None,
3141        };
3142        let mut attempt = raw("test", 0, "passed", &[]);
3143        attempt.runtime[0].decisions.push(DecisionSnapshot {
3144            meta: DecisionMeta {
3145                id: "synthetic-fixture".into(),
3146                file: "fixtures/generated.js".into(),
3147                line: 1,
3148                column: 0,
3149                source: "a && b && c".into(),
3150                conditions: vec!["a".into(), "b".into(), "c".into()],
3151                kind: "if".into(),
3152            },
3153            vectors: vec![McdcVector {
3154                values: vec![Some(true), Some(true), Some(true)],
3155                outcome: true,
3156            }],
3157        });
3158
3159        let view = create_coverage_view(&manifest, &[attempt], "time").unwrap();
3160        assert_eq!(view.decisions.len(), 1);
3161        assert_eq!(view.decisions[0].meta.id, "application-decision");
3162        assert!(view.decisions[0].vectors.is_empty());
3163    }
3164
3165    #[test]
3166    fn frozen_manifest_rejects_in_scope_unknown_or_changed_decisions() {
3167        let expected = DecisionMeta {
3168            id: "application-decision".into(),
3169            file: "src/app.js".into(),
3170            line: 1,
3171            column: 0,
3172            source: "left && right".into(),
3173            conditions: vec!["left".into(), "right".into()],
3174            kind: "if".into(),
3175        };
3176        let manifest = CoverageManifest {
3177            unmeasured: Vec::new(),
3178            decisions: vec![expected.clone()],
3179            points: vec![],
3180            branches: vec![],
3181            limitations: vec![],
3182            scope: Some(serde_json::json!({
3183                "entries": [{ "file": "src/empty.js", "status": "included" }]
3184            })),
3185        };
3186        let vector = McdcVector {
3187            values: vec![Some(true), Some(true)],
3188            outcome: true,
3189        };
3190
3191        let mut unknown = raw("test", 0, "passed", &[]);
3192        unknown.runtime[0].decisions.push(DecisionSnapshot {
3193            meta: DecisionMeta {
3194                id: "unknown-decision".into(),
3195                file: "src/empty.js".into(),
3196                ..expected.clone()
3197            },
3198            vectors: vec![vector.clone()],
3199        });
3200        assert!(matches!(
3201            create_coverage_view(&manifest, &[unknown], "time"),
3202            Err(ReportError::InvalidServerRecord(reason))
3203                if reason.contains("absent from the frozen manifest")
3204        ));
3205
3206        let mut changed = raw("test", 0, "passed", &[]);
3207        changed.runtime[0].decisions.push(DecisionSnapshot {
3208            meta: DecisionMeta {
3209                source: "left || right".into(),
3210                ..expected
3211            },
3212            vectors: vec![vector],
3213        });
3214        assert!(matches!(
3215            create_coverage_view(&manifest, &[changed], "time"),
3216            Err(ReportError::InvalidServerRecord(reason))
3217                if reason.contains("differs from the frozen manifest")
3218        ));
3219    }
3220
3221    #[test]
3222    fn neither_timestamp_nor_explicit_phase_links_award_assertion_credit() {
3223        let manifest = CoverageManifest {
3224            unmeasured: Vec::new(),
3225            decisions: vec![],
3226            points: vec![point("hit", 1)],
3227            branches: vec![],
3228            limitations: vec![],
3229            scope: None,
3230        };
3231        let phase = CoveragePhase {
3232            id: "assertion".into(),
3233            kind: "assertion".into(),
3234            operation: "equal".into(),
3235            source: None,
3236            caused_by_phase_id: None,
3237            started_at_ms: 100,
3238            ended_at_ms: Some(120),
3239            status: Some("passed".into()),
3240            error: None,
3241        };
3242        let mut attempt = raw("test", 0, "passed", &["hit"]);
3243        attempt.phases.push(phase.clone());
3244        attempt.runtime[0].events.push(RuntimeEvent {
3245            event_type: "hit".into(),
3246            id: "hit".into(),
3247            vector: None,
3248            timestamp_ms: 110,
3249            phase_id: None,
3250            statement_id: None,
3251            environment: "server".into(),
3252        });
3253        let inferred = create_coverage_view(&manifest, &[attempt.clone()], "time").unwrap();
3254        assert_eq!(inferred.points[0].confidence.level, "executed");
3255        assert!(!inferred.points[0].confidence.asserted);
3256
3257        attempt.runtime[0].events[0].phase_id = Some(phase.id);
3258        let explicit = create_coverage_view(&manifest, &[attempt], "time").unwrap();
3259        assert_eq!(explicit.points[0].confidence.level, "executed");
3260        assert!(!explicit.points[0].confidence.asserted);
3261    }
3262
3263    #[test]
3264    fn verified_view_uses_only_the_terminal_successful_attempt() {
3265        let manifest = CoverageManifest {
3266            unmeasured: Vec::new(),
3267            decisions: vec![],
3268            points: vec![point("failed", 1), point("passed", 2), point("expected", 3)],
3269            branches: vec![],
3270            limitations: vec![],
3271            scope: None,
3272        };
3273        let failed = raw("flaky", 0, "failed", &["failed"]);
3274        let mut passed = raw("flaky", 1, "passed", &["passed"]);
3275        passed.flaky = true;
3276        let mut expected = raw("expected-failure", 0, "passed", &["expected"]);
3277        expected.expected_status = Some("failed".into());
3278        let request = CoverageReportRequest {
3279            run_id: "run".into(),
3280            manifest,
3281            raw_results: vec![failed, passed, expected],
3282            generated_at: "time".into(),
3283            coverage_model: None,
3284            integrity: None,
3285            test_exit_code: ExitCodeInput::Missing,
3286        };
3287        let report = analyze_coverage_results(&request).unwrap();
3288        assert!(!report.filters.passed.points[0].covered);
3289        assert!(report.filters.passed.points[1].covered);
3290        assert!(!report.filters.passed.points[2].covered);
3291        assert!(report.filters.failed.points[0].covered);
3292        assert_eq!(
3293            report
3294                .view
3295                .tests
3296                .iter()
3297                .find(|test| test.name == "expected-failure")
3298                .unwrap()
3299                .outcome,
3300            "failed"
3301        );
3302        assert_eq!(report.view.tests[1].outcome, "flaky");
3303    }
3304
3305    #[test]
3306    fn expected_failure_is_a_green_outcome_but_not_verified_or_failed_coverage() {
3307        let mut expected = raw("expected-failure", 0, "failed", &["expected"]);
3308        expected.expected_status = Some("failed".into());
3309        let companion = raw("expected-failure", 0, "failed", &["expected"]);
3310        let request = CoverageReportRequest {
3311            run_id: "run".into(),
3312            manifest: CoverageManifest {
3313                unmeasured: Vec::new(),
3314                decisions: vec![],
3315                points: vec![point("expected", 1)],
3316                branches: vec![],
3317                limitations: vec![],
3318                scope: None,
3319            },
3320            raw_results: vec![companion, expected],
3321            generated_at: "time".into(),
3322            coverage_model: None,
3323            integrity: None,
3324            test_exit_code: ExitCodeInput::Missing,
3325        };
3326        let report = analyze_coverage_results(&request).unwrap();
3327        assert_eq!(report.view.tests[0].outcome, "passed");
3328        assert!(!report.filters.passed.points[0].covered);
3329        assert!(!report.filters.failed.points[0].covered);
3330    }
3331
3332    #[test]
3333    fn selected_but_unstarted_test_is_not_an_invented_attempt() {
3334        let mut unstarted = raw("unstarted", 0, "unstarted", &[]);
3335        unstarted.scope = None;
3336        unstarted.retry = None;
3337        unstarted.runtime.clear();
3338        let request = CoverageReportRequest {
3339            run_id: "run".into(),
3340            manifest: CoverageManifest {
3341                unmeasured: Vec::new(),
3342                decisions: vec![],
3343                points: vec![],
3344                branches: vec![],
3345                limitations: vec![],
3346                scope: None,
3347            },
3348            raw_results: vec![unstarted],
3349            generated_at: "time".into(),
3350            coverage_model: None,
3351            integrity: None,
3352            test_exit_code: ExitCodeInput::Present(Some(100)),
3353        };
3354        let report = analyze_coverage_results(&request).unwrap();
3355        assert_eq!(report.view.tests[0].outcome, "unstarted");
3356        assert!(report.view.tests[0].attempts.is_empty());
3357        assert!(report.filters.passed.tests.is_empty());
3358        assert!(report.filters.failed.tests.is_empty());
3359    }
3360
3361    fn archive(mut entries: Vec<EvidenceArchiveEntry>) -> PathBuf {
3362        let nonce = SystemTime::now()
3363            .duration_since(SystemTime::UNIX_EPOCH)
3364            .unwrap()
3365            .as_nanos();
3366        let root = std::env::temp_dir().join(format!(
3367            "supercov-rust-report-{}-{nonce}-{}",
3368            std::process::id(),
3369            ARCHIVE_ID.fetch_add(1, Ordering::Relaxed)
3370        ));
3371        fs::create_dir_all(&root).unwrap();
3372        let path = root.join("evidence.raw.gz");
3373        if !entries
3374            .iter()
3375            .any(|entry| entry.path == "coverage-model.json")
3376        {
3377            entries.push(EvidenceArchiveEntry {
3378                path: "coverage-model.json".into(),
3379                contents: serde_json::to_vec(
3380                    &PersistedCoverageModel::from_declaration(&javascript_coverage_model())
3381                        .unwrap(),
3382                )
3383                .unwrap(),
3384            });
3385        }
3386        if !entries.iter().any(|entry| entry.path == "frontend.json") {
3387            entries.push(EvidenceArchiveEntry {
3388                path: "frontend.json".into(),
3389                contents: serde_json::to_vec(&serde_json::json!({
3390                    "protocolVersion": 2,
3391                    "frontendId": "javascript",
3392                    "frontendVersion": "fixture-v1",
3393                    "language": "javascript",
3394                    "structuralSource": "owned-probes",
3395                    "runners": [{
3396                        "runner": "node:test",
3397                        "executionModel": "serial-in-process",
3398                        "attribution": {
3399                            "run": "exact",
3400                            "worker": "unavailable",
3401                            "test": "exact",
3402                            "retry": "exact",
3403                            "phase": "exact",
3404                            "action": "exact",
3405                            "assertion": "exact"
3406                        },
3407                        "limitations": [{
3408                            "id": "fixture-worker-unavailable",
3409                            "scopes": ["worker"],
3410                            "reason": "The fixture does not require worker identity"
3411                        }]
3412                    }],
3413                    "structuralLimitations": []
3414                }))
3415                .unwrap(),
3416            });
3417        }
3418        write_archive(entries, &path).unwrap();
3419        path
3420    }
3421
3422    #[test]
3423    fn archive_analysis_rejects_any_malformed_recognized_jsonl() {
3424        let manifest = CoverageManifest {
3425            unmeasured: Vec::new(),
3426            decisions: vec![],
3427            points: vec![point("background-hit", 1), point("test-hit", 2)],
3428            branches: vec![],
3429            limitations: vec![],
3430            scope: None,
3431        };
3432        // Declare exactly the observed runners: the journal's node:test and the
3433        // synthesized background runner, shaped as the JavaScript run declares it.
3434        let unattributed = |axis: &str| {
3435            serde_json::json!({
3436                "id": format!("background-no-{axis}"),
3437                "scopes": [axis],
3438                "reason": format!("Runner background did not expose exact {axis} identity for every result")
3439            })
3440        };
3441        let frontend = serde_json::json!({
3442            "protocolVersion": 2,
3443            "frontendId": "javascript",
3444            "frontendVersion": "fixture-v1",
3445            "language": "javascript",
3446            "structuralSource": "owned-probes",
3447            "runners": [
3448                {
3449                    "runner": "node:test",
3450                    "executionModel": "serial-in-process",
3451                    "attribution": {
3452                        "run": "exact", "worker": "unavailable", "test": "exact", "retry": "exact",
3453                        "phase": "exact", "action": "exact", "assertion": "exact"
3454                    },
3455                    "limitations": [{
3456                        "id": "fixture-worker-unavailable",
3457                        "scopes": ["worker"],
3458                        "reason": "The fixture does not require worker identity"
3459                    }]
3460                },
3461                {
3462                    "runner": "background",
3463                    "executionModel": "parallel-unattributed",
3464                    "attribution": {
3465                        "run": "exact", "worker": "unavailable", "test": "unavailable", "retry": "unavailable",
3466                        "phase": "unavailable", "action": "unavailable", "assertion": "unavailable"
3467                    },
3468                    "limitations": [
3469                        unattributed("worker"), unattributed("test"), unattributed("retry"),
3470                        unattributed("phase"), unattributed("action"), unattributed("assertion")
3471                    ]
3472                }
3473            ],
3474            "structuralLimitations": []
3475        });
3476        let path = archive(vec![
3477            EvidenceArchiveEntry {
3478                path: "manifest.json".into(),
3479                contents: serde_json::to_vec(&manifest).unwrap(),
3480            },
3481            EvidenceArchiveEntry {
3482                path: "frontend.json".into(),
3483                contents: serde_json::to_vec(&frontend).unwrap(),
3484            },
3485            EvidenceArchiveEntry {
3486                path: "playwright-worker-1.mcdc.jsonl".into(),
3487                contents: {
3488                    let mut contents =
3489                        serde_json::to_vec(&raw("journal-test", 0, "passed", &["test-hit"]))
3490                            .unwrap();
3491                    contents.extend_from_slice(b"\npartial-final-line");
3492                    contents
3493                },
3494            },
3495            EvidenceArchiveEntry {
3496                path: "server/background/worker.jsonl".into(),
3497                contents: b"{\"type\":\"hit\",\"id\":\"background-hit\"}\nnot-json\n".to_vec(),
3498            },
3499        ]);
3500        let result = analyze_coverage_archive(&ArchiveReportRequest {
3501            archive_path: path.clone(),
3502            run_id: "run".into(),
3503            generated_at: "time".into(),
3504            integrity: None,
3505            test_exit_code: ExitCodeInput::Missing,
3506        });
3507        assert!(matches!(
3508            result,
3509            Err(ReportError::InvalidJson { path, .. })
3510                if path == "playwright-worker-1.mcdc.jsonl"
3511        ));
3512        fs::remove_dir_all(path.parent().unwrap()).unwrap();
3513    }
3514
3515    #[test]
3516    fn a_torn_background_record_is_counted_not_fatal() {
3517        // Pool VMs restored from one snapshot are clones with the same pid and
3518        // cached shard path; their appends over a shared mount tear lines. One
3519        // torn line is one lost record: the report must still build, count it,
3520        // and surface it through the corrupt-evidence accounting.
3521        let manifest = CoverageManifest {
3522            unmeasured: Vec::new(),
3523            decisions: vec![],
3524            points: vec![],
3525            branches: vec![],
3526            limitations: vec![],
3527            scope: None,
3528        };
3529        // Declare exactly the observed runners: the journal's node:test and the
3530        // synthesized background runner, shaped as the JavaScript run declares it.
3531        let unattributed = |axis: &str| {
3532            serde_json::json!({
3533                "id": format!("background-no-{axis}"),
3534                "scopes": [axis],
3535                "reason": format!("Runner background did not expose exact {axis} identity for every result")
3536            })
3537        };
3538        let frontend = serde_json::json!({
3539            "protocolVersion": 2,
3540            "frontendId": "javascript",
3541            "frontendVersion": "fixture-v1",
3542            "language": "javascript",
3543            "structuralSource": "owned-probes",
3544            "runners": [
3545                {
3546                    "runner": "node:test",
3547                    "executionModel": "serial-in-process",
3548                    "attribution": {
3549                        "run": "exact", "worker": "unavailable", "test": "exact", "retry": "exact",
3550                        "phase": "exact", "action": "exact", "assertion": "exact"
3551                    },
3552                    "limitations": [{
3553                        "id": "fixture-worker-unavailable",
3554                        "scopes": ["worker"],
3555                        "reason": "The fixture does not require worker identity"
3556                    }]
3557                },
3558                {
3559                    "runner": "background",
3560                    "executionModel": "parallel-unattributed",
3561                    "attribution": {
3562                        "run": "exact", "worker": "unavailable", "test": "unavailable", "retry": "unavailable",
3563                        "phase": "unavailable", "action": "unavailable", "assertion": "unavailable"
3564                    },
3565                    "limitations": [
3566                        unattributed("worker"), unattributed("test"), unattributed("retry"),
3567                        unattributed("phase"), unattributed("action"), unattributed("assertion")
3568                    ]
3569                }
3570            ],
3571            "structuralLimitations": []
3572        });
3573        let path = archive(vec![
3574            EvidenceArchiveEntry {
3575                path: "manifest.json".into(),
3576                contents: serde_json::to_vec(&manifest).unwrap(),
3577            },
3578            EvidenceArchiveEntry {
3579                path: "frontend.json".into(),
3580                contents: serde_json::to_vec(&frontend).unwrap(),
3581            },
3582            EvidenceArchiveEntry {
3583                path: "playwright-worker-1.mcdc.jsonl".into(),
3584                contents: {
3585                    let mut contents =
3586                        serde_json::to_vec(&raw("journal-test", 0, "passed", &["test-hit"]))
3587                            .unwrap();
3588                    contents.push(b'\n');
3589                    contents
3590                },
3591            },
3592            EvidenceArchiveEntry {
3593                path: "execution.37360-1.168.jsonl".into(),
3594                contents: b"418836ceb3ac9b6277f\"}}\n".to_vec(),
3595            },
3596            EvidenceArchiveEntry {
3597                path: "server/background/process-5118-a1b2c3d4-0.jsonl".into(),
3598                contents: concat!(
3599                    "{\"type\":\"hit\",\"id\":\"background-hit\"}\n",
3600                    "{\"type\":\"decision\",\"meta\":{\"id\":\"torn\",\"file\":\"app/rou{\"type\":\"hit\",\"id\":\"other-clone\"}\n",
3601                    "{\"type\":\"hit\",\"id\":\"after-tear\"}\n",
3602                )
3603                .as_bytes()
3604                .to_vec(),
3605            },
3606        ]);
3607        let report = analyze_coverage_archive(&ArchiveReportRequest {
3608            archive_path: path.clone(),
3609            run_id: "run".into(),
3610            generated_at: "time".into(),
3611            integrity: None,
3612            test_exit_code: ExitCodeInput::Missing,
3613        })
3614        .expect("a torn evidence line must not fail the whole run");
3615        let transport = report.view.transport.expect("transport stats");
3616        assert_eq!(transport.corrupt_records, 2);
3617        assert_eq!(transport.corrupt_files, 2);
3618        assert_eq!(transport.background_server_records, 2);
3619        fs::remove_dir_all(path.parent().unwrap()).unwrap();
3620    }
3621
3622    #[test]
3623    fn archive_analysis_rejects_cross_run_evidence() {
3624        let manifest = CoverageManifest {
3625            unmeasured: Vec::new(),
3626            decisions: vec![],
3627            points: vec![],
3628            branches: vec![],
3629            limitations: vec![],
3630            scope: None,
3631        };
3632        let mut result = raw("test", 0, "passed", &[]);
3633        result.scope = Some(ExecutionScope {
3634            version: 1,
3635            run_id: "other-run".into(),
3636            worker_id: "worker".into(),
3637            test_id: "test".into(),
3638            test_key: "key".into(),
3639            retry: 0,
3640            attempt_id: "attempt".into(),
3641        });
3642        let path = archive(vec![
3643            EvidenceArchiveEntry {
3644                path: "manifest.json".into(),
3645                contents: serde_json::to_vec(&manifest).unwrap(),
3646            },
3647            EvidenceArchiveEntry {
3648                path: "worker/mcdc.json".into(),
3649                contents: serde_json::to_vec(&result).unwrap(),
3650            },
3651        ]);
3652        assert!(matches!(
3653            analyze_coverage_archive(&ArchiveReportRequest {
3654                archive_path: path.clone(),
3655                run_id: "run".into(),
3656                generated_at: "time".into(),
3657                integrity: None,
3658                test_exit_code: ExitCodeInput::Missing,
3659            }),
3660            Err(ReportError::InvalidArchive(reason))
3661                if reason.contains("expected=run actual=other-run")
3662        ));
3663        fs::remove_dir_all(path.parent().unwrap()).unwrap();
3664    }
3665
3666    #[test]
3667    fn coverage_model_vectors_are_strict_and_language_binding_is_fatal() {
3668        let vectors: Value = serde_json::from_str(include_str!(
3669            "../test-assets/coverage-model-v1/vectors.json"
3670        ))
3671        .unwrap();
3672        for value in vectors["valid"].as_array().unwrap() {
3673            let model: PersistedCoverageModel = serde_json::from_value(value.clone()).unwrap();
3674            model.into_declaration().unwrap();
3675        }
3676        for vector in vectors["invalid"].as_array().unwrap() {
3677            let value = vector["value"].clone();
3678            if let Ok(model) = serde_json::from_value::<PersistedCoverageModel>(value) {
3679                assert!(
3680                    model.into_declaration().is_err(),
3681                    "accepted invalid model vector: {}",
3682                    vector["reason"]
3683                );
3684            }
3685        }
3686
3687        let manifest = CoverageManifest {
3688            unmeasured: Vec::new(),
3689            decisions: vec![],
3690            points: vec![],
3691            branches: vec![],
3692            limitations: vec![],
3693            scope: None,
3694        };
3695        let path = archive(vec![
3696            EvidenceArchiveEntry {
3697                path: "coverage-model.json".into(),
3698                contents: serde_json::to_vec(&PersistedCoverageModel {
3699                    schema_version: COVERAGE_MODEL_SCHEMA_VERSION,
3700                    language: "rust".into(),
3701                    variant: "rust-source-v1".into(),
3702                    name: "Rust source coverage".into(),
3703                    completeness_meaning: "Every Rust obligation was satisfied.".into(),
3704                    measured: vec!["Rust statements".into()],
3705                    not_measured: vec![],
3706                })
3707                .unwrap(),
3708            },
3709            EvidenceArchiveEntry {
3710                path: "manifest.json".into(),
3711                contents: serde_json::to_vec(&manifest).unwrap(),
3712            },
3713        ]);
3714        assert!(matches!(
3715            analyze_coverage_archive(&ArchiveReportRequest {
3716                archive_path: path.clone(),
3717                run_id: "run".into(),
3718                generated_at: "time".into(),
3719                integrity: None,
3720                test_exit_code: ExitCodeInput::Missing,
3721            }),
3722            Err(ReportError::InvalidArchive(reason))
3723                if reason.contains("javascript differs from coverage model language rust")
3724        ));
3725        fs::remove_dir_all(path.parent().unwrap()).unwrap();
3726    }
3727
3728    #[test]
3729    fn rust_compiler_scope_requires_an_exact_full_source_fingerprint() {
3730        let mut manifest = CoverageManifest {
3731            unmeasured: Vec::new(),
3732            decisions: vec![],
3733            points: vec![],
3734            branches: vec![],
3735            limitations: vec![],
3736            scope: Some(serde_json::json!({
3737                "language": "rust",
3738                "model": "rust-source-v1",
3739                "crate": "fixture",
3740                "measurementComplete": false,
3741                "sourceFingerprint": {
3742                    "algorithm": "sha256",
3743                    "digest": "1".repeat(64),
3744                    "files": 2,
3745                    "generatedFiles": 1,
3746                },
3747            })),
3748        };
3749        validate_rust_compiler_scope(&manifest).unwrap();
3750
3751        // The optional extension does not open the scope to arbitrary keys or
3752        // unvalidated records, and legacy archives still need no such field.
3753        let mut extended = manifest.clone();
3754        extended.scope.as_mut().unwrap()["assertionIdentities"] = serde_json::json!({
3755            "schema":"supercov-rust-assertion-identities-v1", "records":[]
3756        });
3757        validate_rust_compiler_scope(&extended).unwrap();
3758
3759        manifest.scope.as_mut().unwrap()["sourceFingerprint"]["digest"] =
3760            Value::String("not-a-digest".into());
3761        assert!(matches!(
3762            validate_rust_compiler_scope(&manifest),
3763            Err(ReportError::InvalidArchive(reason))
3764                if reason == "malformed Rust compiler source fingerprint"
3765        ));
3766
3767        manifest.scope.as_mut().unwrap()["sourceFingerprint"]["digest"] =
3768            Value::String("1".repeat(64));
3769        manifest.scope.as_mut().unwrap()["sourceFingerprint"]["unexpected"] = Value::Bool(true);
3770        assert!(validate_rust_compiler_scope(&manifest).is_err());
3771    }
3772
3773    #[test]
3774    fn explicit_null_exit_code_remains_distinct_from_an_absent_exit_code() {
3775        let request: CoverageReportRequest = serde_json::from_value(serde_json::json!({
3776            "runId": "run",
3777            "manifest": { "decisions": [], "points": [], "branches": [] },
3778            "rawResults": [{
3779                "test": "test",
3780                "status": "passed",
3781                "browser": [],
3782                "server": []
3783            }],
3784            "generatedAt": "time",
3785            "testExitCode": null
3786        }))
3787        .unwrap();
3788        let report = analyze_coverage_results(&request).unwrap();
3789        assert_eq!(
3790            report.execution,
3791            Some(ExecutionResult {
3792                test_exit_code: None,
3793                valid: false,
3794            })
3795        );
3796    }
3797    #[test]
3798    fn a_dimension_is_summarised_over_the_tests_it_can_describe() {
3799        // A test whose reach nothing recorded contributes no hits. Counting it
3800        // in the denominator of a coverage percentage reported a package
3801        // covered entirely by parallel tests as covered 0.00% -- a wrong
3802        // number, which is worse than a missing one, because it reads as a
3803        // suite that tests nothing.
3804        let exact = |id: &str, kind: &str| TestCoverageResult {
3805            id: id.into(),
3806            name: id.into(),
3807            file: None,
3808            title: None,
3809            retries: vec![],
3810            attempts: vec![],
3811            outcome: "passed".into(),
3812            provenance: TestProvenance {
3813                runner: "go-test".into(),
3814                kind: kind.into(),
3815                project: None,
3816                source: String::new(),
3817            },
3818            role: "test".into(),
3819            attribution: ATTRIBUTION_EXACT.into(),
3820            hits: vec![],
3821            decisions: vec![],
3822            lines: vec![],
3823        };
3824        let mut run_wide = exact("parallel", "unit");
3825        run_wide.attribution = ATTRIBUTION_RUN_WIDE.into();
3826
3827        // Whose coverage it is, and whether all of it is there, are separate
3828        // questions, and conflating them gets one of them wrong.
3829        //
3830        // `partial` coverage is the test's own -- Ruby credits a line to the
3831        // first test that reaches it, and that crediting is true -- so it
3832        // counts in a percentage, and a union over such tests is a union of
3833        // things that happened. What it cannot support is the opposite
3834        // inference: that a test not recorded against a line did not run it.
3835        assert!(coverage_is_its_own(&exact("serial", "unit").attribution));
3836        assert!(coverage_is_its_own(ATTRIBUTION_PARTIAL));
3837        assert!(!coverage_is_its_own(&run_wide.attribution));
3838
3839        assert!(coverage_is_complete(ATTRIBUTION_EXACT));
3840        assert!(!coverage_is_complete(ATTRIBUTION_PARTIAL));
3841        assert!(!coverage_is_complete(ATTRIBUTION_RUN_WIDE));
3842
3843        // And a frontend that has never heard of the field keeps its meaning:
3844        // absent is exact, so nothing already measured changes shape.
3845        assert!(coverage_is_its_own(""));
3846        assert!(coverage_is_complete(""));
3847    }
3848}