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