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