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