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