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