Skip to main content

supercov_engine/
coverage_index.rs

1//! Typed coverage columns stored in the immutable query-index container.
2//!
3//! This is not a serialized report. Records contain fixed-width values and
4//! checked references into an interned UTF-8 string table. New query surfaces
5//! add sections without forcing existing readers to parse unrelated data.
6
7use std::collections::{BTreeMap, BTreeSet, HashMap};
8
9use serde::Serialize;
10use supercov_contracts::COVERAGE_MODEL_SCHEMA_VERSION;
11
12use crate::{
13    coverage_analysis::{
14        CoverageCount, CoverageSummary, McdcVector, find_witnesses_for_conditions,
15    },
16    coverage_report::{
17        CoverageModel, CoverageReport, CoverageView, TransportStats, coverage_summary_for_tests,
18    },
19    interned::Id,
20    query_index::{QueryIndex, QueryIndexError, QueryIndexSection},
21};
22
23pub const SECTION_STRING_BYTES: u32 = 1;
24pub const SECTION_STRINGS: u32 = 2;
25pub const SECTION_STRING_RELATIONS: u32 = 3;
26pub const SECTION_VIEW_SUMMARIES: u32 = 10;
27pub const SECTION_FILE_GAPS: u32 = 11;
28pub const SECTION_DECISION_GAPS: u32 = 12;
29pub const SECTION_DIMENSIONS: u32 = 13;
30pub const SECTION_PROJECTIONS: u32 = 14;
31pub const SECTION_SCOPE_ENTRIES: u32 = 15;
32pub const SECTION_CONFIDENCE: u32 = 16;
33pub const SECTION_LINES: u32 = 17;
34pub const SECTION_TEST_SUMMARIES: u32 = 18;
35pub const SECTION_PHASE_SUMMARIES: u32 = 19;
36pub const SECTION_ANCHORS: u32 = 20;
37pub const SECTION_TEST_RETRIES: u32 = 21;
38pub const SECTION_TEST_ATTEMPTS: u32 = 22;
39pub const SECTION_TEST_LINES: u32 = 23;
40pub const SECTION_TEST_HITS: u32 = 24;
41pub const SECTION_TEST_DECISIONS: u32 = 25;
42pub const SECTION_TEST_VECTORS: u32 = 26;
43pub const SECTION_VECTOR_VALUES: u32 = 27;
44pub const SECTION_HIT_METADATA: u32 = 28;
45pub const SECTION_DECISION_METADATA: u32 = 29;
46pub const SECTION_DECISION_DETAILS: u32 = 30;
47pub const SECTION_DECISION_VECTOR_OBSERVATIONS: u32 = 31;
48pub const SECTION_DECISION_CONDITIONS: u32 = 32;
49pub const SECTION_LIMITATIONS: u32 = 33;
50pub const SECTION_COVERAGE_MODEL: u32 = 34;
51
52const STRING_RECORD_SIZE: usize = 16;
53const SUMMARY_RECORD_SIZE: usize = 176;
54const FILE_GAP_RECORD_SIZE: usize = 200;
55const DECISION_GAP_RECORD_SIZE: usize = 96;
56const DIMENSION_RECORD_SIZE: usize = 192;
57const PROJECTION_RECORD_SIZE: usize = 544;
58const SCOPE_ENTRY_RECORD_SIZE: usize = 96;
59const CONFIDENCE_RECORD_SIZE: usize = 96;
60const LINE_RECORD_SIZE: usize = 80;
61const TEST_SUMMARY_RECORD_SIZE: usize = 64;
62const PHASE_SUMMARY_RECORD_SIZE: usize = 64;
63const ANCHOR_RECORD_SIZE: usize = 64;
64const TEST_RETRY_RECORD_SIZE: usize = 16;
65const TEST_ATTEMPT_RECORD_SIZE: usize = 24;
66const TEST_LINE_RECORD_SIZE: usize = 24;
67const TEST_HIT_RECORD_SIZE: usize = 16;
68const TEST_DECISION_RECORD_SIZE: usize = 32;
69const TEST_VECTOR_RECORD_SIZE: usize = 24;
70const HIT_METADATA_RECORD_SIZE: usize = 64;
71const DECISION_METADATA_RECORD_SIZE: usize = 64;
72const DECISION_DETAIL_RECORD_SIZE: usize = 64;
73const DECISION_VECTOR_OBSERVATION_RECORD_SIZE: usize = 64;
74const DECISION_CONDITION_RECORD_SIZE: usize = 64;
75const LIMITATION_RECORD_SIZE: usize = 64;
76const COVERAGE_MODEL_RECORD_SIZE: usize = 48;
77const NO_STRING: u32 = u32::MAX;
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
80#[serde(rename_all = "lowercase")]
81pub enum CoverageViewId {
82    All = 0,
83    Passed = 1,
84    Failed = 2,
85}
86
87impl TryFrom<u8> for CoverageViewId {
88    type Error = CoverageIndexError;
89
90    fn try_from(value: u8) -> Result<Self, Self::Error> {
91        match value {
92            0 => Ok(Self::All),
93            1 => Ok(Self::Passed),
94            2 => Ok(Self::Failed),
95            _ => Err(CoverageIndexError::InvalidRecord("coverage view")),
96        }
97    }
98}
99
100#[derive(Debug)]
101pub enum CoverageIndexError {
102    Container(QueryIndexError),
103    InvalidRecord(&'static str),
104    InvalidUtf8,
105    SizeOverflow,
106}
107
108impl From<QueryIndexError> for CoverageIndexError {
109    fn from(value: QueryIndexError) -> Self {
110        Self::Container(value)
111    }
112}
113
114impl std::fmt::Display for CoverageIndexError {
115    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        match self {
117            Self::Container(error) => write!(formatter, "{error}"),
118            Self::InvalidRecord(reason) => write!(formatter, "invalid coverage index: {reason}"),
119            Self::InvalidUtf8 => write!(formatter, "invalid UTF-8 in coverage index"),
120            Self::SizeOverflow => write!(formatter, "coverage index exceeds format limits"),
121        }
122    }
123}
124
125impl std::error::Error for CoverageIndexError {}
126
127fn put_u32(bytes: &mut [u8], offset: usize, value: u32) {
128    bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
129}
130
131fn put_u64(bytes: &mut [u8], offset: usize, value: u64) {
132    bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
133}
134
135/// A field of a record. Every record the reader takes is exactly its kind's
136/// size -- [`CoverageIndex::new`] refuses an index whose sizes differ -- and
137/// every field lies inside it, so a read cannot run past the record.
138fn get_u32(record: &[u8], offset: usize) -> u32 {
139    u32::from_le_bytes(
140        record[offset..offset + 4]
141            .try_into()
142            .expect("a field lies inside its record"),
143    )
144}
145
146fn get_u64(record: &[u8], offset: usize) -> u64 {
147    u64::from_le_bytes(
148        record[offset..offset + 8]
149            .try_into()
150            .expect("a field lies inside its record"),
151    )
152}
153
154fn usize_u64(value: usize) -> Result<u64, CoverageIndexError> {
155    u64::try_from(value).map_err(|_| CoverageIndexError::SizeOverflow)
156}
157
158fn usize_u32(value: usize) -> Result<u32, CoverageIndexError> {
159    u32::try_from(value).map_err(|_| CoverageIndexError::SizeOverflow)
160}
161
162#[derive(Default)]
163struct StringTable {
164    ids: crate::interned::FastMap<String, u32>,
165    strings: Vec<String>,
166    // Keep each allocation alive: its address can then identify repeated Id
167    // references without hashing the same long test name for every line.
168    shared_ids: crate::interned::FastMap<usize, (Id, u32)>,
169}
170
171#[derive(Default)]
172struct StringRelations {
173    values: Vec<u32>,
174    /// Where each distinct run already sits. Readers reach a run only through
175    /// the (offset, count) pair the record stores, so two records naming the
176    /// same run can share one copy of it and nothing downstream can tell.
177    interned: crate::interned::FastMap<Vec<u32>, (u64, u64)>,
178}
179
180impl StringRelations {
181    /// Append a run of interned strings, or point at an identical one already
182    /// stored.
183    ///
184    /// The same run is named over and over: a filtered view repeats the whole
185    /// relation of the view it filters, and one list of tests covers many
186    /// lines of the file they exercise. Appending each time made this the
187    /// largest section in the container -- 61 MB of a 210 MB index, 15.3
188    /// million entries -- for a number of distinct runs far smaller.
189    fn push(
190        &mut self,
191        values: impl IntoIterator<Item = impl AsRef<str>>,
192        strings: &mut StringTable,
193    ) -> Result<(u64, u64), CoverageIndexError> {
194        let mut run = Vec::new();
195        for value in values {
196            run.push(strings.intern(value.as_ref())?);
197        }
198        self.push_run(run)
199    }
200
201    fn push_ids(
202        &mut self,
203        values: &[Id],
204        strings: &mut StringTable,
205    ) -> Result<(u64, u64), CoverageIndexError> {
206        let mut run = Vec::with_capacity(values.len());
207        for value in values {
208            run.push(strings.intern_shared(value)?);
209        }
210        self.push_run(run)
211    }
212
213    fn push_run(&mut self, run: Vec<u32>) -> Result<(u64, u64), CoverageIndexError> {
214        if let Some(found) = self.interned.get(&run) {
215            return Ok(*found);
216        }
217        let offset = usize_u64(self.values.len())?;
218        let count = usize_u64(run.len())?;
219        self.values.extend_from_slice(&run);
220        self.interned.insert(run, (offset, count));
221        Ok((offset, count))
222    }
223
224    fn section(self) -> Result<QueryIndexSection, CoverageIndexError> {
225        let mut bytes = Vec::with_capacity(self.values.len() * 4);
226        for value in self.values {
227            bytes.extend_from_slice(&value.to_le_bytes());
228        }
229        Ok(QueryIndexSection {
230            kind: SECTION_STRING_RELATIONS,
231            record_size: 4,
232            count: usize_u64(bytes.len() / 4)?,
233            bytes,
234        })
235    }
236}
237
238impl StringTable {
239    fn intern_shared(&mut self, value: &Id) -> Result<u32, CoverageIndexError> {
240        let allocation = value.as_str().as_ptr() as usize;
241        if let Some((_, id)) = self.shared_ids.get(&allocation) {
242            return Ok(*id);
243        }
244        // Different allocations of identical text still use the same string
245        // table entry, including IDs built outside the report's interner.
246        let id = self.intern(value.as_str())?;
247        self.shared_ids.insert(allocation, (value.clone(), id));
248        Ok(id)
249    }
250
251    fn intern(&mut self, value: &str) -> Result<u32, CoverageIndexError> {
252        if let Some(id) = self.ids.get(value) {
253            return Ok(*id);
254        }
255        let id = usize_u32(self.strings.len())?;
256        self.ids.insert(value.into(), id);
257        self.strings.push(value.into());
258        Ok(id)
259    }
260
261    fn sections(self) -> Result<[QueryIndexSection; 2], CoverageIndexError> {
262        let mut blob = Vec::new();
263        let mut records = Vec::with_capacity(self.strings.len() * STRING_RECORD_SIZE);
264        for string in self.strings {
265            let offset = usize_u64(blob.len())?;
266            let value = string.as_bytes();
267            let length = usize_u32(value.len())?;
268            blob.extend_from_slice(value);
269            let mut record = [0_u8; STRING_RECORD_SIZE];
270            put_u64(&mut record, 0, offset);
271            put_u32(&mut record, 8, length);
272            records.extend_from_slice(&record);
273        }
274        Ok([
275            QueryIndexSection {
276                kind: SECTION_STRING_BYTES,
277                record_size: 0,
278                count: usize_u64(blob.len())?,
279                bytes: blob,
280            },
281            QueryIndexSection {
282                kind: SECTION_STRINGS,
283                record_size: STRING_RECORD_SIZE as u32,
284                count: usize_u64(records.len() / STRING_RECORD_SIZE)?,
285                bytes: records,
286            },
287        ])
288    }
289}
290
291fn put_count(
292    bytes: &mut [u8],
293    offset: usize,
294    count: &CoverageCount,
295) -> Result<(), CoverageIndexError> {
296    put_u64(bytes, offset, usize_u64(count.covered)?);
297    put_u64(bytes, offset + 8, usize_u64(count.total)?);
298    Ok(())
299}
300
301fn summary_record(
302    id: CoverageViewId,
303    view: &CoverageView,
304    strings: &mut StringTable,
305) -> Result<[u8; SUMMARY_RECORD_SIZE], CoverageIndexError> {
306    let mut record = [0_u8; SUMMARY_RECORD_SIZE];
307    record[0] = id as u8;
308    record[1] = u8::from(view.summary.coverage_complete);
309    record[2] = match view.summary.completeness_blocked {
310        None => 0,
311        Some(false) => 1,
312        Some(true) => 2,
313    };
314    put_u32(&mut record, 4, strings.intern(&view.generated_at)?);
315    put_u32(&mut record, 8, strings.intern(&view.variant)?);
316    let values = [
317        view.summary.decisions,
318        view.summary.executed_decisions,
319        view.summary.covered_decisions,
320        view.summary.conditions,
321        view.summary.covered_conditions,
322    ];
323    for (index, value) in values.into_iter().enumerate() {
324        put_u64(&mut record, 16 + index * 8, usize_u64(value)?);
325    }
326    for (index, count) in [
327        &view.summary.lines,
328        &view.summary.statements,
329        &view.summary.functions,
330        &view.summary.branches,
331        &view.summary.decision_outcomes,
332        &view.summary.condition_outcomes,
333        &view.summary.value_selections,
334    ]
335    .into_iter()
336    .enumerate()
337    {
338        put_count(&mut record, 56 + index * 16, count)?;
339    }
340    Ok(record)
341}
342
343#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
344#[serde(rename_all = "camelCase")]
345pub struct IndexedFileGap {
346    #[serde(skip)]
347    pub view: CoverageViewId,
348    pub file: String,
349    pub uncovered_lines: usize,
350    pub uncovered_statements: usize,
351    pub uncovered_functions: usize,
352    pub missing_branches: usize,
353    pub missing_mcdc_conditions: usize,
354    pub measurement_limitations: usize,
355    pub limitation_kinds: Vec<String>,
356    pub covered_by_other_tests: IndexedGapDimensions,
357    pub uncovered_everywhere: IndexedGapDimensions,
358    /// What the file has to cover, and how much of it the selected tests do.
359    /// The listing gave only what was missing, so no percentage could be
360    /// worked out for a file, or summed for a directory.
361    pub totals: IndexedGapDimensions,
362    pub covered: IndexedGapDimensions,
363    pub score: usize,
364}
365
366#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
367#[serde(rename_all = "camelCase")]
368pub struct IndexedGapDimensions {
369    pub lines: usize,
370    pub statements: usize,
371    pub functions: usize,
372    pub branches: usize,
373    pub mcdc_conditions: usize,
374}
375
376#[derive(Debug, Clone, PartialEq, Serialize)]
377#[serde(rename_all = "camelCase")]
378pub struct IndexedCoverageSnapshot {
379    pub all_summary: CoverageSummary,
380    pub passed_summary: CoverageSummary,
381    pub failed_summary: CoverageSummary,
382    pub all_files: Vec<IndexedFileGap>,
383    pub passed_files: Vec<IndexedFileGap>,
384    pub failed_files: Vec<IndexedFileGap>,
385}
386
387#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
388#[serde(rename_all = "camelCase")]
389pub struct IndexedDecisionGap {
390    #[serde(skip)]
391    pub view: CoverageViewId,
392    #[serde(skip)]
393    pub file: String,
394    pub id: String,
395    pub line: usize,
396    pub column: usize,
397    pub kind: String,
398    pub conditions: usize,
399    pub missing_conditions: usize,
400    pub source: String,
401}
402
403#[derive(Debug, Clone, Copy, PartialEq, Eq)]
404pub enum CoverageDimension {
405    Kind = 0,
406    Runner = 1,
407    /// Every test together: one unnamed entry, what the tests cover between
408    /// them. The run's total less this is what ran outside any test.
409    Tests = 2,
410}
411
412#[derive(Debug, Clone, PartialEq, Serialize)]
413#[serde(rename_all = "camelCase")]
414pub struct IndexedDimensionCoverage {
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub kind: Option<String>,
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub runner: Option<String>,
419    pub tests: usize,
420    pub setups: usize,
421    /// How many of `tests` the summary below actually describes. Where this is
422    /// zero and `tests` is not, the dimension ran tests whose coverage nothing
423    /// can narrow, and its summary describes none of them.
424    pub attributed: usize,
425    pub summary: CoverageSummary,
426}
427
428#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
429#[serde(rename_all = "camelCase")]
430pub struct IndexedAttribution {
431    pub browser_explicit: usize,
432    pub browser_fallback: usize,
433    pub server_explicit: usize,
434    pub server_fallback: usize,
435}
436
437#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
438#[serde(rename_all = "camelCase")]
439pub struct IndexedOutcomeCounts {
440    pub passed: usize,
441    pub failed: usize,
442    pub flaky: usize,
443    pub skipped: usize,
444    pub timed_out: usize,
445    pub interrupted: usize,
446    pub unknown: usize,
447    pub unstarted: usize,
448    /// node:test's todo tests, which the runner counts on their own: neither
449    /// passed nor failed, whatever their body did.
450    pub todo: usize,
451}
452
453#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
454#[serde(rename_all = "camelCase")]
455pub struct IndexedMeasurementKinds {
456    #[serde(rename = "dynamic-code")]
457    pub dynamic_code: usize,
458    #[serde(rename = "semantic-safety")]
459    pub semantic_safety: usize,
460    #[serde(rename = "source-scope")]
461    pub source_scope: usize,
462}
463
464#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
465#[serde(rename_all = "camelCase")]
466pub struct IndexedMeasurement {
467    pub complete: bool,
468    pub limitations: usize,
469    pub evidence_corruptions: usize,
470    pub blocking: usize,
471    /// Limitations that declare a boundary of the denominator rather than
472    /// blocking measurement inside it.
473    pub declared: usize,
474    pub files: usize,
475    pub by_kind: IndexedMeasurementKinds,
476}
477
478#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
479#[serde(rename_all = "camelCase")]
480pub struct IndexedConfidenceLines {
481    pub unexecuted: usize,
482    pub executed: usize,
483    pub action: usize,
484    pub asserted: usize,
485}
486
487#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
488#[serde(rename_all = "camelCase")]
489pub struct IndexedSummaryConfidence {
490    pub lines: IndexedConfidenceLines,
491    pub assertion_covered_mcdc_conditions: usize,
492}
493
494#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
495#[serde(rename_all = "camelCase")]
496pub struct IndexedCoverageModel {
497    pub schema_version: u32,
498    pub variant: String,
499    pub name: String,
500    pub completeness_meaning: String,
501    pub measured: Vec<String>,
502    pub not_measured: Vec<String>,
503}
504
505#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
506#[serde(rename_all = "camelCase")]
507pub struct IndexedSourceScope {
508    pub kind: String,
509    pub language: String,
510    pub model: String,
511    #[serde(skip_serializing_if = "Option::is_none")]
512    pub mode: Option<String>,
513    pub roots: Vec<String>,
514    #[serde(skip_serializing_if = "Option::is_none")]
515    pub unit: Option<String>,
516    #[serde(skip_serializing_if = "Option::is_none")]
517    pub measurement_complete: Option<bool>,
518    pub included: usize,
519    pub excluded: usize,
520    pub ambiguous: usize,
521}
522
523#[derive(Debug, Clone, PartialEq)]
524pub struct IndexedProjection {
525    pub view: CoverageViewId,
526    pub kind: Option<String>,
527    pub runner: Option<String>,
528    pub generated_at: String,
529    pub summary: CoverageSummary,
530    pub measurement: IndexedMeasurement,
531    pub attribution: IndexedAttribution,
532    pub transport: Option<TransportStats>,
533    pub empty_evidence_tests: usize,
534    pub first_empty_evidence_test: Option<String>,
535    pub confidence: IndexedSummaryConfidence,
536    pub files_with_gaps: usize,
537    pub files_with_coverage_gaps: usize,
538    pub tests: usize,
539    pub setups: usize,
540    pub test_outcomes: IndexedOutcomeCounts,
541    pub source_scope: Option<IndexedSourceScope>,
542}
543
544#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
545#[serde(rename_all = "camelCase")]
546pub struct IndexedScopeEntry {
547    pub file: String,
548    pub status: String,
549    pub reason: String,
550    #[serde(skip_serializing_if = "Option::is_none")]
551    pub package_root: Option<String>,
552    pub measurement_limitations: usize,
553    pub limitation_kinds: Vec<String>,
554}
555
556#[derive(Debug, Clone, PartialEq)]
557pub struct IndexedLine {
558    pub file: String,
559    pub line: usize,
560    pub covered: bool,
561    /// False when every obligation on the line was declined: the line stays
562    /// addressable and carries its limitation, but it is neither covered nor
563    /// uncovered.
564    pub measured: bool,
565    pub tests: Vec<String>,
566    pub phases: Vec<String>,
567    pub confidence: crate::coverage_report::CoverageConfidence,
568}
569
570#[derive(Debug, Clone, PartialEq)]
571pub struct IndexedTestSummary {
572    pub id: String,
573    pub name: String,
574    pub file: Option<String>,
575    pub title: Option<String>,
576    pub outcome: String,
577    pub role: String,
578    /// `exact`, or `run-wide` when this test ran and nothing can say what it
579    /// reached. Read before any statement about this test's coverage: an
580    /// empty hit set means "reached nothing" only under `exact`.
581    pub attribution: String,
582    pub provenance: crate::coverage_report::TestProvenance,
583}
584
585#[derive(Debug, Clone, PartialEq)]
586pub struct IndexedPhaseSummary {
587    pub id: String,
588    pub kind: String,
589    pub operation: String,
590    pub source: Option<String>,
591    pub test: String,
592    pub status: Option<String>,
593    pub caused_by_phase_id: Option<String>,
594    pub lines: usize,
595    pub decisions: usize,
596}
597
598#[derive(Debug, Clone, PartialEq)]
599pub struct IndexedAnchor {
600    pub kind: String,
601    pub id: String,
602    pub file: String,
603    pub line: usize,
604    pub column: usize,
605    pub covered: bool,
606    pub conditions: Option<usize>,
607    pub covered_conditions: Option<usize>,
608    pub tests: Vec<String>,
609}
610
611#[derive(Debug, Clone, PartialEq)]
612pub struct IndexedTestDetail {
613    pub summary: IndexedTestSummary,
614    pub retries: Vec<usize>,
615    pub attempts: Vec<crate::coverage_report::TestAttempt>,
616    pub hits: Vec<String>,
617    pub decisions: Vec<crate::coverage_report::TestDecisionResult>,
618    pub lines: Vec<crate::coverage_report::SourceLine>,
619}
620
621#[derive(Debug, Clone, PartialEq, Eq)]
622pub struct IndexedHitMetadata {
623    pub id: String,
624    pub obligation: String,
625    pub branch_kind: Option<String>,
626    pub file: String,
627    pub line: usize,
628    pub column: usize,
629    pub label: Option<String>,
630    pub alternative: Option<String>,
631    pub parent_id: Option<String>,
632    pub source: String,
633    pub tests: Vec<String>,
634}
635
636#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
637pub struct IndexedLimitation {
638    pub id: String,
639    pub kind: String,
640    pub file: String,
641    pub line: usize,
642    pub column: usize,
643    pub source: String,
644    pub reason: String,
645    /// Whether this blocks measurement of the denominator, as opposed to
646    /// declaring a boundary of it.
647    pub blocking: bool,
648}
649
650#[derive(Default)]
651struct MutableFileGap {
652    uncovered_lines: usize,
653    uncovered_statements: usize,
654    uncovered_functions: usize,
655    missing_branches: usize,
656    missing_mcdc_conditions: usize,
657    measurement_limitations: usize,
658    limitation_mask: u32,
659    covered_by_other_tests: [usize; 5],
660    uncovered_everywhere: [usize; 5],
661    totals: [usize; 5],
662}
663
664fn limitation_kind(value: &serde_json::Value) -> Option<(&str, &str)> {
665    Some((value.get("file")?.as_str()?, value.get("kind")?.as_str()?))
666}
667
668fn includes_selected<T: AsRef<str>>(
669    tests: &[T],
670    selected: Option<&Selected>,
671    covered: bool,
672) -> bool {
673    selected.map_or(covered, |selected| {
674        tests.iter().any(|test| selected.contains(test.as_ref()))
675    })
676}
677
678fn classify(
679    gap: &mut MutableFileGap,
680    dimension: usize,
681    selected: Option<&Selected>,
682    covered_overall: bool,
683) {
684    if selected.is_some() && covered_overall {
685        gap.covered_by_other_tests[dimension] += 1;
686    } else {
687        gap.uncovered_everywhere[dimension] += 1;
688    }
689}
690
691/// A file's gap, its name copied only the first time the file is met.
692fn gap_for<'m>(
693    files: &'m mut BTreeMap<String, MutableFileGap>,
694    file: &str,
695) -> &'m mut MutableFileGap {
696    if !files.contains_key(file) {
697        files.insert(file.to_owned(), MutableFileGap::default());
698    }
699    files.get_mut(file).expect("gap was inserted")
700}
701
702fn file_gaps(
703    view: &CoverageView,
704    selected: Option<&Selected>,
705) -> Result<Vec<(String, MutableFileGap)>, CoverageIndexError> {
706    let mut files = BTreeMap::<String, MutableFileGap>::new();
707    for line in &view.lines {
708        let gap = gap_for(&mut files, &line.file);
709        gap.totals[0] += usize::from(line.measured);
710        if line.measured && !includes_selected(&line.tests, selected, line.covered) {
711            gap.uncovered_lines += 1;
712            classify(gap, 0, selected, line.covered);
713        }
714    }
715    for point in &view.points {
716        let gap = gap_for(&mut files, &point.meta.file);
717        if point.measured {
718            gap.totals[match point.meta.kind {
719                crate::coverage_analysis::PointKind::Statement => 1,
720                crate::coverage_analysis::PointKind::Function => 2,
721            }] += 1;
722        }
723        if point.measured && !includes_selected(&point.tests, selected, point.covered) {
724            match point.meta.kind {
725                crate::coverage_analysis::PointKind::Statement => {
726                    gap.uncovered_statements += 1;
727                    classify(gap, 1, selected, point.covered);
728                }
729                crate::coverage_analysis::PointKind::Function => {
730                    gap.uncovered_functions += 1;
731                    classify(gap, 2, selected, point.covered);
732                }
733            }
734        }
735    }
736    for branch in &view.branches {
737        let gap = gap_for(&mut files, &branch.meta.file);
738        gap.totals[3] += branch.alternatives.len();
739        for alternative in &branch.alternatives {
740            if !includes_selected(&alternative.tests, selected, alternative.covered) {
741                gap.missing_branches += 1;
742                classify(gap, 3, selected, alternative.covered);
743            }
744        }
745    }
746    for decision in &view.decisions {
747        let gap = gap_for(&mut files, &decision.meta.file);
748        gap.totals[4] += decision.meta.conditions.len();
749        let selected_vectors = decision
750            .vector_observations
751            .iter()
752            .filter(|observation| includes_selected(&observation.tests, selected, true))
753            .map(|observation| observation.vector.clone())
754            .collect::<Vec<_>>();
755        let witnesses =
756            find_witnesses_for_conditions(&selected_vectors, decision.meta.conditions.len())
757                .map_err(|_| CoverageIndexError::InvalidRecord("MC/DC vector width"))?;
758        for (index, witness) in witnesses.into_iter().enumerate() {
759            if witness.is_none() {
760                gap.missing_mcdc_conditions += 1;
761                classify(gap, 4, selected, decision.conditions[index].covered);
762            }
763        }
764    }
765    for limitation in &view.limitations {
766        let Some((file, kind)) = limitation_kind(limitation) else {
767            continue;
768        };
769        let gap = files.entry(file.into()).or_default();
770        gap.measurement_limitations += 1;
771        gap.limitation_mask |= match kind {
772            "dynamic-code" => 1,
773            "semantic-safety" => 2,
774            "source-scope" => 4,
775            _ => 8,
776        };
777    }
778    Ok(files.into_iter().collect())
779}
780
781fn file_gap_record(
782    view_id: CoverageViewId,
783    file: &str,
784    gap: &MutableFileGap,
785    kind: Option<&str>,
786    runner: Option<&str>,
787    strings: &mut StringTable,
788) -> Result<[u8; FILE_GAP_RECORD_SIZE], CoverageIndexError> {
789    let mut record = [0_u8; FILE_GAP_RECORD_SIZE];
790    record[0] = view_id as u8;
791    put_u32(&mut record, 4, strings.intern(file)?);
792    for (index, value) in [
793        gap.uncovered_lines,
794        gap.uncovered_statements,
795        gap.uncovered_functions,
796        gap.missing_branches,
797        gap.missing_mcdc_conditions,
798        gap.measurement_limitations,
799    ]
800    .into_iter()
801    .enumerate()
802    {
803        put_u64(&mut record, 8 + index * 8, usize_u64(value)?);
804    }
805    put_u32(&mut record, 56, gap.limitation_mask);
806    let score = gap.uncovered_lines
807        + gap.uncovered_functions * 2
808        + gap.missing_branches * 2
809        + gap.missing_mcdc_conditions * 3
810        + gap.measurement_limitations * 3;
811    put_u64(&mut record, 64, usize_u64(score)?);
812    put_u32(
813        &mut record,
814        72,
815        kind.map_or(Ok(NO_STRING), |value| strings.intern(value))?,
816    );
817    put_u32(
818        &mut record,
819        76,
820        runner.map_or(Ok(NO_STRING), |value| strings.intern(value))?,
821    );
822    for (index, value) in gap.covered_by_other_tests.into_iter().enumerate() {
823        put_u64(&mut record, 80 + index * 8, usize_u64(value)?);
824    }
825    for (index, value) in gap.uncovered_everywhere.into_iter().enumerate() {
826        put_u64(&mut record, 120 + index * 8, usize_u64(value)?);
827    }
828    for (index, value) in gap.totals.into_iter().enumerate() {
829        put_u64(&mut record, 160 + index * 8, usize_u64(value)?);
830    }
831    Ok(record)
832}
833
834/// The tests a projection selects. Hashed: every line, point, branch and
835/// vector of the view asks whether any of its tests is selected, once per
836/// projection, and a tree of long test names answered each by comparing them.
837type Selected = crate::interned::FastSet<String>;
838
839fn projections(view: &CoverageView) -> Vec<(Option<String>, Option<String>, Selected)> {
840    let kinds = view
841        .tests
842        .iter()
843        .map(|test| test.provenance.kind.clone())
844        .collect::<BTreeSet<_>>();
845    let runners = view
846        .tests
847        .iter()
848        .map(|test| test.provenance.runner.clone())
849        .collect::<BTreeSet<_>>();
850    let mut selectors = Vec::new();
851    for kind in &kinds {
852        selectors.push((Some(kind.clone()), None));
853    }
854    for runner in &runners {
855        selectors.push((None, Some(runner.clone())));
856    }
857    for kind in &kinds {
858        for runner in &runners {
859            selectors.push((Some(kind.clone()), Some(runner.clone())));
860        }
861    }
862    selectors
863        .into_iter()
864        .filter_map(|(kind, runner)| {
865            let selected = view
866                .tests
867                .iter()
868                .filter(|test| {
869                    kind.as_ref()
870                        .is_none_or(|value| test.provenance.kind == *value)
871                        && runner
872                            .as_ref()
873                            .is_none_or(|value| test.provenance.runner == *value)
874                })
875                .map(|test| test.id.clone())
876                .collect::<Selected>();
877            (!selected.is_empty()).then_some((kind, runner, selected))
878        })
879        .collect()
880}
881
882fn decision_gap_record(
883    view_id: CoverageViewId,
884    decision: &crate::coverage_report::DecisionResult,
885    selected: Option<&Selected>,
886    kind: Option<&str>,
887    runner: Option<&str>,
888    strings: &mut StringTable,
889) -> Result<[u8; DECISION_GAP_RECORD_SIZE], CoverageIndexError> {
890    let vectors = decision
891        .vector_observations
892        .iter()
893        .filter(|observation| includes_selected(&observation.tests, selected, true))
894        .map(|observation| observation.vector.clone())
895        .collect::<Vec<_>>();
896    let witnesses = find_witnesses_for_conditions(&vectors, decision.meta.conditions.len())
897        .map_err(|_| CoverageIndexError::InvalidRecord("MC/DC vector width"))?;
898    let mut record = [0_u8; DECISION_GAP_RECORD_SIZE];
899    record[0] = view_id as u8;
900    put_u32(
901        &mut record,
902        4,
903        kind.map_or(Ok(NO_STRING), |value| strings.intern(value))?,
904    );
905    put_u32(
906        &mut record,
907        8,
908        runner.map_or(Ok(NO_STRING), |value| strings.intern(value))?,
909    );
910    put_u32(&mut record, 12, strings.intern(&decision.meta.id)?);
911    put_u32(&mut record, 16, strings.intern(&decision.meta.file)?);
912    put_u32(&mut record, 20, strings.intern(&decision.meta.kind)?);
913    put_u32(
914        &mut record,
915        24,
916        strings.intern(
917            &decision
918                .meta
919                .source
920                .split_whitespace()
921                .collect::<Vec<_>>()
922                .join(" "),
923        )?,
924    );
925    put_u64(&mut record, 32, usize_u64(decision.meta.line)?);
926    put_u64(&mut record, 40, usize_u64(decision.meta.column)?);
927    put_u64(&mut record, 48, usize_u64(decision.meta.conditions.len())?);
928    put_u64(
929        &mut record,
930        56,
931        usize_u64(witnesses.iter().filter(|witness| witness.is_none()).count())?,
932    );
933    Ok(record)
934}
935
936fn put_summary_payload(
937    record: &mut [u8],
938    flags_offset: usize,
939    base: usize,
940    summary: &CoverageSummary,
941) -> Result<(), CoverageIndexError> {
942    record[flags_offset] = u8::from(summary.coverage_complete);
943    record[flags_offset + 1] = match summary.completeness_blocked {
944        None => 0,
945        Some(false) => 1,
946        Some(true) => 2,
947    };
948    for (index, value) in [
949        summary.decisions,
950        summary.executed_decisions,
951        summary.covered_decisions,
952        summary.conditions,
953        summary.covered_conditions,
954    ]
955    .into_iter()
956    .enumerate()
957    {
958        put_u64(record, base + index * 8, usize_u64(value)?);
959    }
960    for (index, count) in [
961        &summary.lines,
962        &summary.statements,
963        &summary.functions,
964        &summary.branches,
965        &summary.decision_outcomes,
966        &summary.condition_outcomes,
967        &summary.value_selections,
968    ]
969    .into_iter()
970    .enumerate()
971    {
972        put_count(record, base + 40 + index * 16, count)?;
973    }
974    Ok(())
975}
976
977fn dimension_record(
978    view_id: CoverageViewId,
979    dimension: CoverageDimension,
980    value: &crate::coverage_report::DimensionCoverage,
981    strings: &mut StringTable,
982) -> Result<[u8; DIMENSION_RECORD_SIZE], CoverageIndexError> {
983    let mut record = [0_u8; DIMENSION_RECORD_SIZE];
984    record[0] = view_id as u8;
985    record[1] = dimension as u8;
986    let name = match dimension {
987        CoverageDimension::Kind => value.kind.as_deref(),
988        CoverageDimension::Runner => value.runner.as_deref(),
989        CoverageDimension::Tests => Some(""),
990    }
991    .ok_or(CoverageIndexError::InvalidRecord("dimension name"))?;
992    put_u32(&mut record, 4, strings.intern(name)?);
993    put_u64(&mut record, 8, usize_u64(value.tests)?);
994    put_u64(&mut record, 16, usize_u64(value.setups)?);
995    // Bytes 184..192 were reserved from the start and the reader refuses a
996    // record that sets them, so this is an addition rather than a format
997    // change: the record is the same 192 bytes it always was.
998    put_u64(&mut record, 184, usize_u64(value.attributed)?);
999    put_summary_payload(&mut record, 24, 32, &value.summary)?;
1000    Ok(record)
1001}
1002
1003#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1004enum ScopeKind {
1005    SourceDiscovery = 1,
1006    Compiler = 2,
1007}
1008
1009struct ScopeProjection<'a> {
1010    kind: ScopeKind,
1011    language: &'a str,
1012    model: &'a str,
1013    mode: Option<&'a str>,
1014    roots: Vec<String>,
1015    unit: Option<&'a str>,
1016    measurement_complete: Option<bool>,
1017    entries: Option<&'a Vec<serde_json::Value>>,
1018}
1019
1020fn scope_projection<'a>(
1021    scope: Option<&'a serde_json::Value>,
1022    coverage_language: &'a str,
1023    coverage_model: &'a str,
1024) -> Result<Option<ScopeProjection<'a>>, CoverageIndexError> {
1025    let Some(scope) = scope else {
1026        return Ok(None);
1027    };
1028    let object = scope
1029        .as_object()
1030        .ok_or(CoverageIndexError::InvalidRecord("coverage scope"))?;
1031    if let Some(mode) = object.get("mode") {
1032        let mode = mode
1033            .as_str()
1034            .ok_or(CoverageIndexError::InvalidRecord("source-scope mode"))?;
1035        let roots = object
1036            .get("roots")
1037            .and_then(serde_json::Value::as_array)
1038            .ok_or(CoverageIndexError::InvalidRecord("source-scope roots"))?
1039            .iter()
1040            .map(|root| {
1041                root.as_str()
1042                    .map(str::to_owned)
1043                    .ok_or(CoverageIndexError::InvalidRecord("source-scope root"))
1044            })
1045            .collect::<Result<Vec<_>, _>>()?;
1046        let entries = object
1047            .get("entries")
1048            .and_then(serde_json::Value::as_array)
1049            .ok_or(CoverageIndexError::InvalidRecord("source-scope entries"))?;
1050        return Ok(Some(ScopeProjection {
1051            kind: ScopeKind::SourceDiscovery,
1052            language: coverage_language,
1053            model: coverage_model,
1054            mode: Some(mode),
1055            roots,
1056            unit: None,
1057            measurement_complete: None,
1058            entries: Some(entries),
1059        }));
1060    }
1061
1062    let language = object
1063        .get("language")
1064        .and_then(serde_json::Value::as_str)
1065        .ok_or(CoverageIndexError::InvalidRecord("compiler-scope language"))?;
1066    let model = object
1067        .get("model")
1068        .and_then(serde_json::Value::as_str)
1069        .ok_or(CoverageIndexError::InvalidRecord("compiler-scope model"))?;
1070    let unit = object
1071        .get("crate")
1072        .and_then(serde_json::Value::as_str)
1073        .ok_or(CoverageIndexError::InvalidRecord("compiler-scope unit"))?;
1074    let measurement_complete = object
1075        .get("measurementComplete")
1076        .and_then(serde_json::Value::as_bool)
1077        .ok_or(CoverageIndexError::InvalidRecord(
1078            "compiler-scope measurement completeness",
1079        ))?;
1080    Ok(Some(ScopeProjection {
1081        kind: ScopeKind::Compiler,
1082        language,
1083        model,
1084        mode: None,
1085        roots: Vec::new(),
1086        unit: Some(unit),
1087        measurement_complete: Some(measurement_complete),
1088        entries: None,
1089    }))
1090}
1091
1092fn projection_record(
1093    view_id: CoverageViewId,
1094    view: &CoverageView,
1095    selected: Option<&Selected>,
1096    kind: Option<&str>,
1097    runner: Option<&str>,
1098    strings: &mut StringTable,
1099    relations: &mut StringRelations,
1100) -> Result<[u8; PROJECTION_RECORD_SIZE], CoverageIndexError> {
1101    let mut record = [0_u8; PROJECTION_RECORD_SIZE];
1102    record[0] = view_id as u8;
1103    put_u32(
1104        &mut record,
1105        4,
1106        kind.map_or(Ok(NO_STRING), |value| strings.intern(value))?,
1107    );
1108    put_u32(
1109        &mut record,
1110        8,
1111        runner.map_or(Ok(NO_STRING), |value| strings.intern(value))?,
1112    );
1113    put_u32(&mut record, 12, strings.intern(&view.generated_at)?);
1114
1115    let scope = scope_projection(view.scope.as_ref(), &view.model.language, &view.model.name)?;
1116    record[2] = u8::from(scope.is_some());
1117    record[3] = scope.as_ref().map_or(0, |scope| scope.kind as u8);
1118    put_u32(
1119        &mut record,
1120        16,
1121        scope
1122            .as_ref()
1123            .and_then(|scope| scope.mode)
1124            .map_or(Ok(NO_STRING), |value| strings.intern(value))?,
1125    );
1126    let roots = scope
1127        .as_ref()
1128        .map_or_else(Vec::new, |scope| scope.roots.clone());
1129    let (roots_offset, roots_count) = relations.push(roots, strings)?;
1130    put_u64(&mut record, 24, roots_offset);
1131    put_u32(
1132        &mut record,
1133        32,
1134        u32::try_from(roots_count).map_err(|_| CoverageIndexError::SizeOverflow)?,
1135    );
1136
1137    let summary = selected.map_or_else(
1138        || Ok(view.summary.clone()),
1139        |ids| {
1140            coverage_summary_for_tests(view, &ids.iter().cloned().collect())
1141                .map_err(|_| CoverageIndexError::InvalidRecord("projection summary"))
1142        },
1143    )?;
1144    put_summary_payload(&mut record, 36, 40, &summary)?;
1145
1146    let mut limitation_kinds = [0_usize; 3];
1147    let mut limitation_files = BTreeSet::new();
1148    for limitation in &view.limitations {
1149        if let Some((file, kind)) = limitation_kind(limitation) {
1150            limitation_files.insert(file);
1151            match kind {
1152                "dynamic-code" => limitation_kinds[0] += 1,
1153                "semantic-safety" => limitation_kinds[1] += 1,
1154                "source-scope" => limitation_kinds[2] += 1,
1155                _ => {}
1156            }
1157        }
1158    }
1159    let corrupt_records = view
1160        .transport
1161        .as_ref()
1162        .map_or(0, |value| value.corrupt_records);
1163    let corrupt_files = view
1164        .transport
1165        .as_ref()
1166        .map_or(0, |value| value.corrupt_files);
1167    // A limitation that declares a boundary of the denominator does not block
1168    // measurement inside it; corrupt evidence always does.
1169    let declared = view
1170        .limitations
1171        .iter()
1172        .filter(|limitation| !crate::coverage_report::blocking_limitation(limitation))
1173        .count();
1174    for (offset, value) in [
1175        (192, view.limitations.len()),
1176        (200, corrupt_records),
1177        (208, view.limitations.len() - declared + corrupt_records),
1178        (528, declared),
1179        (216, limitation_files.len() + corrupt_files),
1180        (224, limitation_kinds[0]),
1181        (232, limitation_kinds[1]),
1182        (240, limitation_kinds[2]),
1183    ] {
1184        put_u64(&mut record, offset, usize_u64(value)?);
1185    }
1186
1187    let phases = view
1188        .phases
1189        .iter()
1190        .filter(|phase| selected.is_none_or(|selected| selected.contains(phase.test.as_str())));
1191    let mut attribution = [0_usize; 4];
1192    for phase in phases {
1193        attribution[0] += phase.explicit_browser_events;
1194        attribution[1] += phase.inferred_browser_events;
1195        attribution[2] += phase.explicit_server_events;
1196        attribution[3] += phase.inferred_server_events;
1197    }
1198    for (index, value) in attribution.into_iter().enumerate() {
1199        put_u64(&mut record, 248 + index * 8, usize_u64(value)?);
1200    }
1201
1202    let confidence_levels = ["unexecuted", "executed", "action", "asserted"];
1203    for (index, level) in confidence_levels.into_iter().enumerate() {
1204        put_u64(
1205            &mut record,
1206            280 + index * 8,
1207            usize_u64(
1208                view.lines
1209                    .iter()
1210                    .filter(|line| line.confidence.level == level)
1211                    .count(),
1212            )?,
1213        );
1214    }
1215    put_u64(
1216        &mut record,
1217        312,
1218        usize_u64(
1219            view.decisions
1220                .iter()
1221                .flat_map(|decision| &decision.conditions)
1222                .filter(|condition| condition.assertion_covered)
1223                .count(),
1224        )?,
1225    );
1226
1227    let gaps = file_gaps(view, selected)?;
1228    put_u64(
1229        &mut record,
1230        320,
1231        usize_u64(
1232            gaps.iter()
1233                .filter(|(_, gap)| {
1234                    gap.uncovered_lines > 0
1235                        || gap.uncovered_statements > 0
1236                        || gap.uncovered_functions > 0
1237                        || gap.missing_branches > 0
1238                        || gap.missing_mcdc_conditions > 0
1239                        || gap.measurement_limitations > 0
1240                })
1241                .count(),
1242        )?,
1243    );
1244    put_u64(
1245        &mut record,
1246        328,
1247        usize_u64(
1248            gaps.iter()
1249                .filter(|(_, gap)| {
1250                    gap.uncovered_lines > 0
1251                        || gap.uncovered_statements > 0
1252                        || gap.uncovered_functions > 0
1253                        || gap.missing_branches > 0
1254                        || gap.missing_mcdc_conditions > 0
1255                })
1256                .count(),
1257        )?,
1258    );
1259
1260    let selected_tests = view
1261        .tests
1262        .iter()
1263        .filter(|test| selected.is_none_or(|selected| selected.contains(&test.id)))
1264        .collect::<Vec<_>>();
1265    put_u64(
1266        &mut record,
1267        336,
1268        usize_u64(
1269            selected_tests
1270                .iter()
1271                .filter(|test| test.role == "test")
1272                .count(),
1273        )?,
1274    );
1275    put_u64(
1276        &mut record,
1277        344,
1278        usize_u64(
1279            selected_tests
1280                .iter()
1281                .filter(|test| test.role == "setup")
1282                .count(),
1283        )?,
1284    );
1285    for (index, outcome) in [
1286        "passed",
1287        "failed",
1288        "flaky",
1289        "skipped",
1290        "timedOut",
1291        "interrupted",
1292        "unknown",
1293    ]
1294    .into_iter()
1295    .enumerate()
1296    {
1297        put_u64(
1298            &mut record,
1299            352 + index * 8,
1300            usize_u64(
1301                selected_tests
1302                    .iter()
1303                    .filter(|test| test.role == "test" && test.outcome == outcome)
1304                    .count(),
1305            )?,
1306        );
1307    }
1308    put_u64(
1309        &mut record,
1310        520,
1311        usize_u64(
1312            selected_tests
1313                .iter()
1314                .filter(|test| test.role == "test" && test.outcome == "unstarted")
1315                .count(),
1316        )?,
1317    );
1318
1319    if let Some(transport) = &view.transport {
1320        record[1] = 1;
1321        for (index, value) in [
1322            transport.processes,
1323            transport.child_launches,
1324            transport.remote_launches,
1325            transport.workspace_capabilities,
1326            transport.scoped_server_records,
1327            transport.background_server_records,
1328            transport.corrupt_records,
1329            transport.corrupt_files,
1330        ]
1331        .into_iter()
1332        .enumerate()
1333        {
1334            put_u64(&mut record, 408 + index * 8, usize_u64(value)?);
1335        }
1336        put_u64(&mut record, 536, usize_u64(transport.guest_processes)?);
1337    }
1338
1339    let phase_tests = view
1340        .phases
1341        .iter()
1342        .map(|phase| phase.test.as_str())
1343        .collect::<BTreeSet<_>>();
1344    let empty_tests = selected_tests
1345        .iter()
1346        .filter(|test| {
1347            test.role == "test"
1348                && test.lines.is_empty()
1349                && test.hits.is_empty()
1350                && test.decisions.is_empty()
1351                && phase_tests.contains(test.id.as_str())
1352        })
1353        .collect::<Vec<_>>();
1354    put_u64(&mut record, 472, usize_u64(empty_tests.len())?);
1355    put_u32(
1356        &mut record,
1357        20,
1358        empty_tests
1359            .first()
1360            .map_or(Ok(NO_STRING), |test| strings.intern(&test.name))?,
1361    );
1362
1363    let entries = scope.as_ref().and_then(|scope| scope.entries);
1364    for (index, status) in ["included", "excluded", "ambiguous"]
1365        .into_iter()
1366        .enumerate()
1367    {
1368        put_u64(
1369            &mut record,
1370            480 + index * 8,
1371            usize_u64(entries.map_or(0, |entries| {
1372                entries
1373                    .iter()
1374                    .filter(|entry| {
1375                        entry.get("status").and_then(serde_json::Value::as_str) == Some(status)
1376                    })
1377                    .count()
1378            }))?,
1379        );
1380    }
1381    put_u32(
1382        &mut record,
1383        504,
1384        scope
1385            .as_ref()
1386            .map_or(Ok(NO_STRING), |scope| strings.intern(scope.language))?,
1387    );
1388    put_u32(
1389        &mut record,
1390        508,
1391        scope
1392            .as_ref()
1393            .map_or(Ok(NO_STRING), |scope| strings.intern(scope.model))?,
1394    );
1395    put_u32(
1396        &mut record,
1397        512,
1398        scope
1399            .as_ref()
1400            .and_then(|scope| scope.unit)
1401            .map_or(Ok(NO_STRING), |value| strings.intern(value))?,
1402    );
1403    if let Some(measurement_complete) = scope.as_ref().and_then(|scope| scope.measurement_complete)
1404    {
1405        record[516] = 1;
1406        record[517] = u8::from(measurement_complete);
1407    }
1408    Ok(record)
1409}
1410
1411fn scope_entry_records(
1412    view_id: CoverageViewId,
1413    view: &CoverageView,
1414    strings: &mut StringTable,
1415) -> Result<Vec<[u8; SCOPE_ENTRY_RECORD_SIZE]>, CoverageIndexError> {
1416    let Some(scope) = &view.scope else {
1417        return Ok(Vec::new());
1418    };
1419    if scope.get("mode").is_none() {
1420        return Ok(Vec::new());
1421    }
1422    let entries = scope
1423        .get("entries")
1424        .and_then(serde_json::Value::as_array)
1425        .ok_or(CoverageIndexError::InvalidRecord("source-scope entries"))?;
1426    let mut limitations = BTreeMap::<&str, (usize, u32)>::new();
1427    for limitation in &view.limitations {
1428        let Some((file, kind)) = limitation_kind(limitation) else {
1429            return Err(CoverageIndexError::InvalidRecord("coverage limitation"));
1430        };
1431        let value = limitations.entry(file).or_default();
1432        value.0 += 1;
1433        value.1 |= match kind {
1434            "dynamic-code" => 1,
1435            "semantic-safety" => 2,
1436            "source-scope" => 4,
1437            _ => {
1438                return Err(CoverageIndexError::InvalidRecord(
1439                    "coverage limitation kind",
1440                ));
1441            }
1442        };
1443    }
1444    entries
1445        .iter()
1446        .map(|entry| {
1447            let file = entry
1448                .get("file")
1449                .and_then(serde_json::Value::as_str)
1450                .ok_or(CoverageIndexError::InvalidRecord("source-scope file"))?;
1451            let status = entry
1452                .get("status")
1453                .and_then(serde_json::Value::as_str)
1454                .ok_or(CoverageIndexError::InvalidRecord("source-scope status"))?;
1455            let reason = entry
1456                .get("reason")
1457                .and_then(serde_json::Value::as_str)
1458                .ok_or(CoverageIndexError::InvalidRecord("source-scope reason"))?;
1459            let package_root = entry
1460                .get("packageRoot")
1461                .map(|value| {
1462                    value.as_str().ok_or(CoverageIndexError::InvalidRecord(
1463                        "source-scope package root",
1464                    ))
1465                })
1466                .transpose()?;
1467            let mut record = [0_u8; SCOPE_ENTRY_RECORD_SIZE];
1468            record[0] = view_id as u8;
1469            record[1] = match status {
1470                "included" => 0,
1471                "excluded" => 1,
1472                "ambiguous" => 2,
1473                _ => return Err(CoverageIndexError::InvalidRecord("source-scope status")),
1474            };
1475            put_u32(&mut record, 4, strings.intern(file)?);
1476            put_u32(&mut record, 8, strings.intern(reason)?);
1477            put_u32(
1478                &mut record,
1479                12,
1480                package_root.map_or(Ok(NO_STRING), |value| strings.intern(value))?,
1481            );
1482            let (count, mask) = limitations.get(file).copied().unwrap_or_default();
1483            put_u64(&mut record, 16, usize_u64(count)?);
1484            put_u32(&mut record, 24, mask);
1485            Ok(record)
1486        })
1487        .collect()
1488}
1489
1490fn optional_string_id(
1491    value: Option<&str>,
1492    strings: &mut StringTable,
1493) -> Result<u32, CoverageIndexError> {
1494    value.map_or(Ok(NO_STRING), |value| strings.intern(value))
1495}
1496
1497fn confidence_record(
1498    confidence: &crate::coverage_report::CoverageConfidence,
1499    strings: &mut StringTable,
1500    relations: &mut StringRelations,
1501) -> Result<[u8; CONFIDENCE_RECORD_SIZE], CoverageIndexError> {
1502    let mut record = [0_u8; CONFIDENCE_RECORD_SIZE];
1503    record[0] = match confidence.level.as_str() {
1504        "unexecuted" => 0,
1505        "executed" => 1,
1506        "action" => 2,
1507        "asserted" => 3,
1508        _ => return Err(CoverageIndexError::InvalidRecord("confidence level")),
1509    };
1510    record[1] = u8::from(confidence.setup_only)
1511        | (u8::from(confidence.background_only) << 1)
1512        | (u8::from(confidence.asserted) << 2)
1513        | (u8::from(confidence.e2e) << 3);
1514    for (index, values) in [&confidence.tests, &confidence.asserted_tests]
1515        .into_iter()
1516        .enumerate()
1517    {
1518        let (offset, count) = relations.push_ids(values, strings)?;
1519        put_u64(&mut record, 8 + index * 16, offset);
1520        put_u64(&mut record, 16 + index * 16, count);
1521    }
1522    for (index, values) in [&confidence.runners, &confidence.kinds]
1523        .into_iter()
1524        .enumerate()
1525    {
1526        let (offset, count) = relations.push(values, strings)?;
1527        put_u64(&mut record, 40 + index * 16, offset);
1528        put_u64(&mut record, 48 + index * 16, count);
1529    }
1530    Ok(record)
1531}
1532
1533fn line_record(
1534    view_id: CoverageViewId,
1535    line: &crate::coverage_report::LineResult,
1536    confidence_index: usize,
1537    strings: &mut StringTable,
1538    relations: &mut StringRelations,
1539) -> Result<[u8; LINE_RECORD_SIZE], CoverageIndexError> {
1540    let mut record = [0_u8; LINE_RECORD_SIZE];
1541    record[0] = view_id as u8;
1542    record[1] = u8::from(line.covered);
1543    // Inverted: a line every frontend declined is the exception, and a zeroed
1544    // byte then still reads as measured.
1545    record[2] = u8::from(!line.measured);
1546    put_u32(&mut record, 4, strings.intern(&line.file)?);
1547    put_u64(&mut record, 8, usize_u64(line.line)?);
1548    let (tests_offset, tests_count) = relations.push_ids(&line.tests, strings)?;
1549    put_u64(&mut record, 16, tests_offset);
1550    put_u64(&mut record, 24, tests_count);
1551    let (phases_offset, phases_count) = relations.push_ids(&line.phases, strings)?;
1552    put_u64(&mut record, 32, phases_offset);
1553    put_u64(&mut record, 40, phases_count);
1554    put_u64(&mut record, 48, usize_u64(confidence_index)?);
1555    Ok(record)
1556}
1557
1558fn test_summary_record(
1559    view_id: CoverageViewId,
1560    test: &crate::coverage_report::TestCoverageResult,
1561    strings: &mut StringTable,
1562) -> Result<[u8; TEST_SUMMARY_RECORD_SIZE], CoverageIndexError> {
1563    let mut record = [0_u8; TEST_SUMMARY_RECORD_SIZE];
1564    record[0] = view_id as u8;
1565    record[1] = match test.role.as_str() {
1566        "test" => 0,
1567        "setup" => 1,
1568        "background" => 2,
1569        _ => return Err(CoverageIndexError::InvalidRecord("test role")),
1570    };
1571    // Byte 3 was reserved from the start and the reader refuses a record that
1572    // sets it, which is what makes this an addition rather than a format
1573    // change: the record is the same 64 bytes it always was.
1574    record[3] = match test.attribution.as_str() {
1575        crate::coverage_report::ATTRIBUTION_EXACT => 0,
1576        crate::coverage_report::ATTRIBUTION_RUN_WIDE => 1,
1577        crate::coverage_report::ATTRIBUTION_PARTIAL => 2,
1578        _ => return Err(CoverageIndexError::InvalidRecord("test attribution")),
1579    };
1580    record[2] = match test.outcome.as_str() {
1581        "passed" => 0,
1582        "failed" => 1,
1583        "flaky" => 2,
1584        "skipped" => 3,
1585        "timedOut" => 4,
1586        "interrupted" => 5,
1587        "unknown" => 6,
1588        "unstarted" => 7,
1589        "todo" => 8,
1590        _ => return Err(CoverageIndexError::InvalidRecord("test outcome")),
1591    };
1592    put_u32(&mut record, 4, strings.intern(&test.id)?);
1593    put_u32(&mut record, 8, strings.intern(&test.name)?);
1594    put_u32(
1595        &mut record,
1596        12,
1597        optional_string_id(test.file.as_deref(), strings)?,
1598    );
1599    put_u32(
1600        &mut record,
1601        16,
1602        optional_string_id(test.title.as_deref(), strings)?,
1603    );
1604    put_u32(&mut record, 20, strings.intern(&test.provenance.runner)?);
1605    put_u32(&mut record, 24, strings.intern(&test.provenance.kind)?);
1606    put_u32(
1607        &mut record,
1608        28,
1609        optional_string_id(test.provenance.project.as_deref(), strings)?,
1610    );
1611    put_u32(&mut record, 32, strings.intern(&test.provenance.source)?);
1612    Ok(record)
1613}
1614
1615fn phase_summary_record(
1616    view_id: CoverageViewId,
1617    phase: &crate::coverage_report::PhaseResult,
1618    strings: &mut StringTable,
1619) -> Result<[u8; PHASE_SUMMARY_RECORD_SIZE], CoverageIndexError> {
1620    let mut record = [0_u8; PHASE_SUMMARY_RECORD_SIZE];
1621    record[0] = view_id as u8;
1622    put_u32(&mut record, 4, strings.intern(&phase.phase.id)?);
1623    put_u32(&mut record, 8, strings.intern(&phase.phase.kind)?);
1624    put_u32(&mut record, 12, strings.intern(&phase.phase.operation)?);
1625    put_u32(
1626        &mut record,
1627        16,
1628        optional_string_id(phase.phase.source.as_deref(), strings)?,
1629    );
1630    put_u32(&mut record, 20, strings.intern(&phase.test)?);
1631    put_u32(
1632        &mut record,
1633        24,
1634        optional_string_id(phase.phase.status.as_deref(), strings)?,
1635    );
1636    put_u32(
1637        &mut record,
1638        28,
1639        optional_string_id(phase.phase.caused_by_phase_id.as_deref(), strings)?,
1640    );
1641    put_u64(&mut record, 32, usize_u64(phase.lines.len())?);
1642    put_u64(
1643        &mut record,
1644        40,
1645        usize_u64(
1646            phase
1647                .decisions
1648                .iter()
1649                .map(|decision| decision.vectors.len())
1650                .sum(),
1651        )?,
1652    );
1653    Ok(record)
1654}
1655
1656struct AnchorInput<'a> {
1657    view_id: CoverageViewId,
1658    kind: u8,
1659    id: &'a str,
1660    file: &'a str,
1661    line: usize,
1662    column: usize,
1663    covered: bool,
1664    conditions: Option<(usize, usize)>,
1665    tests: &'a [Id],
1666}
1667
1668fn anchor_record(
1669    input: AnchorInput<'_>,
1670    strings: &mut StringTable,
1671    relations: &mut StringRelations,
1672) -> Result<[u8; ANCHOR_RECORD_SIZE], CoverageIndexError> {
1673    let mut record = [0_u8; ANCHOR_RECORD_SIZE];
1674    record[0] = input.view_id as u8;
1675    record[1] = input.kind;
1676    record[2] = u8::from(input.covered);
1677    put_u32(&mut record, 4, strings.intern(input.id)?);
1678    put_u32(&mut record, 8, strings.intern(input.file)?);
1679    put_u64(&mut record, 16, usize_u64(input.line)?);
1680    put_u64(&mut record, 24, usize_u64(input.column)?);
1681    if let Some((covered, total)) = input.conditions {
1682        put_u64(&mut record, 32, usize_u64(total)?);
1683        put_u64(&mut record, 40, usize_u64(covered)?);
1684    }
1685    let (tests_offset, tests_count) = relations.push_ids(input.tests, strings)?;
1686    put_u64(&mut record, 48, tests_offset);
1687    put_u64(&mut record, 56, tests_count);
1688    Ok(record)
1689}
1690
1691fn test_retry_record(
1692    view_id: CoverageViewId,
1693    test_id: &str,
1694    retry: usize,
1695    strings: &mut StringTable,
1696) -> Result<[u8; TEST_RETRY_RECORD_SIZE], CoverageIndexError> {
1697    let mut record = [0_u8; TEST_RETRY_RECORD_SIZE];
1698    record[0] = view_id as u8;
1699    put_u32(&mut record, 4, strings.intern(test_id)?);
1700    put_u64(&mut record, 8, usize_u64(retry)?);
1701    Ok(record)
1702}
1703
1704fn test_attempt_record(
1705    view_id: CoverageViewId,
1706    test_id: &str,
1707    attempt: &crate::coverage_report::TestAttempt,
1708    strings: &mut StringTable,
1709) -> Result<[u8; TEST_ATTEMPT_RECORD_SIZE], CoverageIndexError> {
1710    let mut record = [0_u8; TEST_ATTEMPT_RECORD_SIZE];
1711    record[0] = view_id as u8;
1712    put_u32(&mut record, 4, strings.intern(test_id)?);
1713    put_u64(&mut record, 8, usize_u64(attempt.retry)?);
1714    put_u32(&mut record, 16, strings.intern(&attempt.status)?);
1715    put_u32(
1716        &mut record,
1717        20,
1718        optional_string_id(attempt.expected_status.as_deref(), strings)?,
1719    );
1720    Ok(record)
1721}
1722
1723fn test_line_record(
1724    view_id: CoverageViewId,
1725    test_id: &str,
1726    line: &crate::coverage_report::SourceLine,
1727    strings: &mut StringTable,
1728) -> Result<[u8; TEST_LINE_RECORD_SIZE], CoverageIndexError> {
1729    let mut record = [0_u8; TEST_LINE_RECORD_SIZE];
1730    record[0] = view_id as u8;
1731    put_u32(&mut record, 4, strings.intern(test_id)?);
1732    put_u32(&mut record, 8, strings.intern(&line.file)?);
1733    put_u64(&mut record, 16, usize_u64(line.line)?);
1734    Ok(record)
1735}
1736
1737fn test_hit_record(
1738    view_id: CoverageViewId,
1739    test_id: &str,
1740    hit: &str,
1741    strings: &mut StringTable,
1742) -> Result<[u8; TEST_HIT_RECORD_SIZE], CoverageIndexError> {
1743    let mut record = [0_u8; TEST_HIT_RECORD_SIZE];
1744    record[0] = view_id as u8;
1745    put_u32(&mut record, 4, strings.intern(test_id)?);
1746    put_u32(&mut record, 8, strings.intern(hit)?);
1747    Ok(record)
1748}
1749
1750fn test_vector_record(
1751    vector: &McdcVector,
1752    values: &mut Vec<u8>,
1753) -> Result<[u8; TEST_VECTOR_RECORD_SIZE], CoverageIndexError> {
1754    let mut record = [0_u8; TEST_VECTOR_RECORD_SIZE];
1755    record[0] = u8::from(vector.outcome);
1756    put_u64(&mut record, 8, usize_u64(values.len())?);
1757    put_u64(&mut record, 16, usize_u64(vector.values.len())?);
1758    values.extend(vector.values.iter().map(|value| match value {
1759        None => 0,
1760        Some(false) => 1,
1761        Some(true) => 2,
1762    }));
1763    Ok(record)
1764}
1765
1766fn test_decision_record(
1767    view_id: CoverageViewId,
1768    test_id: &str,
1769    decision_id: &str,
1770    vectors_offset: usize,
1771    vectors_count: usize,
1772    strings: &mut StringTable,
1773) -> Result<[u8; TEST_DECISION_RECORD_SIZE], CoverageIndexError> {
1774    let mut record = [0_u8; TEST_DECISION_RECORD_SIZE];
1775    record[0] = view_id as u8;
1776    put_u32(&mut record, 4, strings.intern(test_id)?);
1777    put_u32(&mut record, 8, strings.intern(decision_id)?);
1778    put_u64(&mut record, 16, usize_u64(vectors_offset)?);
1779    put_u64(&mut record, 24, usize_u64(vectors_count)?);
1780    Ok(record)
1781}
1782
1783struct HitMetadataInput<'a> {
1784    view_id: CoverageViewId,
1785    kind: u8,
1786    id: &'a str,
1787    file: &'a str,
1788    line: usize,
1789    column: usize,
1790    branch_kind: Option<&'a str>,
1791    label: Option<&'a str>,
1792    alternative: Option<&'a str>,
1793    source: &'a str,
1794    tests: &'a [Id],
1795}
1796
1797fn hit_metadata_record(
1798    input: HitMetadataInput<'_>,
1799    strings: &mut StringTable,
1800    relations: &mut StringRelations,
1801) -> Result<[u8; HIT_METADATA_RECORD_SIZE], CoverageIndexError> {
1802    let mut record = [0_u8; HIT_METADATA_RECORD_SIZE];
1803    record[0] = input.view_id as u8;
1804    record[1] = input.kind;
1805    put_u32(&mut record, 4, strings.intern(input.id)?);
1806    put_u32(&mut record, 8, strings.intern(input.file)?);
1807    put_u64(&mut record, 16, usize_u64(input.line)?);
1808    put_u64(&mut record, 24, usize_u64(input.column)?);
1809    put_u32(
1810        &mut record,
1811        32,
1812        optional_string_id(input.branch_kind, strings)?,
1813    );
1814    put_u32(&mut record, 36, optional_string_id(input.label, strings)?);
1815    put_u32(
1816        &mut record,
1817        40,
1818        optional_string_id(input.alternative, strings)?,
1819    );
1820    put_u32(&mut record, 44, strings.intern(input.source)?);
1821    let (tests_offset, tests_count) = relations.push_ids(input.tests, strings)?;
1822    put_u64(&mut record, 48, tests_offset);
1823    put_u64(&mut record, 56, tests_count);
1824    Ok(record)
1825}
1826
1827fn limitation_record(
1828    view_id: CoverageViewId,
1829    limitation: &serde_json::Value,
1830    strings: &mut StringTable,
1831) -> Result<[u8; LIMITATION_RECORD_SIZE], CoverageIndexError> {
1832    let field = |name| {
1833        limitation
1834            .get(name)
1835            .and_then(serde_json::Value::as_str)
1836            .ok_or(CoverageIndexError::InvalidRecord("coverage limitation"))
1837    };
1838    let number = |name| {
1839        limitation
1840            .get(name)
1841            .and_then(serde_json::Value::as_u64)
1842            .ok_or(CoverageIndexError::InvalidRecord("coverage limitation"))
1843    };
1844    let mut record = [0_u8; LIMITATION_RECORD_SIZE];
1845    record[0] = view_id as u8;
1846    record[1] = u8::from(crate::coverage_report::blocking_limitation(limitation));
1847    put_u32(&mut record, 4, strings.intern(field("id")?)?);
1848    put_u32(&mut record, 8, strings.intern(field("kind")?)?);
1849    put_u32(&mut record, 12, strings.intern(field("file")?)?);
1850    put_u32(&mut record, 16, strings.intern(field("source")?)?);
1851    put_u32(&mut record, 20, strings.intern(field("reason")?)?);
1852    put_u64(&mut record, 24, number("line")?);
1853    put_u64(&mut record, 32, number("column")?);
1854    Ok(record)
1855}
1856
1857fn decision_metadata_record(
1858    view_id: CoverageViewId,
1859    decision: &crate::coverage_report::DecisionResult,
1860    strings: &mut StringTable,
1861    relations: &mut StringRelations,
1862) -> Result<[u8; DECISION_METADATA_RECORD_SIZE], CoverageIndexError> {
1863    let mut record = [0_u8; DECISION_METADATA_RECORD_SIZE];
1864    record[0] = view_id as u8;
1865    put_u32(&mut record, 4, strings.intern(&decision.meta.id)?);
1866    put_u32(&mut record, 8, strings.intern(&decision.meta.file)?);
1867    put_u32(&mut record, 12, strings.intern(&decision.meta.source)?);
1868    put_u32(&mut record, 16, strings.intern(&decision.meta.kind)?);
1869    put_u64(&mut record, 24, usize_u64(decision.meta.line)?);
1870    put_u64(&mut record, 32, usize_u64(decision.meta.column)?);
1871    let (conditions_offset, conditions_count) =
1872        relations.push(decision.meta.conditions.clone(), strings)?;
1873    put_u64(&mut record, 40, conditions_offset);
1874    put_u64(&mut record, 48, conditions_count);
1875    Ok(record)
1876}
1877
1878fn decision_vector_observation_record(
1879    observation: &crate::coverage_report::VectorObservation,
1880    confidence_index: usize,
1881    vector_index: usize,
1882    strings: &mut StringTable,
1883    relations: &mut StringRelations,
1884) -> Result<[u8; DECISION_VECTOR_OBSERVATION_RECORD_SIZE], CoverageIndexError> {
1885    let mut record = [0_u8; DECISION_VECTOR_OBSERVATION_RECORD_SIZE];
1886    put_u64(&mut record, 0, usize_u64(confidence_index)?);
1887    put_u64(&mut record, 8, usize_u64(vector_index)?);
1888    for (offset, values) in [
1889        (16, &observation.tests),
1890        (32, &observation.phases),
1891        (48, &observation.explicit_phases),
1892    ] {
1893        let (relation_offset, relation_count) = relations.push_ids(values, strings)?;
1894        put_u64(&mut record, offset, relation_offset);
1895        put_u64(&mut record, offset + 8, relation_count);
1896    }
1897    Ok(record)
1898}
1899
1900fn decision_condition_record(
1901    condition: &crate::coverage_report::ConditionResult,
1902    witness_vectors: Option<(usize, usize)>,
1903    strings: &mut StringTable,
1904    relations: &mut StringRelations,
1905) -> Result<[u8; DECISION_CONDITION_RECORD_SIZE], CoverageIndexError> {
1906    let mut record = [0_u8; DECISION_CONDITION_RECORD_SIZE];
1907    record[0] = u8::from(condition.covered)
1908        | (u8::from(condition.assertion_covered) << 1)
1909        | (u8::from(condition.witness.is_some()) << 2);
1910    put_u32(&mut record, 4, strings.intern(&condition.source)?);
1911    put_u64(&mut record, 8, usize_u64(condition.index)?);
1912    if let Some((first, second)) = witness_vectors {
1913        put_u64(&mut record, 16, usize_u64(first)?);
1914        put_u64(&mut record, 24, usize_u64(second)?);
1915    }
1916    let empty = [Vec::new(), Vec::new()];
1917    let witness_tests = condition.witness_tests.as_ref().unwrap_or(&empty);
1918    for (offset, values) in [(32, &witness_tests[0]), (48, &witness_tests[1])] {
1919        let (relation_offset, relation_count) = relations.push_ids(values, strings)?;
1920        put_u64(&mut record, offset, relation_offset);
1921        put_u64(&mut record, offset + 8, relation_count);
1922    }
1923    Ok(record)
1924}
1925
1926struct DecisionDetailInput<'a> {
1927    view_id: CoverageViewId,
1928    decision: &'a crate::coverage_report::DecisionResult,
1929    confidence_index: usize,
1930    observations: (usize, usize),
1931    conditions: (usize, usize),
1932}
1933
1934fn decision_detail_record(
1935    input: DecisionDetailInput<'_>,
1936    strings: &mut StringTable,
1937    relations: &mut StringRelations,
1938) -> Result<[u8; DECISION_DETAIL_RECORD_SIZE], CoverageIndexError> {
1939    let mut record = [0_u8; DECISION_DETAIL_RECORD_SIZE];
1940    record[0] = input.view_id as u8;
1941    record[1] = u8::from(input.decision.executed) | (u8::from(input.decision.covered) << 1);
1942    put_u32(&mut record, 4, strings.intern(&input.decision.meta.id)?);
1943    put_u64(&mut record, 8, usize_u64(input.confidence_index)?);
1944    let (tests_offset, tests_count) = relations.push_ids(&input.decision.tests, strings)?;
1945    put_u64(&mut record, 16, tests_offset);
1946    put_u64(&mut record, 24, tests_count);
1947    put_u64(&mut record, 32, usize_u64(input.observations.0)?);
1948    put_u64(&mut record, 40, usize_u64(input.observations.1)?);
1949    put_u64(&mut record, 48, usize_u64(input.conditions.0)?);
1950    put_u64(&mut record, 56, usize_u64(input.conditions.1)?);
1951    Ok(record)
1952}
1953
1954fn coverage_model_record(
1955    variant: &str,
1956    model: &CoverageModel,
1957    strings: &mut StringTable,
1958    relations: &mut StringRelations,
1959) -> Result<[u8; COVERAGE_MODEL_RECORD_SIZE], CoverageIndexError> {
1960    let mut record = [0_u8; COVERAGE_MODEL_RECORD_SIZE];
1961    put_u32(&mut record, 0, strings.intern(variant)?);
1962    put_u32(&mut record, 4, strings.intern(&model.name)?);
1963    put_u32(&mut record, 8, strings.intern(&model.completeness_meaning)?);
1964    let (measured_offset, measured_count) = relations.push(model.measured.clone(), strings)?;
1965    put_u64(&mut record, 16, measured_offset);
1966    put_u64(&mut record, 24, measured_count);
1967    let (not_measured_offset, not_measured_count) =
1968        relations.push(model.not_measured.clone(), strings)?;
1969    put_u64(&mut record, 32, not_measured_offset);
1970    put_u64(&mut record, 40, not_measured_count);
1971    Ok(record)
1972}
1973
1974pub fn coverage_index_sections(
1975    report: &CoverageReport,
1976) -> Result<Vec<QueryIndexSection>, CoverageIndexError> {
1977    let views = [
1978        (CoverageViewId::All, &report.view),
1979        (CoverageViewId::Passed, &report.filters.passed),
1980        (CoverageViewId::Failed, &report.filters.failed),
1981    ];
1982    let mut strings = StringTable::default();
1983    let mut relations = StringRelations::default();
1984    let mut summaries = Vec::with_capacity(views.len() * SUMMARY_RECORD_SIZE);
1985    let mut gaps = Vec::new();
1986    let mut decision_gaps = Vec::new();
1987    let mut dimensions = Vec::new();
1988    let mut projection_records = Vec::new();
1989    let mut scope_entries = Vec::new();
1990    let mut confidence_records = Vec::new();
1991    let mut line_records = Vec::new();
1992    let mut test_summaries = Vec::new();
1993    let mut phase_summaries = Vec::new();
1994    let mut anchors = Vec::new();
1995    let mut test_retries = Vec::new();
1996    let mut test_attempts = Vec::new();
1997    let mut test_lines = Vec::new();
1998    let mut test_hits = Vec::new();
1999    let mut test_decisions = Vec::new();
2000    let mut test_vectors = Vec::new();
2001    let mut vector_values = Vec::new();
2002    let mut hit_metadata = Vec::new();
2003    let mut decision_metadata = Vec::new();
2004    let mut decision_details = Vec::new();
2005    let mut decision_vector_observations = Vec::new();
2006    let mut decision_conditions = Vec::new();
2007    let mut limitations = Vec::new();
2008    for (id, view) in views {
2009        summaries.extend_from_slice(&summary_record(id, view, &mut strings)?);
2010        projection_records.extend_from_slice(&projection_record(
2011            id,
2012            view,
2013            None,
2014            None,
2015            None,
2016            &mut strings,
2017            &mut relations,
2018        )?);
2019        for entry in scope_entry_records(id, view, &mut strings)? {
2020            scope_entries.extend_from_slice(&entry);
2021        }
2022        for limitation in &view.limitations {
2023            limitations.extend_from_slice(&limitation_record(id, limitation, &mut strings)?);
2024        }
2025        for line in &view.lines {
2026            let confidence_index = confidence_records.len() / CONFIDENCE_RECORD_SIZE;
2027            confidence_records.extend_from_slice(&confidence_record(
2028                &line.confidence,
2029                &mut strings,
2030                &mut relations,
2031            )?);
2032            line_records.extend_from_slice(&line_record(
2033                id,
2034                line,
2035                confidence_index,
2036                &mut strings,
2037                &mut relations,
2038            )?);
2039        }
2040        for test in &view.tests {
2041            test_summaries.extend_from_slice(&test_summary_record(id, test, &mut strings)?);
2042            for retry in &test.retries {
2043                test_retries.extend_from_slice(&test_retry_record(
2044                    id,
2045                    &test.id,
2046                    *retry,
2047                    &mut strings,
2048                )?);
2049            }
2050            for attempt in &test.attempts {
2051                test_attempts.extend_from_slice(&test_attempt_record(
2052                    id,
2053                    &test.id,
2054                    attempt,
2055                    &mut strings,
2056                )?);
2057            }
2058            for line in &test.lines {
2059                test_lines.extend_from_slice(&test_line_record(id, &test.id, line, &mut strings)?);
2060            }
2061            for hit in &test.hits {
2062                test_hits.extend_from_slice(&test_hit_record(id, &test.id, hit, &mut strings)?);
2063            }
2064            for decision in &test.decisions {
2065                let vectors_offset = test_vectors.len() / TEST_VECTOR_RECORD_SIZE;
2066                for vector in &decision.vectors {
2067                    test_vectors
2068                        .extend_from_slice(&test_vector_record(vector, &mut vector_values)?);
2069                }
2070                test_decisions.extend_from_slice(&test_decision_record(
2071                    id,
2072                    &test.id,
2073                    &decision.id,
2074                    vectors_offset,
2075                    decision.vectors.len(),
2076                    &mut strings,
2077                )?);
2078            }
2079        }
2080        for phase in &view.phases {
2081            phase_summaries.extend_from_slice(&phase_summary_record(id, phase, &mut strings)?);
2082        }
2083        for decision in &view.decisions {
2084            let confidence_index = confidence_records.len() / CONFIDENCE_RECORD_SIZE;
2085            confidence_records.extend_from_slice(&confidence_record(
2086                &decision.confidence,
2087                &mut strings,
2088                &mut relations,
2089            )?);
2090            let observations_offset =
2091                decision_vector_observations.len() / DECISION_VECTOR_OBSERVATION_RECORD_SIZE;
2092            for observation in &decision.vector_observations {
2093                let observation_confidence = confidence_records.len() / CONFIDENCE_RECORD_SIZE;
2094                confidence_records.extend_from_slice(&confidence_record(
2095                    &observation.confidence,
2096                    &mut strings,
2097                    &mut relations,
2098                )?);
2099                let vector_index = test_vectors.len() / TEST_VECTOR_RECORD_SIZE;
2100                test_vectors.extend_from_slice(&test_vector_record(
2101                    &observation.vector,
2102                    &mut vector_values,
2103                )?);
2104                decision_vector_observations.extend_from_slice(
2105                    &decision_vector_observation_record(
2106                        observation,
2107                        observation_confidence,
2108                        vector_index,
2109                        &mut strings,
2110                        &mut relations,
2111                    )?,
2112                );
2113            }
2114            let conditions_offset = decision_conditions.len() / DECISION_CONDITION_RECORD_SIZE;
2115            for condition in &decision.conditions {
2116                let witness_vectors = if let Some(witness) = &condition.witness {
2117                    let first = test_vectors.len() / TEST_VECTOR_RECORD_SIZE;
2118                    test_vectors
2119                        .extend_from_slice(&test_vector_record(&witness[0], &mut vector_values)?);
2120                    let second = test_vectors.len() / TEST_VECTOR_RECORD_SIZE;
2121                    test_vectors
2122                        .extend_from_slice(&test_vector_record(&witness[1], &mut vector_values)?);
2123                    Some((first, second))
2124                } else {
2125                    None
2126                };
2127                decision_conditions.extend_from_slice(&decision_condition_record(
2128                    condition,
2129                    witness_vectors,
2130                    &mut strings,
2131                    &mut relations,
2132                )?);
2133            }
2134            decision_details.extend_from_slice(&decision_detail_record(
2135                DecisionDetailInput {
2136                    view_id: id,
2137                    decision,
2138                    confidence_index,
2139                    observations: (observations_offset, decision.vector_observations.len()),
2140                    conditions: (conditions_offset, decision.conditions.len()),
2141                },
2142                &mut strings,
2143                &mut relations,
2144            )?);
2145            decision_metadata.extend_from_slice(&decision_metadata_record(
2146                id,
2147                decision,
2148                &mut strings,
2149                &mut relations,
2150            )?);
2151            anchors.extend_from_slice(&anchor_record(
2152                AnchorInput {
2153                    view_id: id,
2154                    kind: 0,
2155                    id: &decision.meta.id,
2156                    file: &decision.meta.file,
2157                    line: decision.meta.line,
2158                    column: decision.meta.column,
2159                    covered: decision.covered,
2160                    conditions: Some((
2161                        decision
2162                            .conditions
2163                            .iter()
2164                            .filter(|condition| condition.covered)
2165                            .count(),
2166                        decision.conditions.len(),
2167                    )),
2168                    tests: &decision.tests,
2169                },
2170                &mut strings,
2171                &mut relations,
2172            )?);
2173        }
2174        for branch in &view.branches {
2175            anchors.extend_from_slice(&anchor_record(
2176                AnchorInput {
2177                    view_id: id,
2178                    kind: 1,
2179                    id: &branch.meta.id,
2180                    file: &branch.meta.file,
2181                    line: branch.meta.line,
2182                    column: branch.meta.column,
2183                    covered: branch.covered,
2184                    conditions: None,
2185                    tests: &[],
2186                },
2187                &mut strings,
2188                &mut relations,
2189            )?);
2190            for alternative in &branch.alternatives {
2191                hit_metadata.extend_from_slice(&hit_metadata_record(
2192                    HitMetadataInput {
2193                        view_id: id,
2194                        kind: 2,
2195                        id: &alternative.id,
2196                        file: &branch.meta.file,
2197                        line: branch.meta.line,
2198                        column: branch.meta.column,
2199                        branch_kind: Some(&branch.meta.kind),
2200                        label: Some(&branch.meta.id),
2201                        alternative: Some(&alternative.label),
2202                        source: &branch.meta.source,
2203                        tests: &alternative.tests,
2204                    },
2205                    &mut strings,
2206                    &mut relations,
2207                )?);
2208            }
2209        }
2210        for point in &view.points {
2211            anchors.extend_from_slice(&anchor_record(
2212                AnchorInput {
2213                    view_id: id,
2214                    kind: match point.meta.kind {
2215                        crate::coverage_analysis::PointKind::Statement => 2,
2216                        crate::coverage_analysis::PointKind::Function => 3,
2217                    },
2218                    id: &point.meta.id,
2219                    file: &point.meta.file,
2220                    line: point.meta.line,
2221                    column: point.meta.column,
2222                    covered: point.covered,
2223                    conditions: None,
2224                    tests: &point.tests,
2225                },
2226                &mut strings,
2227                &mut relations,
2228            )?);
2229            hit_metadata.extend_from_slice(&hit_metadata_record(
2230                HitMetadataInput {
2231                    view_id: id,
2232                    kind: match point.meta.kind {
2233                        crate::coverage_analysis::PointKind::Statement => 0,
2234                        crate::coverage_analysis::PointKind::Function => 1,
2235                    },
2236                    id: &point.meta.id,
2237                    file: &point.meta.file,
2238                    line: point.meta.line,
2239                    column: point.meta.column,
2240                    branch_kind: None,
2241                    label: point.meta.label.as_deref(),
2242                    alternative: None,
2243                    source: &point.meta.source,
2244                    tests: &point.tests,
2245                },
2246                &mut strings,
2247                &mut relations,
2248            )?);
2249        }
2250        for value in &view.coverage_by_kind {
2251            dimensions.extend_from_slice(&dimension_record(
2252                id,
2253                CoverageDimension::Kind,
2254                value,
2255                &mut strings,
2256            )?);
2257        }
2258        for value in &view.coverage_by_runner {
2259            dimensions.extend_from_slice(&dimension_record(
2260                id,
2261                CoverageDimension::Runner,
2262                value,
2263                &mut strings,
2264            )?);
2265        }
2266        let role = |role: &str| view.tests.iter().filter(|test| test.role == role).count();
2267        dimensions.extend_from_slice(&dimension_record(
2268            id,
2269            CoverageDimension::Tests,
2270            &crate::coverage_report::DimensionCoverage {
2271                kind: None,
2272                runner: None,
2273                tests: role("test"),
2274                setups: role("setup"),
2275                attributed: view
2276                    .tests
2277                    .iter()
2278                    .filter(|test| {
2279                        test.role == "test"
2280                            && crate::coverage_report::coverage_is_its_own(&test.attribution)
2281                    })
2282                    .count(),
2283                summary: crate::coverage_report::summary_for_tests(view)
2284                    .map_err(|_| CoverageIndexError::InvalidRecord("coverage of the tests"))?,
2285            },
2286            &mut strings,
2287        )?);
2288        for decision in &view.decisions {
2289            decision_gaps.extend_from_slice(&decision_gap_record(
2290                id,
2291                decision,
2292                None,
2293                None,
2294                None,
2295                &mut strings,
2296            )?);
2297        }
2298        for (file, gap) in file_gaps(view, None)? {
2299            gaps.extend_from_slice(&file_gap_record(id, &file, &gap, None, None, &mut strings)?);
2300        }
2301        for (kind, runner, selected) in projections(view) {
2302            projection_records.extend_from_slice(&projection_record(
2303                id,
2304                view,
2305                Some(&selected),
2306                kind.as_deref(),
2307                runner.as_deref(),
2308                &mut strings,
2309                &mut relations,
2310            )?);
2311            for decision in &view.decisions {
2312                decision_gaps.extend_from_slice(&decision_gap_record(
2313                    id,
2314                    decision,
2315                    Some(&selected),
2316                    kind.as_deref(),
2317                    runner.as_deref(),
2318                    &mut strings,
2319                )?);
2320            }
2321            for (file, gap) in file_gaps(view, Some(&selected))? {
2322                gaps.extend_from_slice(&file_gap_record(
2323                    id,
2324                    &file,
2325                    &gap,
2326                    kind.as_deref(),
2327                    runner.as_deref(),
2328                    &mut strings,
2329                )?);
2330            }
2331        }
2332    }
2333    let model = coverage_model_record(
2334        &report.view.variant,
2335        &report.view.model,
2336        &mut strings,
2337        &mut relations,
2338    )?;
2339    let [blob, string_records] = strings.sections()?;
2340    let string_relations = relations.section()?;
2341    Ok(vec![
2342        blob,
2343        string_records,
2344        string_relations,
2345        QueryIndexSection {
2346            kind: SECTION_VIEW_SUMMARIES,
2347            record_size: SUMMARY_RECORD_SIZE as u32,
2348            count: usize_u64(summaries.len() / SUMMARY_RECORD_SIZE)?,
2349            bytes: summaries,
2350        },
2351        QueryIndexSection {
2352            kind: SECTION_FILE_GAPS,
2353            record_size: FILE_GAP_RECORD_SIZE as u32,
2354            count: usize_u64(gaps.len() / FILE_GAP_RECORD_SIZE)?,
2355            bytes: gaps,
2356        },
2357        QueryIndexSection {
2358            kind: SECTION_DECISION_GAPS,
2359            record_size: DECISION_GAP_RECORD_SIZE as u32,
2360            count: usize_u64(decision_gaps.len() / DECISION_GAP_RECORD_SIZE)?,
2361            bytes: decision_gaps,
2362        },
2363        QueryIndexSection {
2364            kind: SECTION_DIMENSIONS,
2365            record_size: DIMENSION_RECORD_SIZE as u32,
2366            count: usize_u64(dimensions.len() / DIMENSION_RECORD_SIZE)?,
2367            bytes: dimensions,
2368        },
2369        QueryIndexSection {
2370            kind: SECTION_PROJECTIONS,
2371            record_size: PROJECTION_RECORD_SIZE as u32,
2372            count: usize_u64(projection_records.len() / PROJECTION_RECORD_SIZE)?,
2373            bytes: projection_records,
2374        },
2375        QueryIndexSection {
2376            kind: SECTION_SCOPE_ENTRIES,
2377            record_size: SCOPE_ENTRY_RECORD_SIZE as u32,
2378            count: usize_u64(scope_entries.len() / SCOPE_ENTRY_RECORD_SIZE)?,
2379            bytes: scope_entries,
2380        },
2381        QueryIndexSection {
2382            kind: SECTION_CONFIDENCE,
2383            record_size: CONFIDENCE_RECORD_SIZE as u32,
2384            count: usize_u64(confidence_records.len() / CONFIDENCE_RECORD_SIZE)?,
2385            bytes: confidence_records,
2386        },
2387        QueryIndexSection {
2388            kind: SECTION_LINES,
2389            record_size: LINE_RECORD_SIZE as u32,
2390            count: usize_u64(line_records.len() / LINE_RECORD_SIZE)?,
2391            bytes: line_records,
2392        },
2393        QueryIndexSection {
2394            kind: SECTION_TEST_SUMMARIES,
2395            record_size: TEST_SUMMARY_RECORD_SIZE as u32,
2396            count: usize_u64(test_summaries.len() / TEST_SUMMARY_RECORD_SIZE)?,
2397            bytes: test_summaries,
2398        },
2399        QueryIndexSection {
2400            kind: SECTION_PHASE_SUMMARIES,
2401            record_size: PHASE_SUMMARY_RECORD_SIZE as u32,
2402            count: usize_u64(phase_summaries.len() / PHASE_SUMMARY_RECORD_SIZE)?,
2403            bytes: phase_summaries,
2404        },
2405        QueryIndexSection {
2406            kind: SECTION_ANCHORS,
2407            record_size: ANCHOR_RECORD_SIZE as u32,
2408            count: usize_u64(anchors.len() / ANCHOR_RECORD_SIZE)?,
2409            bytes: anchors,
2410        },
2411        QueryIndexSection {
2412            kind: SECTION_TEST_RETRIES,
2413            record_size: TEST_RETRY_RECORD_SIZE as u32,
2414            count: usize_u64(test_retries.len() / TEST_RETRY_RECORD_SIZE)?,
2415            bytes: test_retries,
2416        },
2417        QueryIndexSection {
2418            kind: SECTION_TEST_ATTEMPTS,
2419            record_size: TEST_ATTEMPT_RECORD_SIZE as u32,
2420            count: usize_u64(test_attempts.len() / TEST_ATTEMPT_RECORD_SIZE)?,
2421            bytes: test_attempts,
2422        },
2423        QueryIndexSection {
2424            kind: SECTION_TEST_LINES,
2425            record_size: TEST_LINE_RECORD_SIZE as u32,
2426            count: usize_u64(test_lines.len() / TEST_LINE_RECORD_SIZE)?,
2427            bytes: test_lines,
2428        },
2429        QueryIndexSection {
2430            kind: SECTION_TEST_HITS,
2431            record_size: TEST_HIT_RECORD_SIZE as u32,
2432            count: usize_u64(test_hits.len() / TEST_HIT_RECORD_SIZE)?,
2433            bytes: test_hits,
2434        },
2435        QueryIndexSection {
2436            kind: SECTION_TEST_DECISIONS,
2437            record_size: TEST_DECISION_RECORD_SIZE as u32,
2438            count: usize_u64(test_decisions.len() / TEST_DECISION_RECORD_SIZE)?,
2439            bytes: test_decisions,
2440        },
2441        QueryIndexSection {
2442            kind: SECTION_TEST_VECTORS,
2443            record_size: TEST_VECTOR_RECORD_SIZE as u32,
2444            count: usize_u64(test_vectors.len() / TEST_VECTOR_RECORD_SIZE)?,
2445            bytes: test_vectors,
2446        },
2447        QueryIndexSection {
2448            kind: SECTION_VECTOR_VALUES,
2449            record_size: 1,
2450            count: usize_u64(vector_values.len())?,
2451            bytes: vector_values,
2452        },
2453        QueryIndexSection {
2454            kind: SECTION_HIT_METADATA,
2455            record_size: HIT_METADATA_RECORD_SIZE as u32,
2456            count: usize_u64(hit_metadata.len() / HIT_METADATA_RECORD_SIZE)?,
2457            bytes: hit_metadata,
2458        },
2459        QueryIndexSection {
2460            kind: SECTION_DECISION_METADATA,
2461            record_size: DECISION_METADATA_RECORD_SIZE as u32,
2462            count: usize_u64(decision_metadata.len() / DECISION_METADATA_RECORD_SIZE)?,
2463            bytes: decision_metadata,
2464        },
2465        QueryIndexSection {
2466            kind: SECTION_DECISION_DETAILS,
2467            record_size: DECISION_DETAIL_RECORD_SIZE as u32,
2468            count: usize_u64(decision_details.len() / DECISION_DETAIL_RECORD_SIZE)?,
2469            bytes: decision_details,
2470        },
2471        QueryIndexSection {
2472            kind: SECTION_DECISION_VECTOR_OBSERVATIONS,
2473            record_size: DECISION_VECTOR_OBSERVATION_RECORD_SIZE as u32,
2474            count: usize_u64(
2475                decision_vector_observations.len() / DECISION_VECTOR_OBSERVATION_RECORD_SIZE,
2476            )?,
2477            bytes: decision_vector_observations,
2478        },
2479        QueryIndexSection {
2480            kind: SECTION_DECISION_CONDITIONS,
2481            record_size: DECISION_CONDITION_RECORD_SIZE as u32,
2482            count: usize_u64(decision_conditions.len() / DECISION_CONDITION_RECORD_SIZE)?,
2483            bytes: decision_conditions,
2484        },
2485        QueryIndexSection {
2486            kind: SECTION_LIMITATIONS,
2487            record_size: LIMITATION_RECORD_SIZE as u32,
2488            count: usize_u64(limitations.len() / LIMITATION_RECORD_SIZE)?,
2489            bytes: limitations,
2490        },
2491        QueryIndexSection {
2492            kind: SECTION_COVERAGE_MODEL,
2493            record_size: COVERAGE_MODEL_RECORD_SIZE as u32,
2494            count: 1,
2495            bytes: model.to_vec(),
2496        },
2497    ])
2498}
2499
2500pub struct CoverageIndex<'a> {
2501    index: &'a QueryIndex,
2502}
2503
2504impl<'a> CoverageIndex<'a> {
2505    pub fn new(index: &'a QueryIndex) -> Result<Self, CoverageIndexError> {
2506        for (kind, size) in [
2507            (SECTION_STRINGS, STRING_RECORD_SIZE),
2508            (SECTION_VIEW_SUMMARIES, SUMMARY_RECORD_SIZE),
2509            (SECTION_FILE_GAPS, FILE_GAP_RECORD_SIZE),
2510            (SECTION_DECISION_GAPS, DECISION_GAP_RECORD_SIZE),
2511            (SECTION_DIMENSIONS, DIMENSION_RECORD_SIZE),
2512            (SECTION_PROJECTIONS, PROJECTION_RECORD_SIZE),
2513            (SECTION_SCOPE_ENTRIES, SCOPE_ENTRY_RECORD_SIZE),
2514            (SECTION_CONFIDENCE, CONFIDENCE_RECORD_SIZE),
2515            (SECTION_LINES, LINE_RECORD_SIZE),
2516            (SECTION_TEST_SUMMARIES, TEST_SUMMARY_RECORD_SIZE),
2517            (SECTION_PHASE_SUMMARIES, PHASE_SUMMARY_RECORD_SIZE),
2518            (SECTION_ANCHORS, ANCHOR_RECORD_SIZE),
2519            (SECTION_TEST_RETRIES, TEST_RETRY_RECORD_SIZE),
2520            (SECTION_TEST_ATTEMPTS, TEST_ATTEMPT_RECORD_SIZE),
2521            (SECTION_TEST_LINES, TEST_LINE_RECORD_SIZE),
2522            (SECTION_TEST_HITS, TEST_HIT_RECORD_SIZE),
2523            (SECTION_TEST_DECISIONS, TEST_DECISION_RECORD_SIZE),
2524            (SECTION_TEST_VECTORS, TEST_VECTOR_RECORD_SIZE),
2525            (SECTION_VECTOR_VALUES, 1),
2526            (SECTION_HIT_METADATA, HIT_METADATA_RECORD_SIZE),
2527            (SECTION_DECISION_METADATA, DECISION_METADATA_RECORD_SIZE),
2528            (SECTION_DECISION_DETAILS, DECISION_DETAIL_RECORD_SIZE),
2529            (
2530                SECTION_DECISION_VECTOR_OBSERVATIONS,
2531                DECISION_VECTOR_OBSERVATION_RECORD_SIZE,
2532            ),
2533            (SECTION_DECISION_CONDITIONS, DECISION_CONDITION_RECORD_SIZE),
2534            (SECTION_LIMITATIONS, LIMITATION_RECORD_SIZE),
2535            (SECTION_COVERAGE_MODEL, COVERAGE_MODEL_RECORD_SIZE),
2536        ] {
2537            if index.descriptor(kind)?.record_size as usize != size {
2538                return Err(CoverageIndexError::InvalidRecord("record size"));
2539            }
2540        }
2541        index.descriptor(SECTION_STRING_BYTES)?;
2542        if index.descriptor(SECTION_STRING_RELATIONS)?.record_size != 4 {
2543            return Err(CoverageIndexError::InvalidRecord(
2544                "string-relation record size",
2545            ));
2546        }
2547        Ok(Self { index })
2548    }
2549
2550    pub fn model(&self) -> Result<IndexedCoverageModel, CoverageIndexError> {
2551        let descriptor = self.index.descriptor(SECTION_COVERAGE_MODEL)?;
2552        if descriptor.count != 1 {
2553            return Err(CoverageIndexError::InvalidRecord(
2554                "coverage model record count",
2555            ));
2556        }
2557        let record = self.index.record(SECTION_COVERAGE_MODEL, 0)?;
2558        if record[12..16].iter().any(|byte| *byte != 0) {
2559            return Err(CoverageIndexError::InvalidRecord(
2560                "coverage model reserved bytes",
2561            ));
2562        }
2563        Ok(IndexedCoverageModel {
2564            schema_version: COVERAGE_MODEL_SCHEMA_VERSION,
2565            variant: self.string(get_u32(record, 0))?,
2566            name: self.string(get_u32(record, 4))?,
2567            completeness_meaning: self.string(get_u32(record, 8))?,
2568            measured: self.relation_strings(get_u64(record, 16), get_u64(record, 24))?,
2569            not_measured: self.relation_strings(get_u64(record, 32), get_u64(record, 40))?,
2570        })
2571    }
2572
2573    fn string(&self, id: u32) -> Result<String, CoverageIndexError> {
2574        let record = self.index.record(SECTION_STRINGS, u64::from(id))?;
2575        if record[12..].iter().any(|byte| *byte != 0) {
2576            return Err(CoverageIndexError::InvalidRecord("string reserved bytes"));
2577        }
2578        let offset = get_u64(record, 0);
2579        let length = u64::from(get_u32(record, 8));
2580        let value = self.index.bytes(SECTION_STRING_BYTES, offset, length)?;
2581        std::str::from_utf8(value)
2582            .map(str::to_owned)
2583            .map_err(|_| CoverageIndexError::InvalidUtf8)
2584    }
2585
2586    pub fn summary(&self, view: CoverageViewId) -> Result<CoverageSummary, CoverageIndexError> {
2587        let descriptor = self.index.descriptor(SECTION_VIEW_SUMMARIES)?;
2588        if descriptor.count != 3 {
2589            return Err(CoverageIndexError::InvalidRecord("summary view count"));
2590        }
2591        let mut found = None;
2592        for index in 0..descriptor.count {
2593            let record = self.index.record(SECTION_VIEW_SUMMARIES, index)?;
2594            if CoverageViewId::try_from(record[0])? != view {
2595                continue;
2596            }
2597            if record[3] != 0 || record[12..16].iter().any(|byte| *byte != 0) {
2598                return Err(CoverageIndexError::InvalidRecord("summary reserved bytes"));
2599            }
2600            self.string(get_u32(record, 4))?;
2601            self.string(get_u32(record, 8))?;
2602            let count = |offset: usize| -> Result<CoverageCount, CoverageIndexError> {
2603                let covered = usize::try_from(get_u64(record, offset))
2604                    .map_err(|_| CoverageIndexError::SizeOverflow)?;
2605                let total = usize::try_from(get_u64(record, offset + 8))
2606                    .map_err(|_| CoverageIndexError::SizeOverflow)?;
2607                if covered > total {
2608                    return Err(CoverageIndexError::InvalidRecord("covered exceeds total"));
2609                }
2610                Ok(CoverageCount {
2611                    covered,
2612                    total,
2613                    percentage: percentage(covered, total),
2614                })
2615            };
2616            let value = |offset: usize| -> Result<usize, CoverageIndexError> {
2617                usize::try_from(get_u64(record, offset))
2618                    .map_err(|_| CoverageIndexError::SizeOverflow)
2619            };
2620            let conditions = value(40)?;
2621            let covered_conditions = value(48)?;
2622            if covered_conditions > conditions {
2623                return Err(CoverageIndexError::InvalidRecord(
2624                    "covered conditions exceed total",
2625                ));
2626            }
2627            let decisions = value(16)?;
2628            let executed_decisions = value(24)?;
2629            let covered_decisions = value(32)?;
2630            if covered_decisions > executed_decisions || executed_decisions > decisions {
2631                return Err(CoverageIndexError::InvalidRecord("decision count ordering"));
2632            }
2633            let summary = CoverageSummary {
2634                unmeasured_obligations: None,
2635                exact_fraction_pct: None,
2636                decisions,
2637                executed_decisions,
2638                covered_decisions,
2639                conditions,
2640                covered_conditions,
2641                condition_coverage_pct: percentage(covered_conditions, conditions),
2642                lines: count(56)?,
2643                statements: count(72)?,
2644                functions: count(88)?,
2645                branches: count(104)?,
2646                decision_outcomes: count(120)?,
2647                condition_outcomes: count(136)?,
2648                value_selections: count(152)?,
2649                coverage_complete: bool_field(record[1])?,
2650                completeness_blocked: match record[2] {
2651                    0 => None,
2652                    1 => Some(false),
2653                    2 => Some(true),
2654                    _ => return Err(CoverageIndexError::InvalidRecord("optional boolean")),
2655                },
2656            };
2657            if found.replace(summary).is_some() {
2658                return Err(CoverageIndexError::InvalidRecord("duplicate coverage view"));
2659            }
2660        }
2661        found.ok_or(CoverageIndexError::InvalidRecord("missing coverage view"))
2662    }
2663
2664    pub fn file_gaps(
2665        &self,
2666        view: CoverageViewId,
2667        kind: Option<&str>,
2668        runner: Option<&str>,
2669    ) -> Result<Vec<IndexedFileGap>, CoverageIndexError> {
2670        let descriptor = self.index.descriptor(SECTION_FILE_GAPS)?;
2671        let mut gaps = Vec::new();
2672        for index in 0..descriptor.count {
2673            let record = self.index.record(SECTION_FILE_GAPS, index)?;
2674            if CoverageViewId::try_from(record[0])? != view {
2675                continue;
2676            }
2677            if record[1..4].iter().any(|byte| *byte != 0)
2678                || record[60..64].iter().any(|byte| *byte != 0)
2679            {
2680                return Err(CoverageIndexError::InvalidRecord("file-gap reserved bytes"));
2681            }
2682            let number = |offset: usize| -> Result<usize, CoverageIndexError> {
2683                usize::try_from(get_u64(record, offset))
2684                    .map_err(|_| CoverageIndexError::SizeOverflow)
2685            };
2686            let mask = get_u32(record, 56);
2687            if mask & !15 != 0 {
2688                return Err(CoverageIndexError::InvalidRecord("limitation mask"));
2689            }
2690            let measurement_limitations = number(48)?;
2691            if (measurement_limitations == 0) != (mask == 0) {
2692                return Err(CoverageIndexError::InvalidRecord(
2693                    "limitation count and kinds disagree",
2694                ));
2695            }
2696            let uncovered_lines = number(8)?;
2697            let uncovered_statements = number(16)?;
2698            let uncovered_functions = number(24)?;
2699            let missing_branches = number(32)?;
2700            let missing_mcdc_conditions = number(40)?;
2701            let score = number(64)?;
2702            let expected_score = uncovered_lines
2703                + uncovered_functions * 2
2704                + missing_branches * 2
2705                + missing_mcdc_conditions * 3
2706                + measurement_limitations * 3;
2707            if score != expected_score {
2708                return Err(CoverageIndexError::InvalidRecord("file-gap score"));
2709            }
2710            let record_kind = self.optional_string(get_u32(record, 72))?;
2711            let record_runner = self.optional_string(get_u32(record, 76))?;
2712            if record_kind.as_deref() != kind || record_runner.as_deref() != runner {
2713                continue;
2714            }
2715            let mut limitation_kinds = Vec::new();
2716            for (bit, kind) in [
2717                (1, "dynamic-code"),
2718                (2, "semantic-safety"),
2719                (4, "source-scope"),
2720                (8, "unknown"),
2721            ] {
2722                if mask & bit != 0 {
2723                    limitation_kinds.push(kind.into());
2724                }
2725            }
2726            let totals = IndexedGapDimensions {
2727                lines: number(160)?,
2728                statements: number(168)?,
2729                functions: number(176)?,
2730                branches: number(184)?,
2731                mcdc_conditions: number(192)?,
2732            };
2733            let covered = |total: usize, missing: usize| {
2734                total
2735                    .checked_sub(missing)
2736                    .ok_or(CoverageIndexError::InvalidRecord("file-gap totals"))
2737            };
2738            gaps.push(IndexedFileGap {
2739                view,
2740                file: self.string(get_u32(record, 4))?,
2741                uncovered_lines,
2742                uncovered_statements,
2743                uncovered_functions,
2744                missing_branches,
2745                missing_mcdc_conditions,
2746                measurement_limitations,
2747                limitation_kinds,
2748                covered_by_other_tests: IndexedGapDimensions {
2749                    lines: number(80)?,
2750                    statements: number(88)?,
2751                    functions: number(96)?,
2752                    branches: number(104)?,
2753                    mcdc_conditions: number(112)?,
2754                },
2755                uncovered_everywhere: IndexedGapDimensions {
2756                    lines: number(120)?,
2757                    statements: number(128)?,
2758                    functions: number(136)?,
2759                    branches: number(144)?,
2760                    mcdc_conditions: number(152)?,
2761                },
2762                totals: totals.clone(),
2763                covered: IndexedGapDimensions {
2764                    lines: covered(totals.lines, uncovered_lines)?,
2765                    statements: covered(totals.statements, uncovered_statements)?,
2766                    functions: covered(totals.functions, uncovered_functions)?,
2767                    branches: covered(totals.branches, missing_branches)?,
2768                    mcdc_conditions: covered(totals.mcdc_conditions, missing_mcdc_conditions)?,
2769                },
2770                score,
2771            });
2772        }
2773        gaps.sort_by(|left, right| {
2774            right
2775                .score
2776                .cmp(&left.score)
2777                .then_with(|| left.file.cmp(&right.file))
2778        });
2779        Ok(gaps)
2780    }
2781
2782    fn optional_string(&self, id: u32) -> Result<Option<String>, CoverageIndexError> {
2783        if id == NO_STRING {
2784            Ok(None)
2785        } else {
2786            self.string(id).map(Some)
2787        }
2788    }
2789
2790    fn relation_ids(&self, offset: u64, count: u64) -> Result<Vec<Id>, CoverageIndexError> {
2791        Ok(self
2792            .relation_strings(offset, count)?
2793            .into_iter()
2794            .map(Id::from)
2795            .collect())
2796    }
2797
2798    fn relation_strings(&self, offset: u64, count: u64) -> Result<Vec<String>, CoverageIndexError> {
2799        let end = offset
2800            .checked_add(count)
2801            .ok_or(CoverageIndexError::SizeOverflow)?;
2802        let descriptor = self.index.descriptor(SECTION_STRING_RELATIONS)?;
2803        if end > descriptor.count {
2804            return Err(CoverageIndexError::InvalidRecord("string relation range"));
2805        }
2806        (offset..end)
2807            .map(|index| {
2808                let record = self.index.record(SECTION_STRING_RELATIONS, index)?;
2809                self.string(get_u32(record, 0))
2810            })
2811            .collect()
2812    }
2813
2814    pub fn projection(
2815        &self,
2816        view: CoverageViewId,
2817        kind: Option<&str>,
2818        runner: Option<&str>,
2819    ) -> Result<IndexedProjection, CoverageIndexError> {
2820        let descriptor = self.index.descriptor(SECTION_PROJECTIONS)?;
2821        let mut found = None;
2822        for index in 0..descriptor.count {
2823            let record = self.index.record(SECTION_PROJECTIONS, index)?;
2824            if CoverageViewId::try_from(record[0])? != view {
2825                continue;
2826            }
2827            if record[38..40].iter().any(|byte| *byte != 0)
2828                || record[518..520].iter().any(|byte| *byte != 0)
2829            {
2830                return Err(CoverageIndexError::InvalidRecord(
2831                    "projection reserved bytes",
2832                ));
2833            }
2834            let record_kind = self.optional_string(get_u32(record, 4))?;
2835            let record_runner = self.optional_string(get_u32(record, 8))?;
2836            if record_kind.as_deref() != kind || record_runner.as_deref() != runner {
2837                continue;
2838            }
2839            if found.is_some() {
2840                return Err(CoverageIndexError::InvalidRecord(
2841                    "duplicate coverage projection",
2842                ));
2843            }
2844            let number = |offset: usize| -> Result<usize, CoverageIndexError> {
2845                usize::try_from(get_u64(record, offset))
2846                    .map_err(|_| CoverageIndexError::SizeOverflow)
2847            };
2848            let limitations = number(192)?;
2849            let evidence_corruptions = number(200)?;
2850            let blocking = number(208)?;
2851            let declared = number(528)?;
2852            if blocking + declared != limitations + evidence_corruptions {
2853                return Err(CoverageIndexError::InvalidRecord(
2854                    "measurement blocking count",
2855                ));
2856            }
2857            let mut transport_values = (0..8)
2858                .map(|index| number(408 + index * 8))
2859                .collect::<Result<Vec<_>, _>>()?;
2860            transport_values.push(number(536)?);
2861            let transport = if bool_field(record[1])? {
2862                Some(TransportStats {
2863                    processes: transport_values[0],
2864                    child_launches: transport_values[1],
2865                    remote_launches: transport_values[2],
2866                    workspace_capabilities: transport_values[3],
2867                    guest_processes: transport_values[8],
2868                    scoped_server_records: transport_values[4],
2869                    background_server_records: transport_values[5],
2870                    corrupt_records: transport_values[6],
2871                    corrupt_files: transport_values[7],
2872                })
2873            } else {
2874                if transport_values.iter().any(|value| *value != 0) {
2875                    return Err(CoverageIndexError::InvalidRecord("transport presence flag"));
2876                }
2877                None
2878            };
2879            let has_scope = bool_field(record[2])?;
2880            let scope_kind = match record[3] {
2881                0 => None,
2882                1 => Some((ScopeKind::SourceDiscovery, "source-discovery")),
2883                2 => Some((ScopeKind::Compiler, "compiler")),
2884                _ => return Err(CoverageIndexError::InvalidRecord("coverage scope kind")),
2885            };
2886            let scope_mode = self.optional_string(get_u32(record, 16))?;
2887            let scope_language = self.optional_string(get_u32(record, 504))?;
2888            let scope_model = self.optional_string(get_u32(record, 508))?;
2889            let scope_unit = self.optional_string(get_u32(record, 512))?;
2890            let has_measurement_complete = bool_field(record[516])?;
2891            let measurement_complete = bool_field(record[517])?;
2892            if !has_measurement_complete && measurement_complete {
2893                return Err(CoverageIndexError::InvalidRecord(
2894                    "scope measurement completeness flag",
2895                ));
2896            }
2897            if has_scope
2898                != (scope_kind.is_some() && scope_language.is_some() && scope_model.is_some())
2899            {
2900                return Err(CoverageIndexError::InvalidRecord("scope presence flag"));
2901            }
2902            let source_scope = if let Some((kind, kind_name)) = scope_kind {
2903                match kind {
2904                    ScopeKind::SourceDiscovery => {
2905                        if scope_mode.is_none() || scope_unit.is_some() || has_measurement_complete
2906                        {
2907                            return Err(CoverageIndexError::InvalidRecord(
2908                                "source-discovery scope shape",
2909                            ));
2910                        }
2911                    }
2912                    ScopeKind::Compiler => {
2913                        if scope_mode.is_some()
2914                            || scope_unit.is_none()
2915                            || !has_measurement_complete
2916                            || get_u32(record, 32) != 0
2917                            || number(480)? != 0
2918                            || number(488)? != 0
2919                            || number(496)? != 0
2920                        {
2921                            return Err(CoverageIndexError::InvalidRecord("compiler scope shape"));
2922                        }
2923                    }
2924                }
2925                Some(IndexedSourceScope {
2926                    kind: kind_name.into(),
2927                    language: scope_language.expect("validated scope language"),
2928                    model: scope_model.expect("validated scope model"),
2929                    mode: scope_mode,
2930                    roots: self
2931                        .relation_strings(get_u64(record, 24), u64::from(get_u32(record, 32)))?,
2932                    unit: scope_unit,
2933                    measurement_complete: has_measurement_complete.then_some(measurement_complete),
2934                    included: number(480)?,
2935                    excluded: number(488)?,
2936                    ambiguous: number(496)?,
2937                })
2938            } else {
2939                if get_u32(record, 32) != 0
2940                    || scope_mode.is_some()
2941                    || scope_language.is_some()
2942                    || scope_model.is_some()
2943                    || scope_unit.is_some()
2944                    || has_measurement_complete
2945                    || number(480)? != 0
2946                    || number(488)? != 0
2947                    || number(496)? != 0
2948                {
2949                    return Err(CoverageIndexError::InvalidRecord("absent scope data"));
2950                }
2951                None
2952            };
2953            let empty_evidence_tests = number(472)?;
2954            let first_empty_evidence_test = self.optional_string(get_u32(record, 20))?;
2955            if (empty_evidence_tests == 0) != first_empty_evidence_test.is_none() {
2956                return Err(CoverageIndexError::InvalidRecord(
2957                    "empty-evidence diagnostic identity",
2958                ));
2959            }
2960            found = Some(IndexedProjection {
2961                view,
2962                kind: record_kind,
2963                runner: record_runner,
2964                generated_at: self.string(get_u32(record, 12))?,
2965                summary: decode_summary(record, 36, 40)?,
2966                measurement: IndexedMeasurement {
2967                    complete: blocking == 0,
2968                    limitations,
2969                    evidence_corruptions,
2970                    blocking,
2971                    declared,
2972                    files: number(216)?,
2973                    by_kind: IndexedMeasurementKinds {
2974                        dynamic_code: number(224)?,
2975                        semantic_safety: number(232)?,
2976                        source_scope: number(240)?,
2977                    },
2978                },
2979                attribution: IndexedAttribution {
2980                    browser_explicit: number(248)?,
2981                    browser_fallback: number(256)?,
2982                    server_explicit: number(264)?,
2983                    server_fallback: number(272)?,
2984                },
2985                transport,
2986                empty_evidence_tests,
2987                first_empty_evidence_test,
2988                confidence: IndexedSummaryConfidence {
2989                    lines: IndexedConfidenceLines {
2990                        unexecuted: number(280)?,
2991                        executed: number(288)?,
2992                        action: number(296)?,
2993                        asserted: number(304)?,
2994                    },
2995                    assertion_covered_mcdc_conditions: number(312)?,
2996                },
2997                files_with_gaps: number(320)?,
2998                files_with_coverage_gaps: number(328)?,
2999                tests: number(336)?,
3000                setups: number(344)?,
3001                test_outcomes: {
3002                    let mut outcomes = IndexedOutcomeCounts {
3003                        passed: number(352)?,
3004                        failed: number(360)?,
3005                        flaky: number(368)?,
3006                        skipped: number(376)?,
3007                        timed_out: number(384)?,
3008                        interrupted: number(392)?,
3009                        unknown: number(400)?,
3010                        unstarted: number(520)?,
3011                        todo: 0,
3012                    };
3013                    // The projection record has no slot left, so todo is the
3014                    // tests no other outcome counts; zero in older indexes.
3015                    outcomes.todo = number(336)?.saturating_sub(
3016                        outcomes.passed
3017                            + outcomes.failed
3018                            + outcomes.flaky
3019                            + outcomes.skipped
3020                            + outcomes.timed_out
3021                            + outcomes.interrupted
3022                            + outcomes.unknown
3023                            + outcomes.unstarted,
3024                    );
3025                    outcomes
3026                },
3027                source_scope,
3028            });
3029        }
3030        found.ok_or(CoverageIndexError::InvalidRecord(
3031            "missing coverage projection",
3032        ))
3033    }
3034
3035    pub fn decision_gaps(
3036        &self,
3037        view: CoverageViewId,
3038        kind: Option<&str>,
3039        runner: Option<&str>,
3040        file: &str,
3041    ) -> Result<Vec<IndexedDecisionGap>, CoverageIndexError> {
3042        let descriptor = self.index.descriptor(SECTION_DECISION_GAPS)?;
3043        let mut decisions = Vec::new();
3044        for index in 0..descriptor.count {
3045            let record = self.index.record(SECTION_DECISION_GAPS, index)?;
3046            if CoverageViewId::try_from(record[0])? != view {
3047                continue;
3048            }
3049            if record[1..4].iter().any(|byte| *byte != 0)
3050                || record[28..32].iter().any(|byte| *byte != 0)
3051                || record[64..].iter().any(|byte| *byte != 0)
3052            {
3053                return Err(CoverageIndexError::InvalidRecord(
3054                    "decision-gap reserved bytes",
3055                ));
3056            }
3057            let record_kind = self.optional_string(get_u32(record, 4))?;
3058            let record_runner = self.optional_string(get_u32(record, 8))?;
3059            if record_kind.as_deref() != kind || record_runner.as_deref() != runner {
3060                continue;
3061            }
3062            let record_file = self.string(get_u32(record, 16))?;
3063            if record_file != file {
3064                continue;
3065            }
3066            let number = |offset: usize| -> Result<usize, CoverageIndexError> {
3067                usize::try_from(get_u64(record, offset))
3068                    .map_err(|_| CoverageIndexError::SizeOverflow)
3069            };
3070            let conditions = number(48)?;
3071            let missing_conditions = number(56)?;
3072            if conditions == 0 || missing_conditions > conditions {
3073                return Err(CoverageIndexError::InvalidRecord(
3074                    "decision condition counts",
3075                ));
3076            }
3077            decisions.push(IndexedDecisionGap {
3078                view,
3079                file: record_file,
3080                id: self.string(get_u32(record, 12))?,
3081                line: number(32)?,
3082                column: number(40)?,
3083                kind: self.string(get_u32(record, 20))?,
3084                conditions,
3085                missing_conditions,
3086                source: self.string(get_u32(record, 24))?,
3087            });
3088        }
3089        Ok(decisions)
3090    }
3091
3092    pub fn dimensions(
3093        &self,
3094        view: CoverageViewId,
3095        dimension: CoverageDimension,
3096    ) -> Result<Vec<IndexedDimensionCoverage>, CoverageIndexError> {
3097        let descriptor = self.index.descriptor(SECTION_DIMENSIONS)?;
3098        let mut values = Vec::new();
3099        for index in 0..descriptor.count {
3100            let record = self.index.record(SECTION_DIMENSIONS, index)?;
3101            if CoverageViewId::try_from(record[0])? != view {
3102                continue;
3103            }
3104            let record_dimension = match record[1] {
3105                0 => CoverageDimension::Kind,
3106                1 => CoverageDimension::Runner,
3107                2 => CoverageDimension::Tests,
3108                _ => return Err(CoverageIndexError::InvalidRecord("dimension type")),
3109            };
3110            if record_dimension != dimension {
3111                continue;
3112            }
3113            if record[26..32].iter().any(|byte| *byte != 0) {
3114                return Err(CoverageIndexError::InvalidRecord(
3115                    "dimension reserved bytes",
3116                ));
3117            }
3118            let name = self.string(get_u32(record, 4))?;
3119            values.push(IndexedDimensionCoverage {
3120                kind: (dimension == CoverageDimension::Kind).then(|| name.clone()),
3121                runner: (dimension == CoverageDimension::Runner).then_some(name),
3122                tests: usize::try_from(get_u64(record, 8))
3123                    .map_err(|_| CoverageIndexError::SizeOverflow)?,
3124                setups: usize::try_from(get_u64(record, 16))
3125                    .map_err(|_| CoverageIndexError::SizeOverflow)?,
3126                attributed: usize::try_from(get_u64(record, 184))
3127                    .map_err(|_| CoverageIndexError::SizeOverflow)?,
3128                summary: decode_summary(record, 24, 32)?,
3129            });
3130        }
3131        Ok(values)
3132    }
3133
3134    pub fn scope_entries(
3135        &self,
3136        view: CoverageViewId,
3137    ) -> Result<Vec<IndexedScopeEntry>, CoverageIndexError> {
3138        let descriptor = self.index.descriptor(SECTION_SCOPE_ENTRIES)?;
3139        let mut entries = Vec::new();
3140        for index in 0..descriptor.count {
3141            let record = self.index.record(SECTION_SCOPE_ENTRIES, index)?;
3142            if CoverageViewId::try_from(record[0])? != view {
3143                continue;
3144            }
3145            if record[2..4].iter().any(|byte| *byte != 0)
3146                || record[28..].iter().any(|byte| *byte != 0)
3147            {
3148                return Err(CoverageIndexError::InvalidRecord(
3149                    "source-scope reserved bytes",
3150                ));
3151            }
3152            let status = match record[1] {
3153                0 => "included",
3154                1 => "excluded",
3155                2 => "ambiguous",
3156                _ => return Err(CoverageIndexError::InvalidRecord("source-scope status")),
3157            };
3158            let measurement_limitations = usize::try_from(get_u64(record, 16))
3159                .map_err(|_| CoverageIndexError::SizeOverflow)?;
3160            let mask = get_u32(record, 24);
3161            if mask & !7 != 0 || (measurement_limitations == 0) != (mask == 0) {
3162                return Err(CoverageIndexError::InvalidRecord(
3163                    "source-scope limitation annotation",
3164                ));
3165            }
3166            let mut limitation_kinds = Vec::new();
3167            for (bit, kind) in [
3168                (1, "dynamic-code"),
3169                (2, "semantic-safety"),
3170                (4, "source-scope"),
3171            ] {
3172                if mask & bit != 0 {
3173                    limitation_kinds.push(kind.into());
3174                }
3175            }
3176            entries.push(IndexedScopeEntry {
3177                file: self.string(get_u32(record, 4))?,
3178                status: status.into(),
3179                reason: self.string(get_u32(record, 8))?,
3180                package_root: self.optional_string(get_u32(record, 12))?,
3181                measurement_limitations,
3182                limitation_kinds,
3183            });
3184        }
3185        Ok(entries)
3186    }
3187
3188    fn confidence(
3189        &self,
3190        index: u64,
3191    ) -> Result<crate::coverage_report::CoverageConfidence, CoverageIndexError> {
3192        let record = self.index.record(SECTION_CONFIDENCE, index)?;
3193        if record[2..8].iter().any(|byte| *byte != 0)
3194            || record[72..].iter().any(|byte| *byte != 0)
3195            || record[1] & !15 != 0
3196        {
3197            return Err(CoverageIndexError::InvalidRecord("confidence record"));
3198        }
3199        let values = (0..4)
3200            .map(|index| {
3201                self.relation_strings(
3202                    get_u64(record, 8 + index * 16),
3203                    get_u64(record, 16 + index * 16),
3204                )
3205            })
3206            .collect::<Result<Vec<_>, _>>()?;
3207        Ok(crate::coverage_report::CoverageConfidence {
3208            level: match record[0] {
3209                0 => "unexecuted",
3210                1 => "executed",
3211                2 => "action",
3212                3 => "asserted",
3213                _ => return Err(CoverageIndexError::InvalidRecord("confidence level")),
3214            }
3215            .into(),
3216            setup_only: record[1] & 1 != 0,
3217            background_only: record[1] & 2 != 0,
3218            asserted: record[1] & 4 != 0,
3219            e2e: record[1] & 8 != 0,
3220            tests: values[0].iter().map(Id::from).collect(),
3221            asserted_tests: values[1].iter().map(Id::from).collect(),
3222            runners: values[2].clone(),
3223            kinds: values[3].clone(),
3224        })
3225    }
3226
3227    pub fn line(
3228        &self,
3229        view: CoverageViewId,
3230        file: &str,
3231        line: usize,
3232    ) -> Result<Option<IndexedLine>, CoverageIndexError> {
3233        let descriptor = self.index.descriptor(SECTION_LINES)?;
3234        let mut found = None;
3235        for index in 0..descriptor.count {
3236            let record = self.index.record(SECTION_LINES, index)?;
3237            if CoverageViewId::try_from(record[0])? != view {
3238                continue;
3239            }
3240            if record[3] != 0 || record[56..].iter().any(|byte| *byte != 0) {
3241                return Err(CoverageIndexError::InvalidRecord("line record"));
3242            }
3243            let record_file = self.string(get_u32(record, 4))?;
3244            let record_line = usize::try_from(get_u64(record, 8))
3245                .map_err(|_| CoverageIndexError::SizeOverflow)?;
3246            if record_file != file || record_line != line {
3247                continue;
3248            }
3249            if found.is_some() {
3250                return Err(CoverageIndexError::InvalidRecord("duplicate line"));
3251            }
3252            found = Some(IndexedLine {
3253                file: record_file,
3254                line: record_line,
3255                covered: bool_field(record[1])?,
3256                measured: !bool_field(record[2])?,
3257                tests: self.relation_strings(get_u64(record, 16), get_u64(record, 24))?,
3258                phases: self.relation_strings(get_u64(record, 32), get_u64(record, 40))?,
3259                confidence: self.confidence(get_u64(record, 48))?,
3260            });
3261        }
3262        Ok(found)
3263    }
3264
3265    pub fn lines(&self, view: CoverageViewId) -> Result<Vec<IndexedLine>, CoverageIndexError> {
3266        let descriptor = self.index.descriptor(SECTION_LINES)?;
3267        let mut lines = Vec::new();
3268        for index in 0..descriptor.count {
3269            let record = self.index.record(SECTION_LINES, index)?;
3270            if CoverageViewId::try_from(record[0])? != view {
3271                continue;
3272            }
3273            if record[3] != 0 || record[56..].iter().any(|byte| *byte != 0) {
3274                return Err(CoverageIndexError::InvalidRecord("line record"));
3275            }
3276            lines.push(IndexedLine {
3277                file: self.string(get_u32(record, 4))?,
3278                line: usize::try_from(get_u64(record, 8))
3279                    .map_err(|_| CoverageIndexError::SizeOverflow)?,
3280                covered: bool_field(record[1])?,
3281                measured: !bool_field(record[2])?,
3282                tests: self.relation_strings(get_u64(record, 16), get_u64(record, 24))?,
3283                phases: self.relation_strings(get_u64(record, 32), get_u64(record, 40))?,
3284                confidence: self.confidence(get_u64(record, 48))?,
3285            });
3286        }
3287        Ok(lines)
3288    }
3289
3290    pub fn test_summaries(
3291        &self,
3292        view: CoverageViewId,
3293    ) -> Result<Vec<IndexedTestSummary>, CoverageIndexError> {
3294        let descriptor = self.index.descriptor(SECTION_TEST_SUMMARIES)?;
3295        let mut tests = Vec::new();
3296        for index in 0..descriptor.count {
3297            let record = self.index.record(SECTION_TEST_SUMMARIES, index)?;
3298            if CoverageViewId::try_from(record[0])? != view {
3299                continue;
3300            }
3301            if record[36..].iter().any(|byte| *byte != 0) {
3302                return Err(CoverageIndexError::InvalidRecord("test summary record"));
3303            }
3304            tests.push(IndexedTestSummary {
3305                id: self.string(get_u32(record, 4))?,
3306                name: self.string(get_u32(record, 8))?,
3307                file: self.optional_string(get_u32(record, 12))?,
3308                title: self.optional_string(get_u32(record, 16))?,
3309                role: match record[1] {
3310                    0 => "test",
3311                    1 => "setup",
3312                    2 => "background",
3313                    _ => return Err(CoverageIndexError::InvalidRecord("test role")),
3314                }
3315                .into(),
3316                attribution: match record[3] {
3317                    0 => crate::coverage_report::ATTRIBUTION_EXACT,
3318                    1 => crate::coverage_report::ATTRIBUTION_RUN_WIDE,
3319                    2 => crate::coverage_report::ATTRIBUTION_PARTIAL,
3320                    _ => return Err(CoverageIndexError::InvalidRecord("test attribution")),
3321                }
3322                .into(),
3323                outcome: match record[2] {
3324                    0 => "passed",
3325                    1 => "failed",
3326                    2 => "flaky",
3327                    3 => "skipped",
3328                    4 => "timedOut",
3329                    5 => "interrupted",
3330                    6 => "unknown",
3331                    7 => "unstarted",
3332                    8 => "todo",
3333                    _ => return Err(CoverageIndexError::InvalidRecord("test outcome")),
3334                }
3335                .into(),
3336                provenance: crate::coverage_report::TestProvenance {
3337                    runner: self.string(get_u32(record, 20))?,
3338                    kind: self.string(get_u32(record, 24))?,
3339                    project: self.optional_string(get_u32(record, 28))?,
3340                    source: self.string(get_u32(record, 32))?,
3341                },
3342            });
3343        }
3344        Ok(tests)
3345    }
3346
3347    fn test_vector(&self, index: u64) -> Result<McdcVector, CoverageIndexError> {
3348        let record = self.index.record(SECTION_TEST_VECTORS, index)?;
3349        if record[1..8].iter().any(|byte| *byte != 0) {
3350            return Err(CoverageIndexError::InvalidRecord("test vector record"));
3351        }
3352        let offset = get_u64(record, 8);
3353        let count = get_u64(record, 16);
3354        let descriptor = self.index.descriptor(SECTION_VECTOR_VALUES)?;
3355        let end = offset
3356            .checked_add(count)
3357            .ok_or(CoverageIndexError::InvalidRecord("vector value range"))?;
3358        if end > descriptor.count {
3359            return Err(CoverageIndexError::InvalidRecord("vector value range"));
3360        }
3361        let mut values = Vec::with_capacity(
3362            usize::try_from(count).map_err(|_| CoverageIndexError::SizeOverflow)?,
3363        );
3364        for index in offset..end {
3365            values.push(match self.index.record(SECTION_VECTOR_VALUES, index)?[0] {
3366                0 => None,
3367                1 => Some(false),
3368                2 => Some(true),
3369                _ => return Err(CoverageIndexError::InvalidRecord("vector value")),
3370            });
3371        }
3372        Ok(McdcVector {
3373            values,
3374            outcome: bool_field(record[0])?,
3375        })
3376    }
3377
3378    pub fn test_details(
3379        &self,
3380        view: CoverageViewId,
3381    ) -> Result<Vec<IndexedTestDetail>, CoverageIndexError> {
3382        let summaries = self.test_summaries(view)?;
3383        let positions = summaries
3384            .iter()
3385            .enumerate()
3386            .map(|(index, test)| (test.id.clone(), index))
3387            .collect::<HashMap<_, _>>();
3388        if positions.len() != summaries.len() {
3389            return Err(CoverageIndexError::InvalidRecord("duplicate test summary"));
3390        }
3391        let mut details = summaries
3392            .into_iter()
3393            .map(|summary| IndexedTestDetail {
3394                summary,
3395                retries: Vec::new(),
3396                attempts: Vec::new(),
3397                hits: Vec::new(),
3398                decisions: Vec::new(),
3399                lines: Vec::new(),
3400            })
3401            .collect::<Vec<_>>();
3402        let position = |record: &[u8]| -> Result<Option<usize>, CoverageIndexError> {
3403            if CoverageViewId::try_from(record[0])? != view {
3404                return Ok(None);
3405            }
3406            let id = self.string(get_u32(record, 4))?;
3407            positions
3408                .get(&id)
3409                .copied()
3410                .map(Some)
3411                .ok_or(CoverageIndexError::InvalidRecord("unknown test relation"))
3412        };
3413        let descriptor = self.index.descriptor(SECTION_TEST_RETRIES)?;
3414        for index in 0..descriptor.count {
3415            let record = self.index.record(SECTION_TEST_RETRIES, index)?;
3416            if record[1..4].iter().any(|byte| *byte != 0) {
3417                return Err(CoverageIndexError::InvalidRecord("test retry record"));
3418            }
3419            if let Some(position) = position(record)? {
3420                details[position].retries.push(
3421                    usize::try_from(get_u64(record, 8))
3422                        .map_err(|_| CoverageIndexError::SizeOverflow)?,
3423                );
3424            }
3425        }
3426        let descriptor = self.index.descriptor(SECTION_TEST_ATTEMPTS)?;
3427        for index in 0..descriptor.count {
3428            let record = self.index.record(SECTION_TEST_ATTEMPTS, index)?;
3429            if record[1..4].iter().any(|byte| *byte != 0) {
3430                return Err(CoverageIndexError::InvalidRecord("test attempt record"));
3431            }
3432            if let Some(position) = position(record)? {
3433                details[position]
3434                    .attempts
3435                    .push(crate::coverage_report::TestAttempt {
3436                        retry: usize::try_from(get_u64(record, 8))
3437                            .map_err(|_| CoverageIndexError::SizeOverflow)?,
3438                        status: self.string(get_u32(record, 16))?,
3439                        expected_status: self.optional_string(get_u32(record, 20))?,
3440                    });
3441            }
3442        }
3443        let descriptor = self.index.descriptor(SECTION_TEST_LINES)?;
3444        for index in 0..descriptor.count {
3445            let record = self.index.record(SECTION_TEST_LINES, index)?;
3446            if record[1..4].iter().any(|byte| *byte != 0)
3447                || record[12..16].iter().any(|byte| *byte != 0)
3448            {
3449                return Err(CoverageIndexError::InvalidRecord("test line record"));
3450            }
3451            if let Some(position) = position(record)? {
3452                details[position]
3453                    .lines
3454                    .push(crate::coverage_report::SourceLine {
3455                        file: self.string(get_u32(record, 8))?.into(),
3456                        line: usize::try_from(get_u64(record, 16))
3457                            .map_err(|_| CoverageIndexError::SizeOverflow)?,
3458                    });
3459            }
3460        }
3461        let descriptor = self.index.descriptor(SECTION_TEST_HITS)?;
3462        for index in 0..descriptor.count {
3463            let record = self.index.record(SECTION_TEST_HITS, index)?;
3464            if record[1..4].iter().any(|byte| *byte != 0)
3465                || record[12..].iter().any(|byte| *byte != 0)
3466            {
3467                return Err(CoverageIndexError::InvalidRecord("test hit record"));
3468            }
3469            if let Some(position) = position(record)? {
3470                details[position]
3471                    .hits
3472                    .push(self.string(get_u32(record, 8))?);
3473            }
3474        }
3475        let descriptor = self.index.descriptor(SECTION_TEST_DECISIONS)?;
3476        let vectors = self.index.descriptor(SECTION_TEST_VECTORS)?.count;
3477        for index in 0..descriptor.count {
3478            let record = self.index.record(SECTION_TEST_DECISIONS, index)?;
3479            if record[1..4].iter().any(|byte| *byte != 0)
3480                || record[12..16].iter().any(|byte| *byte != 0)
3481            {
3482                return Err(CoverageIndexError::InvalidRecord("test decision record"));
3483            }
3484            if let Some(position) = position(record)? {
3485                let offset = get_u64(record, 16);
3486                let count = get_u64(record, 24);
3487                let end = offset
3488                    .checked_add(count)
3489                    .ok_or(CoverageIndexError::InvalidRecord("test vector range"))?;
3490                if end > vectors {
3491                    return Err(CoverageIndexError::InvalidRecord("test vector range"));
3492                }
3493                let mut observed = Vec::with_capacity(
3494                    usize::try_from(count).map_err(|_| CoverageIndexError::SizeOverflow)?,
3495                );
3496                for vector in offset..end {
3497                    observed.push(self.test_vector(vector)?);
3498                }
3499                details[position]
3500                    .decisions
3501                    .push(crate::coverage_report::TestDecisionResult {
3502                        id: self.string(get_u32(record, 8))?,
3503                        vectors: observed,
3504                    });
3505            }
3506        }
3507        Ok(details)
3508    }
3509
3510    pub fn hit_metadata(
3511        &self,
3512        view: CoverageViewId,
3513    ) -> Result<Vec<IndexedHitMetadata>, CoverageIndexError> {
3514        let descriptor = self.index.descriptor(SECTION_HIT_METADATA)?;
3515        let mut metadata = Vec::new();
3516        for index in 0..descriptor.count {
3517            let record = self.index.record(SECTION_HIT_METADATA, index)?;
3518            if CoverageViewId::try_from(record[0])? != view {
3519                continue;
3520            }
3521            if record[2..4].iter().any(|byte| *byte != 0)
3522                || record[12..16].iter().any(|byte| *byte != 0)
3523            {
3524                return Err(CoverageIndexError::InvalidRecord("hit metadata record"));
3525            }
3526            let obligation = match record[1] {
3527                0 => "statement",
3528                1 => "function",
3529                2 => "branch",
3530                _ => return Err(CoverageIndexError::InvalidRecord("hit obligation")),
3531            };
3532            let metadata_label = self.optional_string(get_u32(record, 36))?;
3533            metadata.push(IndexedHitMetadata {
3534                id: self.string(get_u32(record, 4))?,
3535                obligation: obligation.into(),
3536                file: self.string(get_u32(record, 8))?,
3537                line: usize::try_from(get_u64(record, 16))
3538                    .map_err(|_| CoverageIndexError::SizeOverflow)?,
3539                column: usize::try_from(get_u64(record, 24))
3540                    .map_err(|_| CoverageIndexError::SizeOverflow)?,
3541                branch_kind: self.optional_string(get_u32(record, 32))?,
3542                label: (obligation != "branch")
3543                    .then_some(metadata_label.clone())
3544                    .flatten(),
3545                alternative: self.optional_string(get_u32(record, 40))?,
3546                parent_id: (obligation == "branch").then_some(metadata_label).flatten(),
3547                source: self.string(get_u32(record, 44))?,
3548                tests: self.relation_strings(get_u64(record, 48), get_u64(record, 56))?,
3549            });
3550        }
3551        Ok(metadata)
3552    }
3553
3554    pub fn limitations(
3555        &self,
3556        view: CoverageViewId,
3557    ) -> Result<Vec<IndexedLimitation>, CoverageIndexError> {
3558        let descriptor = self.index.descriptor(SECTION_LIMITATIONS)?;
3559        let mut limitations = Vec::new();
3560        for index in 0..descriptor.count {
3561            let record = self.index.record(SECTION_LIMITATIONS, index)?;
3562            if CoverageViewId::try_from(record[0])? != view {
3563                continue;
3564            }
3565            if record[2..4].iter().any(|byte| *byte != 0)
3566                || record[40..].iter().any(|byte| *byte != 0)
3567            {
3568                return Err(CoverageIndexError::InvalidRecord("limitation record"));
3569            }
3570            limitations.push(IndexedLimitation {
3571                id: self.string(get_u32(record, 4))?,
3572                kind: self.string(get_u32(record, 8))?,
3573                file: self.string(get_u32(record, 12))?,
3574                source: self.string(get_u32(record, 16))?,
3575                reason: self.string(get_u32(record, 20))?,
3576                line: usize::try_from(get_u64(record, 24))
3577                    .map_err(|_| CoverageIndexError::SizeOverflow)?,
3578                column: usize::try_from(get_u64(record, 32))
3579                    .map_err(|_| CoverageIndexError::SizeOverflow)?,
3580                blocking: bool_field(record[1])?,
3581            });
3582        }
3583        Ok(limitations)
3584    }
3585
3586    pub fn decision_metadata(
3587        &self,
3588        view: CoverageViewId,
3589    ) -> Result<Vec<crate::coverage_report::DecisionMeta>, CoverageIndexError> {
3590        let descriptor = self.index.descriptor(SECTION_DECISION_METADATA)?;
3591        let mut metadata = Vec::new();
3592        for index in 0..descriptor.count {
3593            let record = self.index.record(SECTION_DECISION_METADATA, index)?;
3594            if CoverageViewId::try_from(record[0])? != view {
3595                continue;
3596            }
3597            if record[1..4].iter().any(|byte| *byte != 0)
3598                || record[20..24].iter().any(|byte| *byte != 0)
3599                || record[56..].iter().any(|byte| *byte != 0)
3600            {
3601                return Err(CoverageIndexError::InvalidRecord(
3602                    "decision metadata record",
3603                ));
3604            }
3605            metadata.push(crate::coverage_report::DecisionMeta {
3606                id: self.string(get_u32(record, 4))?,
3607                file: self.string(get_u32(record, 8))?,
3608                source: self.string(get_u32(record, 12))?,
3609                kind: self.string(get_u32(record, 16))?,
3610                line: usize::try_from(get_u64(record, 24))
3611                    .map_err(|_| CoverageIndexError::SizeOverflow)?,
3612                column: usize::try_from(get_u64(record, 32))
3613                    .map_err(|_| CoverageIndexError::SizeOverflow)?,
3614                conditions: self.relation_strings(get_u64(record, 40), get_u64(record, 48))?,
3615            });
3616        }
3617        Ok(metadata)
3618    }
3619
3620    fn decision_vector_observation(
3621        &self,
3622        index: u64,
3623    ) -> Result<crate::coverage_report::VectorObservation, CoverageIndexError> {
3624        let record = self
3625            .index
3626            .record(SECTION_DECISION_VECTOR_OBSERVATIONS, index)?;
3627        Ok(crate::coverage_report::VectorObservation {
3628            confidence: self.confidence(get_u64(record, 0))?,
3629            vector: self.test_vector(get_u64(record, 8))?,
3630            tests: self.relation_ids(get_u64(record, 16), get_u64(record, 24))?,
3631            phases: self.relation_ids(get_u64(record, 32), get_u64(record, 40))?,
3632            explicit_phases: self.relation_ids(get_u64(record, 48), get_u64(record, 56))?,
3633        })
3634    }
3635
3636    fn decision_condition(
3637        &self,
3638        index: u64,
3639    ) -> Result<crate::coverage_report::ConditionResult, CoverageIndexError> {
3640        let record = self.index.record(SECTION_DECISION_CONDITIONS, index)?;
3641        if record[0] & !7 != 0 || record[1..4].iter().any(|byte| *byte != 0) {
3642            return Err(CoverageIndexError::InvalidRecord(
3643                "decision condition record",
3644            ));
3645        }
3646        let has_witness = record[0] & 4 != 0;
3647        let witness = if has_witness {
3648            Some([
3649                self.test_vector(get_u64(record, 16))?,
3650                self.test_vector(get_u64(record, 24))?,
3651            ])
3652        } else {
3653            None
3654        };
3655        let first_tests = self.relation_strings(get_u64(record, 32), get_u64(record, 40))?;
3656        let second_tests = self.relation_strings(get_u64(record, 48), get_u64(record, 56))?;
3657        if !has_witness && (!first_tests.is_empty() || !second_tests.is_empty()) {
3658            return Err(CoverageIndexError::InvalidRecord(
3659                "condition witness tests without witness",
3660            ));
3661        }
3662        Ok(crate::coverage_report::ConditionResult {
3663            index: usize::try_from(get_u64(record, 8))
3664                .map_err(|_| CoverageIndexError::SizeOverflow)?,
3665            source: self.string(get_u32(record, 4))?,
3666            covered: record[0] & 1 != 0,
3667            assertion_covered: record[0] & 2 != 0,
3668            witness,
3669            witness_tests: has_witness.then_some([
3670                first_tests.into_iter().map(Id::from).collect(),
3671                second_tests.into_iter().map(Id::from).collect(),
3672            ]),
3673        })
3674    }
3675
3676    pub fn decision_details(
3677        &self,
3678        view: CoverageViewId,
3679    ) -> Result<Vec<crate::coverage_report::DecisionResult>, CoverageIndexError> {
3680        let metadata = self
3681            .decision_metadata(view)?
3682            .into_iter()
3683            .map(|meta| (meta.id.clone(), meta))
3684            .collect::<HashMap<_, _>>();
3685        let descriptor = self.index.descriptor(SECTION_DECISION_DETAILS)?;
3686        let observation_count = self
3687            .index
3688            .descriptor(SECTION_DECISION_VECTOR_OBSERVATIONS)?
3689            .count;
3690        let condition_count = self.index.descriptor(SECTION_DECISION_CONDITIONS)?.count;
3691        let mut decisions = Vec::new();
3692        for index in 0..descriptor.count {
3693            let record = self.index.record(SECTION_DECISION_DETAILS, index)?;
3694            if CoverageViewId::try_from(record[0])? != view {
3695                continue;
3696            }
3697            if record[1] & !3 != 0 || record[2..4].iter().any(|byte| *byte != 0) {
3698                return Err(CoverageIndexError::InvalidRecord("decision detail record"));
3699            }
3700            let executed = record[1] & 1 != 0;
3701            let covered = record[1] & 2 != 0;
3702            if covered && !executed {
3703                return Err(CoverageIndexError::InvalidRecord(
3704                    "covered unexecuted decision",
3705                ));
3706            }
3707            let range = |offset: usize,
3708                         available: u64,
3709                         label: &'static str|
3710             -> Result<std::ops::Range<u64>, CoverageIndexError> {
3711                let start = get_u64(record, offset);
3712                let count = get_u64(record, offset + 8);
3713                let end = start
3714                    .checked_add(count)
3715                    .ok_or(CoverageIndexError::InvalidRecord(label))?;
3716                if end > available {
3717                    return Err(CoverageIndexError::InvalidRecord(label));
3718                }
3719                Ok(start..end)
3720            };
3721            let observations = range(32, observation_count, "decision observation range")?
3722                .map(|index| self.decision_vector_observation(index))
3723                .collect::<Result<Vec<_>, _>>()?;
3724            let conditions = range(48, condition_count, "decision condition range")?
3725                .map(|index| self.decision_condition(index))
3726                .collect::<Result<Vec<_>, _>>()?;
3727            let id = self.string(get_u32(record, 4))?;
3728            let meta = metadata
3729                .get(&id)
3730                .cloned()
3731                .ok_or(CoverageIndexError::InvalidRecord(
3732                    "missing decision metadata",
3733                ))?;
3734            if conditions.len() != meta.conditions.len()
3735                || conditions.iter().enumerate().any(|(index, condition)| {
3736                    condition.index != index || condition.source != meta.conditions[index]
3737                })
3738            {
3739                return Err(CoverageIndexError::InvalidRecord(
3740                    "decision condition denominator",
3741                ));
3742            }
3743            decisions.push(crate::coverage_report::DecisionResult {
3744                meta,
3745                executed,
3746                covered,
3747                vectors: observations
3748                    .iter()
3749                    .map(|observation| observation.vector.clone())
3750                    .collect(),
3751                vector_observations: observations,
3752                conditions,
3753                tests: self.relation_ids(get_u64(record, 16), get_u64(record, 24))?,
3754                confidence: self.confidence(get_u64(record, 8))?,
3755            });
3756        }
3757        Ok(decisions)
3758    }
3759
3760    pub fn phase_summaries(
3761        &self,
3762        view: CoverageViewId,
3763    ) -> Result<Vec<IndexedPhaseSummary>, CoverageIndexError> {
3764        let descriptor = self.index.descriptor(SECTION_PHASE_SUMMARIES)?;
3765        let mut phases = Vec::new();
3766        for index in 0..descriptor.count {
3767            let record = self.index.record(SECTION_PHASE_SUMMARIES, index)?;
3768            if CoverageViewId::try_from(record[0])? != view {
3769                continue;
3770            }
3771            if record[1..4].iter().any(|byte| *byte != 0)
3772                || record[48..].iter().any(|byte| *byte != 0)
3773            {
3774                return Err(CoverageIndexError::InvalidRecord("phase summary record"));
3775            }
3776            phases.push(IndexedPhaseSummary {
3777                id: self.string(get_u32(record, 4))?,
3778                kind: self.string(get_u32(record, 8))?,
3779                operation: self.string(get_u32(record, 12))?,
3780                source: self.optional_string(get_u32(record, 16))?,
3781                test: self.string(get_u32(record, 20))?,
3782                status: self.optional_string(get_u32(record, 24))?,
3783                caused_by_phase_id: self.optional_string(get_u32(record, 28))?,
3784                lines: usize::try_from(get_u64(record, 32))
3785                    .map_err(|_| CoverageIndexError::SizeOverflow)?,
3786                decisions: usize::try_from(get_u64(record, 40))
3787                    .map_err(|_| CoverageIndexError::SizeOverflow)?,
3788            });
3789        }
3790        Ok(phases)
3791    }
3792
3793    pub fn anchors(
3794        &self,
3795        view: CoverageViewId,
3796        file: &str,
3797        line: usize,
3798    ) -> Result<Vec<IndexedAnchor>, CoverageIndexError> {
3799        let descriptor = self.index.descriptor(SECTION_ANCHORS)?;
3800        let mut anchors = Vec::new();
3801        for index in 0..descriptor.count {
3802            let record = self.index.record(SECTION_ANCHORS, index)?;
3803            if CoverageViewId::try_from(record[0])? != view {
3804                continue;
3805            }
3806            if record[3] != 0 || record[12..16].iter().any(|byte| *byte != 0) {
3807                return Err(CoverageIndexError::InvalidRecord("anchor record"));
3808            }
3809            let record_file = self.string(get_u32(record, 8))?;
3810            let record_line = usize::try_from(get_u64(record, 16))
3811                .map_err(|_| CoverageIndexError::SizeOverflow)?;
3812            if record_file != file || record_line != line {
3813                continue;
3814            }
3815            let total = usize::try_from(get_u64(record, 32))
3816                .map_err(|_| CoverageIndexError::SizeOverflow)?;
3817            let covered_conditions = usize::try_from(get_u64(record, 40))
3818                .map_err(|_| CoverageIndexError::SizeOverflow)?;
3819            let (kind, conditions, covered_conditions) = match record[1] {
3820                0 => {
3821                    if total == 0 || covered_conditions > total {
3822                        return Err(CoverageIndexError::InvalidRecord(
3823                            "decision anchor conditions",
3824                        ));
3825                    }
3826                    ("decision", Some(total), Some(covered_conditions))
3827                }
3828                1 => ("branch", None, None),
3829                2 => ("statement", None, None),
3830                3 => ("function", None, None),
3831                _ => return Err(CoverageIndexError::InvalidRecord("anchor kind")),
3832            };
3833            if kind != "decision" && (total != 0 || covered_conditions.is_some()) {
3834                return Err(CoverageIndexError::InvalidRecord("anchor conditions"));
3835            }
3836            anchors.push(IndexedAnchor {
3837                kind: kind.into(),
3838                id: self.string(get_u32(record, 4))?,
3839                file: record_file,
3840                line: record_line,
3841                column: usize::try_from(get_u64(record, 24))
3842                    .map_err(|_| CoverageIndexError::SizeOverflow)?,
3843                covered: bool_field(record[2])?,
3844                conditions,
3845                covered_conditions,
3846                tests: self.relation_strings(get_u64(record, 48), get_u64(record, 56))?,
3847            });
3848        }
3849        anchors.sort_by_key(|anchor| anchor.column);
3850        Ok(anchors)
3851    }
3852
3853    pub fn snapshot(&self) -> Result<IndexedCoverageSnapshot, CoverageIndexError> {
3854        Ok(IndexedCoverageSnapshot {
3855            all_summary: self.summary(CoverageViewId::All)?,
3856            passed_summary: self.summary(CoverageViewId::Passed)?,
3857            failed_summary: self.summary(CoverageViewId::Failed)?,
3858            all_files: self.file_gaps(CoverageViewId::All, None, None)?,
3859            passed_files: self.file_gaps(CoverageViewId::Passed, None, None)?,
3860            failed_files: self.file_gaps(CoverageViewId::Failed, None, None)?,
3861        })
3862    }
3863}
3864
3865fn bool_field(value: u8) -> Result<bool, CoverageIndexError> {
3866    match value {
3867        0 => Ok(false),
3868        1 => Ok(true),
3869        _ => Err(CoverageIndexError::InvalidRecord("boolean")),
3870    }
3871}
3872
3873fn decode_summary(
3874    record: &[u8],
3875    flags_offset: usize,
3876    base: usize,
3877) -> Result<CoverageSummary, CoverageIndexError> {
3878    let number = |offset: usize| -> Result<usize, CoverageIndexError> {
3879        usize::try_from(get_u64(record, offset)).map_err(|_| CoverageIndexError::SizeOverflow)
3880    };
3881    let count = |offset: usize| -> Result<CoverageCount, CoverageIndexError> {
3882        let covered = number(offset)?;
3883        let total = number(offset + 8)?;
3884        if covered > total {
3885            return Err(CoverageIndexError::InvalidRecord("covered exceeds total"));
3886        }
3887        Ok(CoverageCount {
3888            covered,
3889            total,
3890            percentage: percentage(covered, total),
3891        })
3892    };
3893    let decisions = number(base)?;
3894    let executed_decisions = number(base + 8)?;
3895    let covered_decisions = number(base + 16)?;
3896    let conditions = number(base + 24)?;
3897    let covered_conditions = number(base + 32)?;
3898    if covered_decisions > executed_decisions
3899        || executed_decisions > decisions
3900        || covered_conditions > conditions
3901    {
3902        return Err(CoverageIndexError::InvalidRecord("summary count ordering"));
3903    }
3904    Ok(CoverageSummary {
3905        unmeasured_obligations: None,
3906        exact_fraction_pct: None,
3907        decisions,
3908        executed_decisions,
3909        covered_decisions,
3910        conditions,
3911        covered_conditions,
3912        condition_coverage_pct: percentage(covered_conditions, conditions),
3913        lines: count(base + 40)?,
3914        statements: count(base + 56)?,
3915        functions: count(base + 72)?,
3916        branches: count(base + 88)?,
3917        decision_outcomes: count(base + 104)?,
3918        condition_outcomes: count(base + 120)?,
3919        value_selections: count(base + 136)?,
3920        coverage_complete: bool_field(record[flags_offset])?,
3921        completeness_blocked: match record[flags_offset + 1] {
3922            0 => None,
3923            1 => Some(false),
3924            2 => Some(true),
3925            _ => return Err(CoverageIndexError::InvalidRecord("optional boolean")),
3926        },
3927    })
3928}
3929
3930fn percentage(covered: usize, total: usize) -> f64 {
3931    if total == 0 {
3932        100.0
3933    } else {
3934        ((covered as f64 / total as f64) * 10_000.0).round() / 100.0
3935    }
3936}
3937
3938#[cfg(test)]
3939mod tests {
3940    use std::{
3941        fs,
3942        path::PathBuf,
3943        sync::atomic::{AtomicU64, Ordering},
3944        time::{SystemTime, UNIX_EPOCH},
3945    };
3946
3947    use crate::{
3948        coverage_analysis::{McdcVector, PointKind},
3949        coverage_report::{
3950            CoverageManifest, CoverageReportRequest, DecisionMeta, ExitCodeInput, PointMeta,
3951            RawTestResult, RuntimeSnapshot, TestProvenance, analyze_coverage_results,
3952        },
3953        query_index::{QueryIndexIdentity, write_query_index},
3954    };
3955
3956    use super::*;
3957
3958    static ROOT_SEQUENCE: AtomicU64 = AtomicU64::new(0);
3959
3960    fn root() -> PathBuf {
3961        let nonce = SystemTime::now()
3962            .duration_since(UNIX_EPOCH)
3963            .unwrap()
3964            .as_nanos();
3965        let root = std::env::temp_dir().join(format!(
3966            "supercov-coverage-index-{}-{nonce}-{}",
3967            std::process::id(),
3968            ROOT_SEQUENCE.fetch_add(1, Ordering::Relaxed),
3969        ));
3970        fs::create_dir_all(&root).unwrap();
3971        root
3972    }
3973
3974    fn identity() -> QueryIndexIdentity {
3975        QueryIndexIdentity {
3976            evidence_sha256: [1; 32],
3977            evidence_bytes: 100,
3978            analysis_sha256: [2; 32],
3979            producer_sha256: [3; 32],
3980            archive_schema_version: 2,
3981        }
3982    }
3983
3984    fn report() -> CoverageReport {
3985        let decision = DecisionMeta {
3986            id: "d".into(),
3987            file: "src/a.js".into(),
3988            line: 1,
3989            column: 1,
3990            source: "a && b".into(),
3991            conditions: vec!["a".into(), "b".into()],
3992            kind: "if".into(),
3993        };
3994        analyze_coverage_results(&CoverageReportRequest {
3995            run_id: "run".into(),
3996            manifest: CoverageManifest {
3997                unmeasured: Vec::new(),
3998                decisions: vec![decision.clone()],
3999                points: vec![PointMeta {
4000                    id: "point".into(),
4001                    kind: PointKind::Statement,
4002                    file: "src/a.js".into(),
4003                    line: 2,
4004                    column: 3,
4005                    source: "work();".into(),
4006                    label: None,
4007                }],
4008                branches: Vec::new(),
4009                limitations: vec![serde_json::json!({
4010                    "id": "dynamic",
4011                    "kind": "dynamic-code",
4012                    "file": "src/a.js",
4013                    "line": 3,
4014                    "column": 1,
4015                    "source": "eval(code)",
4016                    "reason": "dynamic source"
4017                })],
4018                scope: None,
4019                assertion_sites: Vec::new(),
4020            },
4021            raw_results: vec![RawTestResult {
4022                test_id: Some("test".into()),
4023                scope: None,
4024                test: "test".into(),
4025                test_file: Some("tests/a.js".into()),
4026                title: None,
4027                retry: Some(0),
4028                status: Some("passed".into()),
4029                expected_status: None,
4030                flaky: false,
4031                provenance: TestProvenance {
4032                    runner: "node:test".into(),
4033                    kind: "unit".into(),
4034                    project: None,
4035                    source: "runner-default".into(),
4036                },
4037                role: "test".into(),
4038                attribution: crate::coverage_report::ATTRIBUTION_EXACT.into(),
4039                phases: Vec::new(),
4040                runtime: vec![RuntimeSnapshot {
4041                    decisions: vec![crate::coverage_report::DecisionSnapshot {
4042                        meta: decision,
4043                        vectors: vec![McdcVector {
4044                            values: vec![Some(false), None],
4045                            outcome: false,
4046                        }],
4047                    }],
4048                    hits: vec!["point".into()],
4049                    events: Vec::new(),
4050                    logicals: Vec::new(),
4051                    phase_id: None,
4052                }],
4053                browser: Vec::new(),
4054                server: Vec::new(),
4055            }],
4056            generated_at: "time".into(),
4057            coverage_model: None,
4058            integrity: None,
4059            test_exit_code: ExitCodeInput::Present(Some(0)),
4060        })
4061        .unwrap()
4062    }
4063
4064    fn report_attributed(attribution: &str) -> CoverageReport {
4065        let mut report = report();
4066        for view in [
4067            &mut report.view,
4068            &mut report.filters.passed,
4069            &mut report.filters.failed,
4070        ] {
4071            for test in &mut view.tests {
4072                test.attribution = attribution.to_owned();
4073            }
4074        }
4075        report
4076    }
4077
4078    #[test]
4079    fn declined_lines_and_points_are_not_reported_as_coverage_gaps() {
4080        let mut report = report();
4081        for line in &mut report.view.lines {
4082            line.measured = false;
4083            line.covered = false;
4084            line.tests.clear();
4085        }
4086        for point in &mut report.view.points {
4087            point.measured = false;
4088            point.covered = false;
4089            point.tests.clear();
4090        }
4091        let selected = Selected::default();
4092        for filter in [None, Some(&selected)] {
4093            let gaps = file_gaps(&report.view, filter).unwrap();
4094            let (_, gap) = &gaps[0];
4095            assert_eq!(gap.uncovered_lines, 0);
4096            assert_eq!(gap.uncovered_statements, 0);
4097            assert_eq!(gap.uncovered_functions, 0);
4098            assert_eq!(gap.uncovered_everywhere[0..3], [0, 0, 0]);
4099            assert_eq!(gap.measurement_limitations, 1);
4100        }
4101        // Actual missed obligations must still appear.
4102        report.view.lines[0].measured = true;
4103        report.view.points[0].measured = true;
4104        let gaps = file_gaps(&report.view, None).unwrap();
4105        assert_eq!(gaps[0].1.uncovered_lines, 1);
4106        assert_eq!(gaps[0].1.uncovered_statements, 1);
4107    }
4108
4109    #[test]
4110    fn a_test_keeps_the_attribution_it_was_written_with() {
4111        // Attribution rides in a byte the reader used to refuse outright, so a
4112        // value that does not survive the trip is not a wrong number on a
4113        // report -- it is a run that will not open. Only `exact` was ever
4114        // written by a test, which left the other two arms of both the encoder
4115        // and the decoder standing on the claim that they matched.
4116        let root = root();
4117        for attribution in [
4118            crate::coverage_report::ATTRIBUTION_EXACT,
4119            crate::coverage_report::ATTRIBUTION_RUN_WIDE,
4120            crate::coverage_report::ATTRIBUTION_PARTIAL,
4121        ] {
4122            let report = report_attributed(attribution);
4123            let path = root.join(format!("query-index-{attribution}.v1.bin"));
4124            write_query_index(
4125                &coverage_index_sections(&report).unwrap(),
4126                &identity(),
4127                &path,
4128            )
4129            .unwrap();
4130            let container = QueryIndex::open(&path, &identity()).unwrap();
4131            let index = CoverageIndex::new(&container).unwrap();
4132            let tests = index.test_summaries(CoverageViewId::All).unwrap();
4133            assert_eq!(tests.len(), 1);
4134            assert_eq!(
4135                tests[0].attribution, attribution,
4136                "written as `{attribution}` and read back as `{}`",
4137                tests[0].attribution
4138            );
4139        }
4140    }
4141
4142    #[test]
4143    fn an_attribution_the_byte_has_no_room_for_is_refused_at_the_write() {
4144        // Refusing here is what keeps the reserved byte honest: a value nobody
4145        // taught the encoder must not be written as some neighbouring one and
4146        // read back as a measurement.
4147        let error = coverage_index_sections(&report_attributed("probably-exact")).unwrap_err();
4148        assert!(
4149            matches!(error, CoverageIndexError::InvalidRecord("test attribution")),
4150            "{error:?}"
4151        );
4152    }
4153
4154    // The same run is named over and over -- a filtered view repeats the
4155    // relation of the view it filters, and one list of tests covers many lines
4156    // of the file they exercise. Storing each naming separately made this the
4157    // largest section in the container: 61 MB of a 210 MB index.
4158    #[test]
4159    fn a_relation_named_twice_is_stored_once() {
4160        let mut strings = StringTable::default();
4161        let mut relations = StringRelations::default();
4162        let run = || ["alpha".to_string(), "beta".to_string(), "gamma".to_string()];
4163
4164        let first = relations.push(run(), &mut strings).unwrap();
4165        let second = relations.push(run(), &mut strings).unwrap();
4166        assert_eq!(first, second, "the second naming points at the first run");
4167        assert_eq!(relations.values.len(), 3, "and nothing was appended for it");
4168
4169        // A different run still gets its own storage, after the one already
4170        // there -- readers reach a run only by (offset, count), so the
4171        // existing pair must keep meaning what it meant.
4172        let other = relations
4173            .push(["alpha".to_string(), "delta".to_string()], &mut strings)
4174            .unwrap();
4175        assert_eq!(other, (3, 2));
4176        assert_eq!(relations.values.len(), 5);
4177
4178        // A prefix of a stored run is a different run, and is stored as one
4179        // rather than aliasing into the middle of another.
4180        let prefix = relations.push(["alpha".to_string()], &mut strings).unwrap();
4181        assert_eq!(prefix, (5, 1));
4182
4183        // What each pair resolves to is what was pushed.
4184        let read = |(offset, count): (u64, u64)| {
4185            relations.values[offset as usize..(offset + count) as usize].to_vec()
4186        };
4187        assert_eq!(read(first), read(second));
4188        assert_eq!(read(first).len(), 3);
4189        assert_ne!(read(first), read(other));
4190    }
4191
4192    #[test]
4193    fn shared_id_relations_match_text_relations_across_allocations() {
4194        let mut shared_strings = StringTable::default();
4195        let mut text_strings = StringTable::default();
4196        let mut shared_relations = StringRelations::default();
4197        let mut text_relations = StringRelations::default();
4198        let original: Vec<Id> = ["", "test::a[1]", "test::a[10]", "unicodé ✓"]
4199            .into_iter()
4200            .map(Id::from)
4201            .collect();
4202        let copies = original.clone();
4203        let separate: Vec<Id> = original.iter().map(|id| Id::from(id.as_str())).collect();
4204        for values in [&original[..], &copies, &separate, &separate[1..], &[]] {
4205            assert_eq!(
4206                shared_relations
4207                    .push_ids(values, &mut shared_strings)
4208                    .unwrap(),
4209                text_relations.push(values, &mut text_strings).unwrap(),
4210            );
4211        }
4212        // The table owns references to cached allocations even after callers
4213        // drop theirs, so a later ID cannot reuse an earlier cache address.
4214        drop(original);
4215        drop(copies);
4216        drop(separate);
4217        for value in ["new", "test::a[1]", ""] {
4218            let id = Id::from(value);
4219            assert_eq!(
4220                shared_strings.intern_shared(&id).unwrap(),
4221                text_strings.intern(value).unwrap(),
4222            );
4223        }
4224        assert_eq!(shared_relations.values, text_relations.values);
4225        assert_eq!(shared_strings.strings, text_strings.strings);
4226    }
4227
4228    #[test]
4229    fn typed_columns_round_trip_all_outcome_views_without_json() {
4230        let report = report();
4231        let root = root();
4232        let path = root.join("query-index.v1.bin");
4233        write_query_index(
4234            &coverage_index_sections(&report).unwrap(),
4235            &identity(),
4236            &path,
4237        )
4238        .unwrap();
4239        let container = QueryIndex::open(&path, &identity()).unwrap();
4240        let index = CoverageIndex::new(&container).unwrap();
4241        assert_eq!(
4242            index.model().unwrap(),
4243            IndexedCoverageModel {
4244                schema_version: COVERAGE_MODEL_SCHEMA_VERSION,
4245                variant: report.view.variant.clone(),
4246                name: report.view.model.name.clone(),
4247                completeness_meaning: report.view.model.completeness_meaning.clone(),
4248                measured: report.view.model.measured.clone(),
4249                not_measured: report.view.model.not_measured.clone(),
4250            }
4251        );
4252        for (id, view) in [
4253            (CoverageViewId::All, &report.view),
4254            (CoverageViewId::Passed, &report.filters.passed),
4255            (CoverageViewId::Failed, &report.filters.failed),
4256        ] {
4257            assert_eq!(index.summary(id).unwrap(), view.summary);
4258        }
4259        let gaps = index.file_gaps(CoverageViewId::All, None, None).unwrap();
4260        assert_eq!(gaps.len(), 1);
4261        assert_eq!(gaps[0].file, "src/a.js");
4262        assert_eq!(gaps[0].missing_mcdc_conditions, 2);
4263        let projection = index.projection(CoverageViewId::All, None, None).unwrap();
4264        assert_eq!(projection.summary, report.view.summary);
4265        assert_eq!(projection.tests, 1);
4266        assert_eq!(projection.setups, 0);
4267        assert_eq!(projection.test_outcomes.passed, 1);
4268        assert!(projection.source_scope.is_none());
4269        let line = index
4270            .line(CoverageViewId::All, "src/a.js", 2)
4271            .unwrap()
4272            .unwrap();
4273        assert!(line.covered);
4274        assert_eq!(line.tests, ["test"]);
4275        assert_eq!(line.confidence.level, "executed");
4276        let tests = index.test_summaries(CoverageViewId::All).unwrap();
4277        assert_eq!(tests.len(), 1);
4278        assert_eq!(tests[0].provenance.runner, "node:test");
4279        // The decision, and its two outcomes as a branch.
4280        let anchors = index.anchors(CoverageViewId::All, "src/a.js", 1).unwrap();
4281        assert_eq!(anchors.len(), 2);
4282        let decision = anchors
4283            .iter()
4284            .filter(|anchor| anchor.kind == "decision")
4285            .collect::<Vec<_>>();
4286        assert_eq!(decision.len(), 1);
4287        assert_eq!(decision[0].conditions, Some(2));
4288        assert_eq!(decision[0].tests, ["test"]);
4289        assert!(anchors.iter().any(|anchor| anchor.kind == "branch"));
4290        let point = index.anchors(CoverageViewId::All, "src/a.js", 2).unwrap();
4291        assert_eq!(point.len(), 1);
4292        assert_eq!(point[0].kind, "statement");
4293        assert_eq!(point[0].tests, ["test"]);
4294        let details = index.test_details(CoverageViewId::All).unwrap();
4295        assert_eq!(details.len(), 1);
4296        assert_eq!(details[0].retries, [0]);
4297        assert_eq!(details[0].attempts.len(), 1);
4298        assert_eq!(details[0].hits, ["point"]);
4299        assert_eq!(details[0].lines.len(), 1);
4300        assert_eq!(details[0].lines[0].line, 2);
4301        assert_eq!(details[0].decisions.len(), 1);
4302        assert_eq!(details[0].decisions[0].vectors.len(), 1);
4303        assert_eq!(
4304            details[0].decisions[0].vectors[0].values,
4305            [Some(false), None]
4306        );
4307        let hits = index.hit_metadata(CoverageViewId::All).unwrap();
4308        // The point, and the decision's two outcomes.
4309        assert_eq!(hits.len(), 3);
4310        let hits = hits
4311            .into_iter()
4312            .filter(|hit| hit.obligation != "branch")
4313            .collect::<Vec<_>>();
4314        assert_eq!(hits.len(), 1);
4315        assert_eq!(hits[0].id, "point");
4316        assert_eq!(hits[0].source, "work();");
4317        assert_eq!(hits[0].tests, ["test"]);
4318        let decisions = index.decision_metadata(CoverageViewId::All).unwrap();
4319        assert_eq!(decisions.len(), 1);
4320        assert_eq!(decisions[0].conditions, ["a", "b"]);
4321        assert_eq!(
4322            index.decision_details(CoverageViewId::All).unwrap(),
4323            report.view.decisions
4324        );
4325        let limitations = index.limitations(CoverageViewId::All).unwrap();
4326        assert_eq!(limitations.len(), 1);
4327        assert_eq!(limitations[0].kind, "dynamic-code");
4328        assert_eq!(limitations[0].line, 3);
4329        fs::remove_dir_all(root).unwrap();
4330    }
4331
4332    #[test]
4333    fn index_preserves_catalogued_unstarted_tests_without_attempts() {
4334        let report = analyze_coverage_results(&CoverageReportRequest {
4335            run_id: "run".into(),
4336            manifest: CoverageManifest {
4337                unmeasured: Vec::new(),
4338                decisions: Vec::new(),
4339                points: Vec::new(),
4340                branches: Vec::new(),
4341                limitations: Vec::new(),
4342                scope: None,
4343                assertion_sites: Vec::new(),
4344            },
4345            raw_results: vec![RawTestResult {
4346                test_id: Some("unstarted".into()),
4347                scope: None,
4348                test: "unstarted".into(),
4349                test_file: Some("tests/a.rs".into()),
4350                title: None,
4351                retry: None,
4352                status: Some("unstarted".into()),
4353                expected_status: Some("passed".into()),
4354                flaky: false,
4355                provenance: TestProvenance {
4356                    runner: "rust-nextest".into(),
4357                    kind: "unit".into(),
4358                    project: None,
4359                    source: "selected-but-not-started".into(),
4360                },
4361                role: "test".into(),
4362                attribution: crate::coverage_report::ATTRIBUTION_EXACT.into(),
4363                phases: Vec::new(),
4364                runtime: Vec::new(),
4365                browser: Vec::new(),
4366                server: Vec::new(),
4367            }],
4368            generated_at: "time".into(),
4369            coverage_model: None,
4370            integrity: None,
4371            test_exit_code: ExitCodeInput::Present(Some(100)),
4372        })
4373        .unwrap();
4374        let root = root();
4375        let path = root.join("query-index.v1.bin");
4376        write_query_index(
4377            &coverage_index_sections(&report).unwrap(),
4378            &identity(),
4379            &path,
4380        )
4381        .unwrap();
4382        let container = QueryIndex::open(&path, &identity()).unwrap();
4383        let index = CoverageIndex::new(&container).unwrap();
4384        let summaries = index.test_summaries(CoverageViewId::All).unwrap();
4385        assert_eq!(summaries.len(), 1);
4386        assert_eq!(summaries[0].outcome, "unstarted");
4387        let details = index.test_details(CoverageViewId::All).unwrap();
4388        assert!(details[0].retries.is_empty());
4389        assert!(details[0].attempts.is_empty());
4390        let projection = index.projection(CoverageViewId::All, None, None).unwrap();
4391        assert_eq!(projection.tests, 1);
4392        assert_eq!(projection.test_outcomes.unstarted, 1);
4393        fs::remove_dir_all(root).unwrap();
4394    }
4395
4396    #[test]
4397    fn source_discovery_scope_preserves_python_language() {
4398        let mut report = report();
4399        let scope = serde_json::json!({
4400            "version": 1,
4401            "mode": "automatic",
4402            "roots": ["."],
4403            "entries": [{
4404                "file": "src/a.py",
4405                "status": "included",
4406                "reason": "python source",
4407                "packageRoot": "."
4408            }]
4409        });
4410        for view in [
4411            &mut report.view,
4412            &mut report.filters.passed,
4413            &mut report.filters.failed,
4414        ] {
4415            view.scope = Some(scope.clone());
4416            view.model.language = "python".into();
4417            view.model.name = "python-probes-v1".into();
4418        }
4419        let root = root();
4420        let path = root.join("query-index.python.bin");
4421        write_query_index(
4422            &coverage_index_sections(&report).unwrap(),
4423            &identity(),
4424            &path,
4425        )
4426        .unwrap();
4427        let container = QueryIndex::open(&path, &identity()).unwrap();
4428        let index = CoverageIndex::new(&container).unwrap();
4429        let projection = index.projection(CoverageViewId::All, None, None).unwrap();
4430        let source_scope = projection.source_scope.unwrap();
4431        assert_eq!(source_scope.language, "python");
4432        assert_eq!(source_scope.model, "python-probes-v1");
4433        assert_eq!(source_scope.kind, "source-discovery");
4434        assert_eq!(source_scope.included, 1);
4435        assert_eq!(index.scope_entries(CoverageViewId::All).unwrap().len(), 1);
4436        fs::remove_dir_all(root).unwrap();
4437    }
4438
4439    #[test]
4440    fn compiler_owned_scope_round_trips_without_javascript_scope_fields() {
4441        let mut report = report();
4442        let scope = serde_json::json!({
4443            "language": "rust",
4444            "model": "rust-source-v1",
4445            "crate": "fixture",
4446            "measurementComplete": false
4447        });
4448        report.view.scope = Some(scope.clone());
4449        report.filters.passed.scope = Some(scope.clone());
4450        report.filters.failed.scope = Some(scope);
4451        let root = root();
4452        let path = root.join("query-index.v2.bin");
4453        write_query_index(
4454            &coverage_index_sections(&report).unwrap(),
4455            &identity(),
4456            &path,
4457        )
4458        .unwrap();
4459        let container = QueryIndex::open(&path, &identity()).unwrap();
4460        let index = CoverageIndex::new(&container).unwrap();
4461        let projection = index.projection(CoverageViewId::All, None, None).unwrap();
4462        assert_eq!(
4463            projection.source_scope,
4464            Some(IndexedSourceScope {
4465                kind: "compiler".into(),
4466                language: "rust".into(),
4467                model: "rust-source-v1".into(),
4468                mode: None,
4469                roots: Vec::new(),
4470                unit: Some("fixture".into()),
4471                measurement_complete: Some(false),
4472                included: 0,
4473                excluded: 0,
4474                ambiguous: 0,
4475            })
4476        );
4477        assert!(index.scope_entries(CoverageViewId::All).unwrap().is_empty());
4478        fs::remove_dir_all(root).unwrap();
4479    }
4480}