Skip to main content

supercov_engine/
coverage_index.rs

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