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