Skip to main content

supercov_engine/
owned_evidence.rs

1//! Turning what a Supercov-owned runtime wrote into the model the report reads.
2//!
3//! Go, Java and Kotlin all write the same transport, because there is no
4//! reason for them to differ and every reason not to: one format means one
5//! reader, one set of edge cases, and no way for two languages to disagree
6//! about what an evaluation was. Only the frontend declaration is per
7//! language, because only that differs in substance.
8//!
9//! The transport itself is deliberately dumb — sparse pairs of probe ids and packed
10//! vector words — because everything it could have computed instead is cheaper
11//! to compute here, once, than in a process that is trying to run tests. The
12//! mapping from probe id back to obligation lives in the manifest this module
13//! is handed, so the runtime never carries a string.
14
15use std::collections::{BTreeMap, BTreeSet};
16
17use supercov_contracts::{
18    AttributionPrecision, ExecutionModel, FrontendAttribution, FrontendLimitation,
19    FrontendLimitationScope, FrontendRunDeclaration, FrontendRunnerDeclaration,
20    LANGUAGE_FRONTEND_PROTOCOL_VERSION,
21};
22
23use crate::coverage_analysis::McdcVector;
24use crate::coverage_report::{
25    CoverageManifest, CoverageModelDeclaration, CoveragePhase, CoverageReportRequest,
26    DecisionSnapshot, ExecutionScope, ExitCodeInput, PersistedCoverageModel, RawTestResult,
27    RuntimeEvent, RuntimeSnapshot, TestProvenance,
28};
29
30/// The one phase an owned frontend can speak for is the test body itself, but
31/// a phase identifies itself across the whole run: two tests naming their
32/// bodies the same thing would be one phase claimed twice.
33fn test_phase(test_id: &str) -> String {
34    format!("{test_id}#call")
35}
36use crate::evidence_archive::EvidenceArchiveEntry;
37use crate::go_instrumenter::{GoProbe, GoProbeTarget};
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum OwnedEvidenceError {
41    Truncated(&'static str),
42    NoTests,
43}
44
45impl std::fmt::Display for OwnedEvidenceError {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        match self {
48            OwnedEvidenceError::Truncated(part) => {
49                write!(f, "Coverage evidence ended in the middle of its {part}")
50            }
51            OwnedEvidenceError::NoTests => write!(
52                f,
53                "the run produced coverage evidence but no test announced itself"
54            ),
55        }
56    }
57}
58
59/// One evaluation of a decision, as the runtime packed it.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct PackedVector {
62    pub decision: u32,
63    pub key: u64,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Default)]
67pub struct OwnedTestEvidence {
68    pub name: String,
69    /// How the test ended, as the framework saw it: `passed`, `failed` or
70    /// `skipped`.
71    pub status: String,
72    /// Which runner announced it. Empty where the frontend has only one, in
73    /// which case that one is the answer.
74    pub runner: String,
75    /// Probe id to the bitmask it was observed with.
76    pub probes: BTreeMap<u32, u32>,
77    pub vectors: Vec<PackedVector>,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Default)]
81pub struct OwnedEvidence {
82    /// Run-wide probe totals, the union of every test's.
83    pub global: Vec<u32>,
84    pub tests: Vec<OwnedTestEvidence>,
85    /// Conditions per decision, as the manifest ordered them.
86    pub widths: Vec<u8>,
87    /// Every distinct vector each decision produced, run-wide.
88    pub decision_vectors: Vec<Vec<u64>>,
89}
90
91struct Cursor<'a> {
92    bytes: &'a [u8],
93    offset: usize,
94}
95
96impl Cursor<'_> {
97    fn u64(&mut self, part: &'static str) -> Result<u64, OwnedEvidenceError> {
98        let end = self.offset + 8;
99        let slice = self
100            .bytes
101            .get(self.offset..end)
102            .ok_or(OwnedEvidenceError::Truncated(part))?;
103        self.offset = end;
104        Ok(u64::from_le_bytes(slice.try_into().expect("eight bytes")))
105    }
106
107    fn text(&mut self, length: usize, part: &'static str) -> Result<String, OwnedEvidenceError> {
108        let end = self.offset + length;
109        let slice = self
110            .bytes
111            .get(self.offset..end)
112            .ok_or(OwnedEvidenceError::Truncated(part))?;
113        self.offset = end;
114        String::from_utf8(slice.to_vec()).map_err(|_| OwnedEvidenceError::Truncated(part))
115    }
116}
117
118/// Read the transport. A truncated file is an error rather than a short read:
119/// a run that was killed half way through has partial evidence, and reporting
120/// it as though it were complete would understate coverage as a fact.
121pub fn read_evidence(bytes: &[u8]) -> Result<OwnedEvidence, OwnedEvidenceError> {
122    let mut cursor = Cursor { bytes, offset: 0 };
123    let probe_count = cursor.u64("probe totals")? as usize;
124    let mut global = Vec::with_capacity(probe_count);
125    for _ in 0..probe_count {
126        global.push(cursor.u64("probe totals")? as u32);
127    }
128    let test_count = cursor.u64("test count")? as usize;
129    let mut tests = Vec::with_capacity(test_count);
130    for _ in 0..test_count {
131        let length = cursor.u64("test name")? as usize;
132        let name = cursor.text(length, "test name")?;
133        let status_length = cursor.u64("test status")? as usize;
134        let status = cursor.text(status_length, "test status")?;
135        let runner_length = cursor.u64("test runner")? as usize;
136        let runner = cursor.text(runner_length, "test runner")?;
137        let hits = cursor.u64("test probes")? as usize;
138        let mut probes = BTreeMap::new();
139        for _ in 0..hits {
140            let index = cursor.u64("test probes")? as u32;
141            probes.insert(index, cursor.u64("test probes")? as u32);
142        }
143        let vector_count = cursor.u64("test vectors")? as usize;
144        let mut vectors = Vec::with_capacity(vector_count);
145        for _ in 0..vector_count {
146            let decision = cursor.u64("test vectors")? as u32;
147            vectors.push(PackedVector {
148                decision,
149                key: cursor.u64("test vectors")?,
150            });
151        }
152        tests.push(OwnedTestEvidence {
153            name,
154            status,
155            runner,
156            probes,
157            vectors,
158        });
159    }
160    let decision_count = cursor.u64("decision table")? as usize;
161    let mut widths = Vec::with_capacity(decision_count);
162    let mut decision_vectors = Vec::with_capacity(decision_count);
163    for _ in 0..decision_count {
164        widths.push(cursor.u64("decision table")? as u8);
165        let keys = cursor.u64("decision table")? as usize;
166        let mut seen = Vec::with_capacity(keys);
167        for _ in 0..keys {
168            seen.push(cursor.u64("decision table")?);
169        }
170        decision_vectors.push(seen);
171    }
172    Ok(OwnedEvidence {
173        global,
174        tests,
175        widths,
176        decision_vectors,
177    })
178}
179
180const PACKED_VALUE_SHIFT: u32 = 24;
181const PACKED_OUTCOME_SHIFT: u32 = 48;
182
183/// Unpack one evaluation into the vector MC/DC is judged from.
184///
185/// A condition the run never evaluated is `None`, not `false`. That is the
186/// distinction the whole measurement rests on: `a && b` with `a` false says
187/// nothing about `b`, and recording it as `false` would claim it had been
188/// tested.
189pub fn unpack_vector(key: u64, width: u8) -> McdcVector {
190    let evaluated = key & ((1 << PACKED_VALUE_SHIFT) - 1);
191    let values = (key >> PACKED_VALUE_SHIFT) & ((1 << PACKED_VALUE_SHIFT) - 1);
192    McdcVector {
193        values: (0..width)
194            .map(|index| {
195                let bit = 1_u64 << index;
196                (evaluated & bit != 0).then_some(values & bit != 0)
197            })
198            .collect(),
199        outcome: (key >> PACKED_OUTCOME_SHIFT) & 1 == 1,
200    }
201}
202
203/// How a test's name and package became the identity the report uses.
204#[derive(Debug, Clone, PartialEq, Eq)]
205pub struct OwnedTestOutcome {
206    pub name: String,
207    pub package: String,
208    pub file: Option<String>,
209    /// `passed`, `failed` or `skipped`, as the runner reported it.
210    pub status: String,
211    /// Which runner announced it, as the frontend declares that runner. Empty
212    /// where the frontend has only one.
213    pub runner: String,
214}
215
216/// How a language's runner attributes what it records.
217///
218/// Declared rather than assumed. A frontend that overstates its precision is
219/// worse than one that admits a gap, because the report would then present a
220/// guess as a measurement.
221pub fn go_declaration() -> FrontendRunDeclaration {
222    FrontendRunDeclaration {
223        protocol_version: LANGUAGE_FRONTEND_PROTOCOL_VERSION,
224        frontend_id: "supercov-go".into(),
225        frontend_version: "go-owned-v1".into(),
226        language: "go".into(),
227        // Supercov owns the probes: `go test -cover` is a development oracle
228        // here, not a product input.
229        structural_source: supercov_contracts::StructuralSource::OwnedProbes,
230        runners: vec![FrontendRunnerDeclaration {
231            runner: "go-test".into(),
232            // Go runs a package's tests one after another unless a test opts
233            // into parallelism, and Supercov runs one package at a time.
234            execution_model: ExecutionModel::SerialInProcess,
235            attribution: exact_per_test(),
236            limitations: {
237                let mut limitations = owned_attribution_limitations("go");
238                limitations.push(FrontendLimitation {
239                    id: "go-parallel-tests".into(),
240                    scopes: vec![FrontendLimitationScope::Test],
241                    reason:
242                        "a test that calls t.Parallel() runs alongside others, so work it does after that call is recorded run-wide rather than against that test"
243                            .into(),
244                });
245                limitations
246            },
247        }],
248        structural_limitations: Vec::new(),
249    }
250}
251
252/// What a Go run's numbers mean, so a reader is never left to infer it.
253pub fn go_coverage_model() -> CoverageModelDeclaration {
254    CoverageModelDeclaration {
255        language: "go".into(),
256        variant: "go-owned-probes-v1".into(),
257        name: "supercov-go-owned-v1".into(),
258        completeness_meaning: "Every obligation Supercov derived from the module's own Go sources was observed; explicit manifest limitations identify unmeasured Go surfaces.".into(),
259        measured: vec![
260            "owned Go statements and function entries".into(),
261            "owned atomic condition vectors and decision outcomes".into(),
262            "exact per-test attribution for tests that do not call t.Parallel()".into(),
263        ],
264        not_measured: vec![
265            "generated code, vendored packages and testdata".into(),
266            "work a test does after calling t.Parallel(), which counts run-wide".into(),
267            "causal linkage to individual actions".into(),
268            "all input values, semantic partitions, paths, or concurrency interleavings".into(),
269            "mutation score or assertion fault-detection strength".into(),
270        ],
271    }
272}
273
274/// The JVM's, where each framework reports a test's start and finish and
275/// attribution follows the lifecycle the framework itself defines.
276///
277/// Two runners, because there are two lifecycles. The JUnit Platform covers
278/// every engine built on it — Jupiter, Vintage, Kotest, Spock — and TestNG,
279/// which is not one, reports through its own listener interface. They measure
280/// the same way and say so with the same precision; what differs is who does
281/// the announcing.
282pub fn jvm_declaration() -> FrontendRunDeclaration {
283    let runner = |name: &str, concurrency: &str| FrontendRunnerDeclaration {
284        runner: name.into(),
285        execution_model: ExecutionModel::SerialInProcess,
286        attribution: exact_per_test(),
287        limitations: {
288            let mut limitations = owned_attribution_limitations(name);
289            limitations.push(FrontendLimitation {
290                id: format!("{name}-parallel-execution"),
291                scopes: vec![FrontendLimitationScope::Test],
292                reason: format!(
293                    "with {concurrency}, tests overlap in one process; work they do concurrently is recorded run-wide rather than against a single test, and condition coverage is dropped because concurrent evaluations corrupt it"
294                ),
295            });
296            limitations
297        },
298    };
299    FrontendRunDeclaration {
300        protocol_version: LANGUAGE_FRONTEND_PROTOCOL_VERSION,
301        frontend_id: "supercov-jvm".into(),
302        frontend_version: "jvm-owned-v1".into(),
303        language: "jvm".into(),
304        structural_source: supercov_contracts::StructuralSource::OwnedProbes,
305        runners: vec![
306            runner("junit-platform", "JUnit parallel execution enabled"),
307            runner("testng", "TestNG's parallel suites or methods"),
308        ],
309        structural_limitations: Vec::new(),
310    }
311}
312
313/// What a JVM run's numbers mean.
314pub fn jvm_coverage_model() -> CoverageModelDeclaration {
315    CoverageModelDeclaration {
316        language: "jvm".into(),
317        variant: "jvm-owned-probes-v1".into(),
318        name: "supercov-jvm-owned-v1".into(),
319        completeness_meaning: "Every obligation Supercov derived from the project's own Java and Kotlin sources was observed; explicit manifest limitations identify unmeasured JVM surfaces.".into(),
320        measured: vec![
321            "owned Java and Kotlin statements and method entries".into(),
322            "owned atomic condition vectors and decision outcomes".into(),
323            "exact per-test attribution through the JUnit Platform's own lifecycle".into(),
324        ],
325        not_measured: vec![
326            "generated sources, and bytecode with no source in the project".into(),
327            "tests run concurrently, which count run-wide and drop condition coverage".into(),
328            "causal linkage to individual actions".into(),
329            "all input values, semantic partitions, paths, or concurrency interleavings".into(),
330            "mutation score or assertion fault-detection strength".into(),
331        ],
332    }
333}
334
335/// What the owned frontends cannot attribute, and why.
336///
337/// The contract will not accept a precision below `Exact` without a limitation
338/// naming that scope, which is the right rule: a number that quietly means
339/// less than it appears to is worse than one that says so. Supercov measures
340/// statements and decisions here, so a test's individual actions and
341/// assertions are outside what it claims, and a phase is a lifecycle the
342/// probes never see.
343fn owned_attribution_limitations(language: &str) -> Vec<FrontendLimitation> {
344    vec![
345        FrontendLimitation {
346            id: format!("{language}-phase-linkage-aggregate"),
347            scopes: vec![FrontendLimitationScope::Phase],
348            reason:
349                "probes record what a test reached, not which of its setup, body or teardown phases reached it"
350                    .into(),
351        },
352        FrontendLimitation {
353            id: format!("{language}-action-linkage-unavailable"),
354            scopes: vec![FrontendLimitationScope::Action],
355            reason: "there is no general application-action lifecycle to link coverage to".into(),
356        },
357        FrontendLimitation {
358            id: format!("{language}-assertion-linkage-unavailable"),
359            scopes: vec![FrontendLimitationScope::Assertion],
360            reason:
361                "coverage is attributed to the test that reached the code, not to the assertion that checked it"
362                    .into(),
363        },
364    ]
365}
366
367fn exact_per_test() -> FrontendAttribution {
368    FrontendAttribution {
369        run: AttributionPrecision::Exact,
370        worker: AttributionPrecision::Exact,
371        test: AttributionPrecision::Exact,
372        retry: AttributionPrecision::Exact,
373        phase: AttributionPrecision::Aggregate,
374        // Supercov measures statements and decisions here, not the individual
375        // actions or assertions inside a test.
376        action: AttributionPrecision::Unavailable,
377        assertion: AttributionPrecision::Unavailable,
378    }
379}
380
381/// Merge what several processes recorded into one run's evidence.
382///
383/// Both owned frontends need this, because both run a suite as more than one
384/// process: `go test` builds a binary per package, and a multi-module JVM
385/// build forks a JVM per module. Probe indices are project-wide, so run-wide
386/// totals are a union of equal-length arrays, and test records simply
387/// accumulate — each belongs to exactly the process that produced it.
388pub fn merge_evidence(parts: Vec<OwnedEvidence>) -> OwnedEvidence {
389    let mut merged = OwnedEvidence::default();
390    for part in parts {
391        if merged.global.len() < part.global.len() {
392            merged.global.resize(part.global.len(), 0);
393        }
394        for (slot, value) in merged.global.iter_mut().zip(part.global) {
395            *slot |= value;
396        }
397        if merged.widths.len() < part.widths.len() {
398            merged.widths.resize(part.widths.len(), 0);
399            merged
400                .decision_vectors
401                .resize(part.widths.len(), Vec::new());
402        }
403        for (id, width) in part.widths.into_iter().enumerate() {
404            merged.widths[id] = merged.widths[id].max(width);
405        }
406        for (id, keys) in part.decision_vectors.into_iter().enumerate() {
407            let seen = &mut merged.decision_vectors[id];
408            for key in keys {
409                if !seen.contains(&key) {
410                    seen.push(key);
411                }
412            }
413        }
414        merged.tests.extend(part.tests);
415    }
416    merged
417}
418
419/// A short, stable identity derived from what makes the thing itself, so two
420/// runs of the same test agree on what to call it.
421fn stable_id(prefix: &str, values: &[&str]) -> String {
422    use sha2::{Digest, Sha256};
423    let mut hash = Sha256::new();
424    for value in values {
425        hash.update(value.as_bytes());
426        hash.update([0]);
427    }
428    let digest = hash.finalize();
429    let mut encoded = String::with_capacity(prefix.len() + 25);
430    encoded.push_str(prefix);
431    encoded.push(':');
432    for byte in &digest[..12] {
433        use std::fmt::Write as _;
434        write!(&mut encoded, "{byte:02x}").expect("string formatting");
435    }
436    encoded
437}
438
439/// The obligation each probe answers for, by id.
440fn obligations(probes: &BTreeMap<u64, GoProbe>) -> BTreeMap<u32, String> {
441    probes
442        .iter()
443        .map(|(id, probe)| {
444            let obligation = match &probe.target {
445                GoProbeTarget::Statement { id } | GoProbeTarget::Function { id } => id.clone(),
446                GoProbeTarget::Alternative { alternative, .. } => alternative.clone(),
447            };
448            (*id as u32, obligation)
449        })
450        .collect()
451}
452
453fn snapshot(
454    environment: &str,
455    evidence: &OwnedTestEvidence,
456    manifest: &CoverageManifest,
457    by_probe: &BTreeMap<u32, String>,
458    phase: &str,
459) -> RuntimeSnapshot {
460    let mut events = Vec::new();
461    let mut clock = 0_i64;
462    let hits = evidence
463        .probes
464        .keys()
465        .filter_map(|probe| by_probe.get(probe).cloned())
466        .collect::<BTreeSet<_>>();
467    for id in &hits {
468        events.push(RuntimeEvent {
469            event_type: "hit".into(),
470            id: id.clone(),
471            vector: None,
472            timestamp_ms: clock,
473            phase_id: Some(phase.to_owned()),
474            statement_id: None,
475            environment: environment.into(),
476        });
477        clock += 1;
478    }
479    let mut by_decision: BTreeMap<u32, Vec<u64>> = BTreeMap::new();
480    for vector in &evidence.vectors {
481        by_decision
482            .entry(vector.decision)
483            .or_default()
484            .push(vector.key);
485    }
486    let mut decisions = Vec::new();
487    for (index, meta) in manifest.decisions.iter().enumerate() {
488        let Some(keys) = by_decision.get(&(index as u32)) else {
489            continue;
490        };
491        let width = meta.conditions.len() as u8;
492        let observed = keys
493            .iter()
494            .map(|key| unpack_vector(*key, width))
495            .collect::<Vec<_>>();
496        for vector in &observed {
497            events.push(RuntimeEvent {
498                event_type: "decision".into(),
499                id: meta.id.clone(),
500                vector: Some(vector.clone()),
501                timestamp_ms: clock,
502                phase_id: Some(phase.to_owned()),
503                statement_id: None,
504                environment: environment.into(),
505            });
506            clock += 1;
507        }
508        decisions.push(DecisionSnapshot {
509            meta: meta.clone(),
510            vectors: observed,
511        });
512    }
513    RuntimeSnapshot {
514        decisions,
515        hits: hits.into_iter().collect(),
516        events,
517        logicals: Vec::new(),
518    }
519}
520
521pub struct OwnedFrontendRun {
522    pub declaration: FrontendRunDeclaration,
523    pub request: CoverageReportRequest,
524    pub tests: usize,
525}
526
527impl OwnedFrontendRun {
528    /// The archive a run publishes: what the numbers mean, who produced them,
529    /// the obligations they are measured against, and one record per test.
530    pub fn archive_entries(&self) -> Result<Vec<EvidenceArchiveEntry>, serde_json::Error> {
531        let model = PersistedCoverageModel::from_declaration(
532            self.request
533                .coverage_model
534                .as_ref()
535                .expect("an owned frontend always declares a coverage model"),
536        )
537        .expect("owned coverage models are contract-valid");
538        let mut entries = vec![
539            EvidenceArchiveEntry {
540                path: "coverage-model.json".into(),
541                contents: serde_json::to_vec(&model)?,
542            },
543            EvidenceArchiveEntry {
544                path: "frontend.json".into(),
545                contents: serde_json::to_vec(&self.declaration)?,
546            },
547            EvidenceArchiveEntry {
548                path: "manifest.json".into(),
549                contents: serde_json::to_vec(&self.request.manifest)?,
550            },
551        ];
552        for (index, result) in self.request.raw_results.iter().enumerate() {
553            entries.push(EvidenceArchiveEntry {
554                path: format!("results/{index:08}/mcdc.json"),
555                contents: serde_json::to_vec(result)?,
556            });
557        }
558        Ok(entries)
559    }
560}
561
562/// Build the report request from what the run recorded.
563///
564/// A test the runner reported but that announced nothing still becomes a
565/// result, with no coverage. Dropping it would make a suite look smaller than
566/// it is, and a test that ran without reaching any measured line is a fact
567/// worth seeing rather than an absence worth hiding.
568/// Everything a report needs about one run of one owned frontend.
569pub struct OwnedRunInputs<'a> {
570    pub declaration: FrontendRunDeclaration,
571    /// The label runtime events carry, so a reader can tell which frontend
572    /// produced them.
573    pub environment: &'a str,
574    pub manifest: &'a CoverageManifest,
575    pub probes: &'a BTreeMap<u64, GoProbe>,
576    pub evidence: &'a OwnedEvidence,
577    pub outcomes: &'a [OwnedTestOutcome],
578    pub run_id: &'a str,
579    pub generated_at: &'a str,
580    pub test_exit_code: i32,
581    /// What the numbers mean. Carried rather than inferred from the language,
582    /// so a frontend cannot quietly inherit another's claims.
583    pub coverage_model: CoverageModelDeclaration,
584}
585
586/// Build the report request from what the run recorded.
587///
588/// A test the runner reported but that announced nothing still becomes a
589/// result, with no coverage. Dropping it would make a suite look smaller than
590/// it is, and a test that ran without reaching any measured line is a fact
591/// worth seeing rather than an absence worth hiding.
592pub fn build_frontend_run(inputs: OwnedRunInputs) -> Result<OwnedFrontendRun, OwnedEvidenceError> {
593    let OwnedRunInputs {
594        declaration,
595        environment,
596        manifest,
597        probes,
598        evidence,
599        outcomes,
600        run_id,
601        generated_at,
602        test_exit_code,
603        coverage_model,
604    } = inputs;
605    if outcomes.is_empty() {
606        return Err(OwnedEvidenceError::NoTests);
607    }
608    // The runner every result claims must be one the declaration names: a
609    // result attributed to a runner nobody declared is a result nothing can
610    // say the precision of, and the reader refuses it rather than guess.
611    let default_runner = declaration
612        .runners
613        .first()
614        .map(|runner| runner.runner.clone())
615        .ok_or(OwnedEvidenceError::NoTests)?;
616    let declared = declaration
617        .runners
618        .iter()
619        .map(|runner| runner.runner.as_str())
620        .collect::<BTreeSet<_>>();
621    let source = declaration.frontend_version.clone();
622    let by_probe = obligations(probes);
623    let recorded = evidence
624        .tests
625        .iter()
626        .map(|test| (test.name.clone(), test))
627        .collect::<BTreeMap<_, _>>();
628    let empty = OwnedTestEvidence::default();
629    let mut raw_results = outcomes
630        .iter()
631        .map(|outcome| {
632            let test = recorded.get(&outcome.name).copied().unwrap_or(&empty);
633            let test_id = format!("{}::{}", outcome.package, outcome.name);
634            let phase = test_phase(&test_id);
635            let provenance = TestProvenance {
636                // What the record says, when the frontend declares it. A JVM
637                // project can run JUnit and TestNG in one JVM, and a result
638                // naming the wrong one would claim it was attributed by a
639                // lifecycle that never saw it.
640                runner: if declared.contains(outcome.runner.as_str()) {
641                    outcome.runner.clone()
642                } else {
643                    default_runner.clone()
644                },
645                kind: "unit".into(),
646                project: Some(outcome.package.clone()),
647                source: source.clone(),
648            };
649            RawTestResult {
650                scope: Some(ExecutionScope {
651                    version: 1,
652                    run_id: run_id.to_owned(),
653                    // The unit that ran as its own process: a Go test binary
654                    // is built per package, and a JVM suite is one JVM. A
655                    // worker identity the reader can trust is what lets it
656                    // accept exact attribution at all.
657                    worker_id: outcome.package.clone(),
658                    test_id: test_id.clone(),
659                    test_key: stable_id("owned-test", &[&outcome.package, &outcome.name]),
660                    retry: 0,
661                    attempt_id: stable_id(
662                        "owned-attempt",
663                        &[run_id, &outcome.package, &outcome.name, "0"],
664                    ),
665                }),
666                test_id: Some(test_id),
667                test: outcome.name.clone(),
668                test_file: outcome.file.clone(),
669                title: None,
670                retry: Some(0),
671                status: Some(outcome.status.clone()),
672                expected_status: None,
673                flaky: false,
674                provenance: provenance.clone(),
675                role: "test".into(),
676                // Every event a probe produces belongs to the test body: the
677                // runtime binds coverage at the test boundary and knows
678                // nothing of setup or teardown. One declared phase says
679                // exactly that, and leaves the events with somewhere real to
680                // point rather than at a phase nobody declared.
681                phases: vec![CoveragePhase {
682                    id: phase.clone(),
683                    kind: "test".into(),
684                    operation: format!("{} {}", provenance.runner, outcome.name),
685                    source: outcome.file.clone(),
686                    caused_by_phase_id: None,
687                    started_at_ms: 0,
688                    ended_at_ms: None,
689                    status: Some(outcome.status.clone()),
690                    error: None,
691                }],
692                runtime: vec![snapshot(environment, test, manifest, &by_probe, &phase)],
693                browser: Vec::new(),
694                server: Vec::new(),
695            }
696        })
697        .collect::<Vec<_>>();
698    // Everything the run reached that no test claimed.
699    //
700    // A Go test that calls t.Parallel() is deliberately left unattributed:
701    // probes are a store into one shared array, so what it reaches while
702    // others run beside it cannot be credited to it. The declaration says that
703    // coverage still counts run-wide -- and until now nothing carried it, so a
704    // suite written the way Go suites are written measured almost nothing and
705    // was told nothing about why. samber/lo calls t.Parallel() 1606 times.
706    //
707    // It is recorded as its own record, under a role that is not a test,
708    // holding only what the tests did not claim: reached, by nobody nameable.
709    let claimed = evidence
710        .tests
711        .iter()
712        .flat_map(|test| test.probes.keys().copied())
713        .collect::<BTreeSet<_>>();
714    let unclaimed = evidence
715        .global
716        .iter()
717        .enumerate()
718        .filter(|(index, mask)| **mask != 0 && !claimed.contains(&(*index as u32)))
719        .map(|(index, mask)| (index as u32, *mask))
720        .collect::<BTreeMap<_, _>>();
721    if !unclaimed.is_empty() {
722        // Whether this counts as coverage turns on whether it was produced by
723        // something that passed, which is the same rule every test record
724        // obeys: a failing test's coverage is not evidence that anything
725        // works. Here the producers cannot be named individually, so the
726        // question is asked of the run: if every test passed, everything this
727        // holds was produced by a passing test. If any failed, there is no
728        // telling which of this came from it, and none of it counts.
729        let status = if test_exit_code == 0 {
730            "passed"
731        } else {
732            "failed"
733        };
734        let test_id = format!("{}::background", declaration.language);
735        let phase = test_phase(&test_id);
736        let background = OwnedTestEvidence {
737            name: "background".into(),
738            status: "unknown".into(),
739            runner: String::new(),
740            probes: unclaimed,
741            vectors: Vec::new(),
742        };
743        raw_results.push(RawTestResult {
744            scope: Some(ExecutionScope {
745                version: 1,
746                run_id: run_id.to_owned(),
747                worker_id: "background".into(),
748                test_id: test_id.clone(),
749                test_key: stable_id("owned-background", &[run_id]),
750                retry: 0,
751                attempt_id: stable_id("owned-background-attempt", &[run_id]),
752            }),
753            test_id: Some(test_id),
754            test: "execution no test could be credited with".into(),
755            test_file: None,
756            title: None,
757            retry: Some(0),
758            status: Some(status.into()),
759            expected_status: None,
760            flaky: false,
761            provenance: TestProvenance {
762                runner: default_runner.clone(),
763                kind: "background".into(),
764                project: None,
765                source: source.clone(),
766            },
767            role: "background".into(),
768            phases: vec![CoveragePhase {
769                id: phase.clone(),
770                kind: "background".into(),
771                operation: "execution outside any test".into(),
772                source: None,
773                caused_by_phase_id: None,
774                started_at_ms: 0,
775                ended_at_ms: Some(0),
776                status: Some(status.into()),
777                error: None,
778            }],
779            runtime: vec![snapshot(
780                environment,
781                &background,
782                manifest,
783                &by_probe,
784                &phase,
785            )],
786            browser: Vec::new(),
787            server: Vec::new(),
788        });
789    }
790
791    // A declaration naming a runner that produced nothing claims something the
792    // run did not do, and the reader refuses it — rightly. A JVM frontend can
793    // drive JUnit and TestNG, but any one project usually runs one of them, so
794    // the run declares the ones it actually observed.
795    let observed = raw_results
796        .iter()
797        .map(|result| result.provenance.runner.as_str())
798        .collect::<BTreeSet<_>>();
799    let mut declaration = declaration;
800
801    // What the manifest says could not be measured, the declaration has to
802    // name too: the reader checks that the two agree, so a limitation cannot
803    // appear in one and be missing from the other. The ids are derived per
804    // obligation from the file and the node, so a static declaration could
805    // never have listed them and this is the only place that knows them.
806    declaration.structural_limitations = manifest
807        .limitations
808        .iter()
809        .filter_map(|limitation| limitation.get("id")?.as_str().map(str::to_owned))
810        .collect::<BTreeSet<_>>()
811        .into_iter()
812        .collect();
813
814    if declaration
815        .runners
816        .iter()
817        .any(|runner| observed.contains(runner.runner.as_str()))
818    {
819        declaration
820            .runners
821            .retain(|runner| observed.contains(runner.runner.as_str()));
822    }
823    Ok(OwnedFrontendRun {
824        declaration,
825        tests: raw_results.len(),
826        request: CoverageReportRequest {
827            run_id: run_id.to_owned(),
828            manifest: manifest.clone(),
829            raw_results,
830            generated_at: generated_at.to_owned(),
831            coverage_model: Some(coverage_model),
832            integrity: None,
833            test_exit_code: ExitCodeInput::Present(Some(test_exit_code)),
834        },
835    })
836}
837
838#[cfg(test)]
839mod tests {
840    use super::*;
841
842    fn write(values: &[u64]) -> Vec<u8> {
843        values.iter().flat_map(|v| v.to_le_bytes()).collect()
844    }
845
846    #[test]
847    fn an_unevaluated_condition_is_absent_rather_than_false() {
848        // The distinction the whole measurement rests on. `a && b` with `a`
849        // false says nothing about `b`; recording it as false would claim it
850        // had been tested, and independence would be judged from a fact that
851        // never happened.
852        let short_circuited = unpack_vector(0b01, 2);
853        assert_eq!(short_circuited.values, [Some(false), None]);
854        assert!(!short_circuited.outcome);
855
856        let both = unpack_vector(0b11 | (0b11 << 24) | (1 << 48), 2);
857        assert_eq!(both.values, [Some(true), Some(true)]);
858        assert!(both.outcome);
859
860        // A condition evaluated and false is Some(false), which is a different
861        // vector from the one above.
862        let first_false = unpack_vector(0b11 | (0b01 << 24), 2);
863        assert_eq!(first_false.values, [Some(true), Some(false)]);
864    }
865
866    #[test]
867    fn a_truncated_file_is_refused_rather_than_read_short() {
868        // A run killed part way through has partial evidence. Reading what
869        // survived and reporting it as complete would understate coverage as
870        // though it were a fact about the tests.
871        let full = write(&[2, 0, 2, 1, 4, u64::from_le_bytes(*b"Test\0\0\0\0"), 0, 0, 0]);
872        for cut in [0, 8, 16, 24, 32] {
873            assert!(
874                read_evidence(&full[..cut.min(full.len())]).is_err(),
875                "a file cut at {cut} bytes must not decode"
876            );
877        }
878        assert!(matches!(
879            read_evidence(&write(&[5, 1])),
880            Err(OwnedEvidenceError::Truncated("probe totals"))
881        ));
882    }
883
884    #[test]
885    fn a_run_with_no_tests_is_an_error_not_an_empty_report() {
886        let evidence = OwnedEvidence::default();
887        let manifest = CoverageManifest {
888            decisions: Vec::new(),
889            points: Vec::new(),
890            branches: Vec::new(),
891            limitations: Vec::new(),
892            unmeasured: Vec::new(),
893            scope: None,
894        };
895        assert_eq!(
896            build_frontend_run(OwnedRunInputs {
897                declaration: go_declaration(),
898                environment: "go",
899                manifest: &manifest,
900                probes: &BTreeMap::new(),
901                evidence: &evidence,
902                outcomes: &[],
903                run_id: "run",
904                generated_at: "now",
905                test_exit_code: 0,
906                coverage_model: go_coverage_model(),
907            })
908            .err(),
909            Some(OwnedEvidenceError::NoTests)
910        );
911    }
912
913    #[test]
914    fn each_declaration_says_what_its_runner_can_and_cannot_attribute() {
915        // The JVM's differs in substance, not just in name: it lists TestNG as
916        // a gap because TestNG is not a platform engine, and parallel
917        // execution as another.
918        let jvm = jvm_declaration();
919        let jvm_runner = &jvm.runners[0];
920        assert_eq!(jvm_runner.runner, "junit-platform");
921        let gaps = jvm_runner
922            .limitations
923            .iter()
924            .map(|limitation| limitation.id.as_str())
925            .collect::<Vec<_>>();
926        assert!(
927            gaps.contains(&"junit-platform-parallel-execution"),
928            "{gaps:?}"
929        );
930        // TestNG is a runner of its own rather than a gap in another: it is
931        // not a platform engine, so it reports through its own listener.
932        let named = jvm
933            .runners
934            .iter()
935            .map(|runner| runner.runner.as_str())
936            .collect::<Vec<_>>();
937        assert_eq!(named, ["junit-platform", "testng"]);
938    }
939
940    #[test]
941    fn the_declaration_says_what_go_can_and_cannot_attribute() {
942        // A frontend that overstates its precision is worse than one that
943        // admits a gap: the report would present guesses as measurements.
944        let declared = go_declaration();
945        let runner = &declared.runners[0];
946        assert_eq!(runner.execution_model, ExecutionModel::SerialInProcess);
947        assert_eq!(runner.attribution.test, AttributionPrecision::Exact);
948        assert_eq!(
949            runner.attribution.assertion,
950            AttributionPrecision::Unavailable
951        );
952        let gaps = runner
953            .limitations
954            .iter()
955            .map(|limitation| limitation.id.as_str())
956            .collect::<Vec<_>>();
957        assert!(gaps.contains(&"go-parallel-tests"), "{gaps:?}");
958        // And every precision below Exact is accounted for, which is what the
959        // contract requires before a reader will accept the run at all.
960        for gap in [
961            "go-phase-linkage-aggregate",
962            "go-action-linkage-unavailable",
963            "go-assertion-linkage-unavailable",
964        ] {
965            assert!(gaps.contains(&gap), "{gaps:?}");
966        }
967    }
968
969    #[test]
970    fn a_test_the_runner_saw_but_that_recorded_nothing_still_appears() {
971        // Dropping it would make the suite look smaller than it is, and a test
972        // that reached no measured line is a fact worth seeing.
973        let manifest = CoverageManifest {
974            decisions: Vec::new(),
975            points: Vec::new(),
976            branches: Vec::new(),
977            limitations: Vec::new(),
978            unmeasured: Vec::new(),
979            scope: None,
980        };
981        let run = build_frontend_run(OwnedRunInputs {
982            declaration: go_declaration(),
983            environment: "go",
984            manifest: &manifest,
985            probes: &BTreeMap::new(),
986            evidence: &OwnedEvidence::default(),
987            outcomes: &[OwnedTestOutcome {
988                name: "TestSilent".into(),
989                runner: String::new(),
990                package: "example.com/p".into(),
991                file: Some("p/x_test.go".into()),
992                status: "passed".into(),
993            }],
994            run_id: "run",
995            generated_at: "now",
996            test_exit_code: 0,
997            coverage_model: go_coverage_model(),
998        })
999        .expect("run");
1000        assert_eq!(run.tests, 1);
1001        let result = &run.request.raw_results[0];
1002        assert_eq!(result.test, "TestSilent");
1003        assert_eq!(result.test_id.as_deref(), Some("example.com/p::TestSilent"));
1004        assert!(result.runtime[0].hits.is_empty());
1005    }
1006
1007    #[test]
1008    fn both_owned_declarations_satisfy_the_contract_they_are_read_back_through() {
1009        // A declaration is written at publication and checked at read. The two
1010        // owned frontends once hardcoded a protocol version while the contract
1011        // moved on, so every Go and JVM run published cleanly and then failed
1012        // the moment anyone asked for its report.
1013        for declaration in [go_declaration(), jvm_declaration()] {
1014            let language = declaration.language.clone();
1015            supercov_contracts::validate_frontend_run_declaration(&declaration)
1016                .unwrap_or_else(|error| panic!("{language} declaration is unreadable: {error}"));
1017        }
1018    }
1019    #[test]
1020    fn merging_processes_unions_the_run_and_keeps_every_test() {
1021        let left = OwnedEvidence {
1022            global: vec![0b01, 0b00],
1023            tests: vec![OwnedTestEvidence {
1024                name: "TestA".into(),
1025                status: "passed".into(),
1026                ..Default::default()
1027            }],
1028            widths: vec![2],
1029            decision_vectors: vec![vec![0b01]],
1030        };
1031        let right = OwnedEvidence {
1032            global: vec![0b10, 0b10],
1033            tests: vec![OwnedTestEvidence {
1034                name: "TestB".into(),
1035                status: "failed".into(),
1036                ..Default::default()
1037            }],
1038            widths: vec![2],
1039            decision_vectors: vec![vec![0b01, 0b11]],
1040        };
1041        let merged = merge_evidence(vec![left, right]);
1042        // Run-wide totals are a union: a probe any package reached is reached.
1043        assert_eq!(merged.global, [0b11, 0b10]);
1044        // Tests accumulate, because each belongs to exactly one binary.
1045        assert_eq!(
1046            merged
1047                .tests
1048                .iter()
1049                .map(|test| test.name.as_str())
1050                .collect::<Vec<_>>(),
1051            ["TestA", "TestB"]
1052        );
1053        // And a vector seen in both packages is one vector, not two.
1054        assert_eq!(merged.decision_vectors, [vec![0b01, 0b11]]);
1055    }
1056}