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