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