Skip to main content

supercov_engine/
coverage_index.rs

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