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