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