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