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