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    /// The tags the framework gave the test: a JUnit `@Tag`, a TestNG group.
76    pub tags: Vec<String>,
77    /// Whether what this test reached could be credited to it. False for a Go
78    /// test that called `t.Parallel()`, an Example or a Fuzz target: the test
79    /// ran and its coverage is in the run-wide totals, and nothing can say
80    /// which of those totals were its.
81    pub unattributed: bool,
82    /// Probe id to the bitmask it was observed with.
83    pub probes: BTreeMap<u32, u32>,
84    pub vectors: Vec<PackedVector>,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Default)]
88pub struct OwnedEvidence {
89    /// Run-wide probe totals, the union of every test's.
90    pub global: Vec<u32>,
91    pub tests: Vec<OwnedTestEvidence>,
92    /// Conditions per decision, as the manifest ordered them.
93    pub widths: Vec<u8>,
94    /// Every distinct vector each decision produced, run-wide.
95    pub decision_vectors: Vec<Vec<u64>>,
96}
97
98struct Cursor<'a> {
99    bytes: &'a [u8],
100    offset: usize,
101}
102
103impl Cursor<'_> {
104    fn u64(&mut self, part: &'static str) -> Result<u64, OwnedEvidenceError> {
105        let end = self.offset + 8;
106        let slice = self
107            .bytes
108            .get(self.offset..end)
109            .ok_or(OwnedEvidenceError::Truncated(part))?;
110        self.offset = end;
111        Ok(u64::from_le_bytes(slice.try_into().expect("eight bytes")))
112    }
113
114    fn text(&mut self, length: usize, part: &'static str) -> Result<String, OwnedEvidenceError> {
115        let end = self.offset + length;
116        let slice = self
117            .bytes
118            .get(self.offset..end)
119            .ok_or(OwnedEvidenceError::Truncated(part))?;
120        self.offset = end;
121        String::from_utf8(slice.to_vec()).map_err(|_| OwnedEvidenceError::Truncated(part))
122    }
123}
124
125/// Read the transport. A truncated file is an error rather than a short read:
126/// a run that was killed half way through has partial evidence, and reporting
127/// it as though it were complete would understate coverage as a fact.
128pub fn read_evidence(bytes: &[u8]) -> Result<OwnedEvidence, OwnedEvidenceError> {
129    let mut cursor = Cursor { bytes, offset: 0 };
130    let probe_count = cursor.u64("probe totals")? as usize;
131    let mut global = Vec::with_capacity(probe_count);
132    for _ in 0..probe_count {
133        global.push(cursor.u64("probe totals")? as u32);
134    }
135    let test_count = cursor.u64("test count")? as usize;
136    let mut tests = Vec::with_capacity(test_count);
137    for _ in 0..test_count {
138        let length = cursor.u64("test name")? as usize;
139        let name = cursor.text(length, "test name")?;
140        let status_length = cursor.u64("test status")? as usize;
141        let status = cursor.text(status_length, "test status")?;
142        // Whether the probes below are this test's own. An empty probe list
143        // means "reached nothing" when they are and "nothing can say what it
144        // reached" when they are not, and the two must never be conflated.
145        let unattributed = cursor.u64("test attribution")? != 0;
146        let runner_length = cursor.u64("test runner")? as usize;
147        let runner = cursor.text(runner_length, "test runner")?;
148        let tag_count = cursor.u64("test tags")? as usize;
149        let mut tags = Vec::with_capacity(tag_count.min(64));
150        for _ in 0..tag_count {
151            let tag_length = cursor.u64("test tags")? as usize;
152            let tag = cursor.text(tag_length, "test tags")?;
153            if !tags.contains(&tag) {
154                tags.push(tag);
155            }
156        }
157        let hits = cursor.u64("test probes")? as usize;
158        let mut probes = BTreeMap::new();
159        for _ in 0..hits {
160            let index = cursor.u64("test probes")? as u32;
161            probes.insert(index, cursor.u64("test probes")? as u32);
162        }
163        let vector_count = cursor.u64("test vectors")? as usize;
164        let mut vectors = Vec::with_capacity(vector_count);
165        for _ in 0..vector_count {
166            let decision = cursor.u64("test vectors")? as u32;
167            vectors.push(PackedVector {
168                decision,
169                key: cursor.u64("test vectors")?,
170            });
171        }
172        tests.push(OwnedTestEvidence {
173            name,
174            status,
175            runner,
176            tags,
177            unattributed,
178            probes,
179            vectors,
180        });
181    }
182    let decision_count = cursor.u64("decision table")? as usize;
183    let mut widths = Vec::with_capacity(decision_count);
184    let mut decision_vectors = Vec::with_capacity(decision_count);
185    for _ in 0..decision_count {
186        widths.push(cursor.u64("decision table")? as u8);
187        let keys = cursor.u64("decision table")? as usize;
188        let mut seen = Vec::with_capacity(keys);
189        for _ in 0..keys {
190            seen.push(cursor.u64("decision table")?);
191        }
192        decision_vectors.push(seen);
193    }
194    Ok(OwnedEvidence {
195        global,
196        tests,
197        widths,
198        decision_vectors,
199    })
200}
201
202const PACKED_VALUE_SHIFT: u32 = 24;
203const PACKED_OUTCOME_SHIFT: u32 = 48;
204
205/// Unpack one evaluation into the vector MC/DC is judged from.
206///
207/// A condition the run never evaluated is `None`, not `false`. That is the
208/// distinction the whole measurement rests on: `a && b` with `a` false says
209/// nothing about `b`, and recording it as `false` would claim it had been
210/// tested.
211pub fn unpack_vector(key: u64, width: u8) -> McdcVector {
212    let evaluated = key & ((1 << PACKED_VALUE_SHIFT) - 1);
213    let values = (key >> PACKED_VALUE_SHIFT) & ((1 << PACKED_VALUE_SHIFT) - 1);
214    McdcVector {
215        values: (0..width)
216            .map(|index| {
217                let bit = 1_u64 << index;
218                (evaluated & bit != 0).then_some(values & bit != 0)
219            })
220            .collect(),
221        outcome: (key >> PACKED_OUTCOME_SHIFT) & 1 == 1,
222    }
223}
224
225/// How a test's name and package became the identity the report uses.
226#[derive(Debug, Clone, PartialEq, Eq)]
227pub struct OwnedTestOutcome {
228    pub name: String,
229    pub package: String,
230    pub file: Option<String>,
231    /// `passed`, `failed` or `skipped`, as the runner reported it.
232    pub status: String,
233    /// Which runner announced it, as the frontend declares that runner. Empty
234    /// where the frontend has only one.
235    pub runner: String,
236    /// Whether what this test reached could be credited to it. False for a Go
237    /// test that called `t.Parallel()`, an Example or a Fuzz target: it ran,
238    /// and its coverage is in the run-wide record rather than in its own.
239    pub attributed: bool,
240}
241
242/// How a language's runner attributes what it records.
243///
244/// Declared rather than assumed. A frontend that overstates its precision is
245/// worse than one that admits a gap, because the report would then present a
246/// guess as a measurement.
247pub fn go_declaration() -> FrontendRunDeclaration {
248    FrontendRunDeclaration {
249        protocol_version: LANGUAGE_FRONTEND_PROTOCOL_VERSION,
250        frontend_id: "supercov-go".into(),
251        frontend_version: "go-owned-v1".into(),
252        language: "go".into(),
253        // Supercov owns the probes: `go test -cover` is a development oracle
254        // here, not a product input.
255        structural_source: supercov_contracts::StructuralSource::OwnedProbes,
256        runners: vec![FrontendRunnerDeclaration {
257            runner: "go-test".into(),
258            // Go runs a package's tests one after another unless a test opts
259            // into parallelism, and Supercov runs one package at a time.
260            execution_model: ExecutionModel::SerialInProcess,
261            attribution: exact_per_test(),
262            limitations: {
263                let mut limitations = owned_attribution_limitations("go");
264                limitations.push(FrontendLimitation {
265                    id: "go-parallel-tests".into(),
266                    scopes: vec![FrontendLimitationScope::Test],
267                    reason:
268                        "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"
269                            .into(),
270                });
271                limitations
272            },
273        }],
274        structural_limitations: Vec::new(),
275    }
276}
277
278/// What a Go run's numbers mean, so a reader is never left to infer it.
279pub fn go_coverage_model() -> CoverageModelDeclaration {
280    CoverageModelDeclaration {
281        language: "go".into(),
282        variant: "go-owned-probes-v1".into(),
283        name: "supercov-go-owned-v1".into(),
284        completeness_meaning: "Every obligation Supercov derived from the module's own Go sources was observed; explicit manifest limitations identify unmeasured Go surfaces.".into(),
285        measured: vec![
286            "owned Go statements and function entries".into(),
287            "owned atomic condition vectors and decision outcomes".into(),
288            "exact per-test attribution for tests that do not call t.Parallel()".into(),
289        ],
290        not_measured: vec![
291            "generated code, vendored packages and testdata".into(),
292            "work a test does after calling t.Parallel(), which counts run-wide".into(),
293            "causal linkage to individual actions".into(),
294            "all input values, semantic partitions, paths, or concurrency interleavings".into(),
295            "mutation score or assertion fault-detection strength".into(),
296        ],
297    }
298}
299
300/// The JVM's, where each framework reports a test's start and finish and
301/// attribution follows the lifecycle the framework itself defines.
302///
303/// Two runners, because there are two lifecycles. The JUnit Platform covers
304/// every engine built on it — Jupiter, Vintage, Kotest, Spock — and TestNG,
305/// which is not one, reports through its own listener interface. They measure
306/// the same way and say so with the same precision; what differs is who does
307/// the announcing.
308pub fn jvm_declaration() -> FrontendRunDeclaration {
309    let runner = |name: &str, concurrency: &str| FrontendRunnerDeclaration {
310        runner: name.into(),
311        execution_model: ExecutionModel::SerialInProcess,
312        attribution: exact_per_test(),
313        limitations: {
314            let mut limitations = owned_attribution_limitations(name);
315            limitations.push(FrontendLimitation {
316                id: format!("{name}-parallel-execution"),
317                scopes: vec![FrontendLimitationScope::Test],
318                reason: format!(
319                    "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"
320                ),
321            });
322            limitations
323        },
324    };
325    FrontendRunDeclaration {
326        protocol_version: LANGUAGE_FRONTEND_PROTOCOL_VERSION,
327        frontend_id: "supercov-jvm".into(),
328        frontend_version: "jvm-owned-v1".into(),
329        language: "jvm".into(),
330        structural_source: supercov_contracts::StructuralSource::OwnedProbes,
331        runners: vec![
332            runner("junit-platform", "JUnit parallel execution enabled"),
333            runner("testng", "TestNG's parallel suites or methods"),
334        ],
335        structural_limitations: Vec::new(),
336    }
337}
338
339/// What a JVM run's numbers mean.
340pub fn jvm_coverage_model() -> CoverageModelDeclaration {
341    CoverageModelDeclaration {
342        language: "jvm".into(),
343        variant: "jvm-owned-probes-v1".into(),
344        name: "supercov-jvm-owned-v1".into(),
345        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(),
346        measured: vec![
347            "owned Java and Kotlin statements and method entries".into(),
348            "owned atomic condition vectors and decision outcomes".into(),
349            "exact per-test attribution through the JUnit Platform's own lifecycle".into(),
350        ],
351        not_measured: vec![
352            "generated sources, and bytecode with no source in the project".into(),
353            "tests run concurrently, which count run-wide and drop condition coverage".into(),
354            "causal linkage to individual actions".into(),
355            "all input values, semantic partitions, paths, or concurrency interleavings".into(),
356            "mutation score or assertion fault-detection strength".into(),
357        ],
358    }
359}
360
361/// What the owned frontends cannot attribute, and why.
362///
363/// The contract will not accept a precision below `Exact` without a limitation
364/// naming that scope, which is the right rule: a number that quietly means
365/// less than it appears to is worse than one that says so. Supercov measures
366/// statements and decisions here, so a test's individual actions and
367/// assertions are outside what it claims, and a phase is a lifecycle the
368/// probes never see.
369fn owned_attribution_limitations(language: &str) -> Vec<FrontendLimitation> {
370    vec![
371        FrontendLimitation {
372            id: format!("{language}-phase-linkage-aggregate"),
373            scopes: vec![FrontendLimitationScope::Phase],
374            reason:
375                "probes record what a test reached, not which of its setup, body or teardown phases reached it"
376                    .into(),
377        },
378        FrontendLimitation {
379            id: format!("{language}-action-linkage-unavailable"),
380            scopes: vec![FrontendLimitationScope::Action],
381            reason: "there is no general application-action lifecycle to link coverage to".into(),
382        },
383        FrontendLimitation {
384            id: format!("{language}-assertion-linkage-unavailable"),
385            scopes: vec![FrontendLimitationScope::Assertion],
386            reason:
387                "coverage is attributed to the test that reached the code, not to the assertion that checked it"
388                    .into(),
389        },
390    ]
391}
392
393fn exact_per_test() -> FrontendAttribution {
394    FrontendAttribution {
395        run: AttributionPrecision::Exact,
396        worker: AttributionPrecision::Exact,
397        test: AttributionPrecision::Exact,
398        retry: AttributionPrecision::Exact,
399        phase: AttributionPrecision::Aggregate,
400        // Supercov measures statements and decisions here, not the individual
401        // actions or assertions inside a test.
402        action: AttributionPrecision::Unavailable,
403        assertion: AttributionPrecision::Unavailable,
404    }
405}
406
407/// Merge what several processes recorded into one run's evidence.
408///
409/// Both owned frontends need this, because both run a suite as more than one
410/// process: `go test` builds a binary per package, and a multi-module JVM
411/// build forks a JVM per module. Probe indices are project-wide, so run-wide
412/// totals are a union of equal-length arrays, and test records simply
413/// accumulate — each belongs to exactly the process that produced it.
414pub fn merge_evidence(parts: Vec<OwnedEvidence>) -> OwnedEvidence {
415    let mut merged = OwnedEvidence::default();
416    for part in parts {
417        if merged.global.len() < part.global.len() {
418            merged.global.resize(part.global.len(), 0);
419        }
420        for (slot, value) in merged.global.iter_mut().zip(part.global) {
421            *slot |= value;
422        }
423        if merged.widths.len() < part.widths.len() {
424            merged.widths.resize(part.widths.len(), 0);
425            merged
426                .decision_vectors
427                .resize(part.widths.len(), Vec::new());
428        }
429        for (id, width) in part.widths.into_iter().enumerate() {
430            merged.widths[id] = merged.widths[id].max(width);
431        }
432        for (id, keys) in part.decision_vectors.into_iter().enumerate() {
433            let seen = &mut merged.decision_vectors[id];
434            for key in keys {
435                if !seen.contains(&key) {
436                    seen.push(key);
437                }
438            }
439        }
440        merged.tests.extend(part.tests);
441    }
442    merged
443}
444
445/// A short, stable identity derived from what makes the thing itself, so two
446/// runs of the same test agree on what to call it.
447fn stable_id(prefix: &str, values: &[&str]) -> String {
448    use sha2::{Digest, Sha256};
449    let mut hash = Sha256::new();
450    for value in values {
451        hash.update(value.as_bytes());
452        hash.update([0]);
453    }
454    let digest = hash.finalize();
455    let mut encoded = String::with_capacity(prefix.len() + 25);
456    encoded.push_str(prefix);
457    encoded.push(':');
458    for byte in &digest[..12] {
459        use std::fmt::Write as _;
460        write!(&mut encoded, "{byte:02x}").expect("string formatting");
461    }
462    encoded
463}
464
465/// The obligation each probe answers for, by id.
466fn obligations(probes: &BTreeMap<u64, GoProbe>) -> BTreeMap<u32, String> {
467    probes
468        .iter()
469        .map(|(id, probe)| {
470            let obligation = match &probe.target {
471                GoProbeTarget::Statement { id } | GoProbeTarget::Function { id } => id.clone(),
472                GoProbeTarget::Alternative { alternative, .. } => alternative.clone(),
473            };
474            (*id as u32, obligation)
475        })
476        .collect()
477}
478
479fn snapshot(
480    environment: &str,
481    evidence: &OwnedTestEvidence,
482    manifest: &CoverageManifest,
483    by_probe: &BTreeMap<u32, String>,
484    phase: &str,
485) -> RuntimeSnapshot {
486    let mut events = Vec::new();
487    let mut clock = 0_i64;
488    let hits = evidence
489        .probes
490        .keys()
491        .filter_map(|probe| by_probe.get(probe).cloned())
492        .collect::<BTreeSet<_>>();
493    for id in &hits {
494        events.push(RuntimeEvent {
495            event_type: "hit".into(),
496            id: id.clone(),
497            vector: None,
498            timestamp_ms: clock,
499            phase_id: Some(phase.to_owned()),
500            statement_id: None,
501            environment: environment.into(),
502        });
503        clock += 1;
504    }
505    let mut by_decision: BTreeMap<u32, Vec<u64>> = BTreeMap::new();
506    for vector in &evidence.vectors {
507        by_decision
508            .entry(vector.decision)
509            .or_default()
510            .push(vector.key);
511    }
512    let mut decisions = Vec::new();
513    for (index, meta) in manifest.decisions.iter().enumerate() {
514        let Some(keys) = by_decision.get(&(index as u32)) else {
515            continue;
516        };
517        let width = meta.conditions.len() as u8;
518        let observed = keys
519            .iter()
520            .map(|key| unpack_vector(*key, width))
521            .collect::<Vec<_>>();
522        for vector in &observed {
523            events.push(RuntimeEvent {
524                event_type: "decision".into(),
525                id: meta.id.clone(),
526                vector: Some(vector.clone()),
527                timestamp_ms: clock,
528                phase_id: Some(phase.to_owned()),
529                statement_id: None,
530                environment: environment.into(),
531            });
532            clock += 1;
533        }
534        decisions.push(DecisionSnapshot {
535            meta: meta.clone(),
536            vectors: observed,
537        });
538    }
539    RuntimeSnapshot {
540        decisions,
541        hits: hits.into_iter().collect(),
542        events,
543        logicals: Vec::new(),
544        phase_id: None,
545    }
546}
547
548pub struct OwnedFrontendRun {
549    pub declaration: FrontendRunDeclaration,
550    pub request: CoverageReportRequest,
551    pub tests: usize,
552}
553
554impl OwnedFrontendRun {
555    /// The archive a run publishes: what the numbers mean, who produced them,
556    /// the obligations they are measured against, and one record per test.
557    pub fn archive_entries(&self) -> Result<Vec<EvidenceArchiveEntry>, serde_json::Error> {
558        let model = PersistedCoverageModel::from_declaration(
559            self.request
560                .coverage_model
561                .as_ref()
562                .expect("an owned frontend always declares a coverage model"),
563        )
564        .expect("owned coverage models are contract-valid");
565        let mut entries = vec![
566            EvidenceArchiveEntry {
567                path: "coverage-model.json".into(),
568                contents: serde_json::to_vec(&model)?,
569            },
570            EvidenceArchiveEntry {
571                path: "frontend.json".into(),
572                contents: serde_json::to_vec(&self.declaration)?,
573            },
574            EvidenceArchiveEntry {
575                path: "manifest.json".into(),
576                contents: serde_json::to_vec(&self.request.manifest)?,
577            },
578        ];
579        for (index, result) in self.request.raw_results.iter().enumerate() {
580            entries.push(EvidenceArchiveEntry {
581                path: format!("results/{index:08}/mcdc.json"),
582                contents: serde_json::to_vec(result)?,
583            });
584        }
585        Ok(entries)
586    }
587}
588
589/// Build the report request from what the run recorded.
590///
591/// A test the runner reported but that announced nothing still becomes a
592/// result, with no coverage. Dropping it would make a suite look smaller than
593/// it is, and a test that ran without reaching any measured line is a fact
594/// worth seeing rather than an absence worth hiding.
595/// Everything a report needs about one run of one owned frontend.
596pub struct OwnedRunInputs<'a> {
597    pub declaration: FrontendRunDeclaration,
598    /// The label runtime events carry, so a reader can tell which frontend
599    /// produced them.
600    pub environment: &'a str,
601    pub manifest: &'a CoverageManifest,
602    pub probes: &'a BTreeMap<u64, GoProbe>,
603    pub evidence: &'a OwnedEvidence,
604    pub outcomes: &'a [OwnedTestOutcome],
605    pub run_id: &'a str,
606    pub generated_at: &'a str,
607    pub test_exit_code: i32,
608    /// What the numbers mean. Carried rather than inferred from the language,
609    /// so a frontend cannot quietly inherit another's claims.
610    pub coverage_model: CoverageModelDeclaration,
611}
612
613/// Build the report request from what the run recorded.
614///
615/// A test the runner reported but that announced nothing still becomes a
616/// result, with no coverage. Dropping it would make a suite look smaller than
617/// it is, and a test that ran without reaching any measured line is a fact
618/// worth seeing rather than an absence worth hiding.
619pub fn build_frontend_run(inputs: OwnedRunInputs) -> Result<OwnedFrontendRun, OwnedEvidenceError> {
620    let OwnedRunInputs {
621        declaration,
622        environment,
623        manifest,
624        probes,
625        evidence,
626        outcomes,
627        run_id,
628        generated_at,
629        test_exit_code,
630        coverage_model,
631    } = inputs;
632    // The runner every result claims must be one the declaration names: a
633    // result attributed to a runner nobody declared is a result nothing can
634    // say the precision of, and the reader refuses it rather than guess.
635    let default_runner = declaration
636        .runners
637        .first()
638        .map(|runner| runner.runner.clone())
639        .ok_or(OwnedEvidenceError::NoTests)?;
640    let declared = declaration
641        .runners
642        .iter()
643        .map(|runner| runner.runner.as_str())
644        .collect::<BTreeSet<_>>();
645    let source = declaration.frontend_version.clone();
646    let by_probe = obligations(probes);
647    let recorded = evidence
648        .tests
649        .iter()
650        .map(|test| (test.name.clone(), test))
651        .collect::<BTreeMap<_, _>>();
652    let empty = OwnedTestEvidence::default();
653    let mut raw_results = outcomes
654        .iter()
655        .map(|outcome| {
656            let test = recorded.get(&outcome.name).copied().unwrap_or(&empty);
657            // A test nothing could be credited to takes no hits at all, and
658            // says so. Handing it the record's probes would be worse than
659            // wrong: the runtime gives an unattributed record none, so the
660            // result would read as a test that reached nothing.
661            let test = if outcome.attributed { test } else { &empty };
662            let test_id = format!("{}::{}", outcome.package, outcome.name);
663            let phase = test_phase(&test_id);
664            let provenance = TestProvenance {
665                // What the record says, when the frontend declares it. A JVM
666                // project can run JUnit and TestNG in one JVM, and a result
667                // naming the wrong one would claim it was attributed by a
668                // lifecycle that never saw it.
669                runner: if declared.contains(outcome.runner.as_str()) {
670                    outcome.runner.clone()
671                } else {
672                    default_runner.clone()
673                },
674                kind: crate::test_kind::from_tags(&test.tags)
675                    .unwrap_or("unit")
676                    .into(),
677                project: Some(outcome.package.clone()),
678                source: source.clone(),
679                tags: test.tags.clone(),
680            };
681            RawTestResult {
682                scope: Some(ExecutionScope {
683                    version: 1,
684                    run_id: run_id.to_owned(),
685                    // The unit that ran as its own process: a Go test binary
686                    // is built per package, and a JVM suite is one JVM. A
687                    // worker identity the reader can trust is what lets it
688                    // accept exact attribution at all.
689                    worker_id: outcome.package.clone(),
690                    test_id: test_id.clone(),
691                    test_key: stable_id("owned-test", &[&outcome.package, &outcome.name]),
692                    retry: 0,
693                    attempt_id: stable_id(
694                        "owned-attempt",
695                        &[run_id, &outcome.package, &outcome.name, "0"],
696                    ),
697                }),
698                test_id: Some(test_id),
699                test: outcome.name.clone(),
700                test_file: outcome.file.clone(),
701                title: None,
702                retry: Some(0),
703                status: Some(outcome.status.clone()),
704                expected_status: None,
705                flaky: false,
706                provenance: provenance.clone(),
707                role: "test".into(),
708                attribution: if outcome.attributed {
709                    crate::coverage_report::ATTRIBUTION_EXACT
710                } else {
711                    crate::coverage_report::ATTRIBUTION_RUN_WIDE
712                }
713                .into(),
714                // Every event a probe produces belongs to the test body: the
715                // runtime binds coverage at the test boundary and knows
716                // nothing of setup or teardown. One declared phase says
717                // exactly that, and leaves the events with somewhere real to
718                // point rather than at a phase nobody declared.
719                phases: vec![CoveragePhase {
720                    id: phase.clone(),
721                    kind: "test".into(),
722                    operation: format!("{} {}", provenance.runner, outcome.name),
723                    source: outcome.file.clone(),
724                    caused_by_phase_id: None,
725                    started_at_ms: 0,
726                    ended_at_ms: None,
727                    status: Some(outcome.status.clone()),
728                    error: None,
729                }],
730                runtime: vec![snapshot(environment, test, manifest, &by_probe, &phase)],
731                browser: Vec::new(),
732                server: Vec::new(),
733            }
734        })
735        .collect::<Vec<_>>();
736    // Everything the run reached that no test claimed.
737    //
738    // A Go test that calls t.Parallel() is deliberately left unattributed:
739    // probes are a store into one shared array, so what it reaches while
740    // others run beside it cannot be credited to it. The declaration says that
741    // coverage still counts run-wide -- and until now nothing carried it, so a
742    // suite written the way Go suites are written measured almost nothing and
743    // was told nothing about why. samber/lo calls t.Parallel() 1606 times.
744    //
745    // It is recorded as its own record, under a role that is not a test,
746    // holding only what the tests did not claim: reached, by nobody nameable.
747    let claimed = evidence
748        .tests
749        .iter()
750        .filter(|test| !test.unattributed)
751        .flat_map(|test| test.probes.keys().copied())
752        .collect::<BTreeSet<_>>();
753    let unclaimed = evidence
754        .global
755        .iter()
756        .enumerate()
757        .filter(|(index, mask)| **mask != 0 && !claimed.contains(&(*index as u32)))
758        .map(|(index, mask)| (index as u32, *mask))
759        .collect::<BTreeMap<_, _>>();
760    if !unclaimed.is_empty() {
761        // Whether this counts as coverage turns on whether it was produced by
762        // something that passed, which is the same rule every test record
763        // obeys: a failing test's coverage is not evidence that anything
764        // works. Here the producers cannot be named individually, so the
765        // question is asked of the run: if every test passed, everything this
766        // holds was produced by a passing test. If any failed, there is no
767        // telling which of this came from it, and none of it counts.
768        let status = if test_exit_code == 0 {
769            "passed"
770        } else {
771            "failed"
772        };
773        let test_id = format!("{}::background", declaration.language);
774        let phase = test_phase(&test_id);
775        let background = OwnedTestEvidence {
776            name: "background".into(),
777            status: "unknown".into(),
778            runner: String::new(),
779            tags: Vec::new(),
780            // This record is where unattributed coverage goes; it is not
781            // itself a test whose coverage went elsewhere.
782            unattributed: false,
783            probes: unclaimed,
784            vectors: Vec::new(),
785        };
786        raw_results.push(RawTestResult {
787            scope: Some(ExecutionScope {
788                version: 1,
789                run_id: run_id.to_owned(),
790                worker_id: "background".into(),
791                test_id: test_id.clone(),
792                test_key: stable_id("owned-background", &[run_id]),
793                retry: 0,
794                attempt_id: stable_id("owned-background-attempt", &[run_id]),
795            }),
796            test_id: Some(test_id),
797            test: "execution no test could be credited with".into(),
798            test_file: None,
799            title: None,
800            retry: Some(0),
801            status: Some(status.into()),
802            expected_status: None,
803            flaky: false,
804            provenance: TestProvenance {
805                runner: default_runner.clone(),
806                kind: "background".into(),
807                project: None,
808                source: source.clone(),
809                tags: Vec::new(),
810            },
811            role: "background".into(),
812            attribution: crate::coverage_report::ATTRIBUTION_EXACT.into(),
813            phases: vec![CoveragePhase {
814                id: phase.clone(),
815                kind: "background".into(),
816                operation: "execution outside any test".into(),
817                source: None,
818                caused_by_phase_id: None,
819                started_at_ms: 0,
820                ended_at_ms: Some(0),
821                status: Some(status.into()),
822                error: None,
823            }],
824            runtime: vec![snapshot(
825                environment,
826                &background,
827                manifest,
828                &by_probe,
829                &phase,
830            )],
831            browser: Vec::new(),
832            server: Vec::new(),
833        });
834    }
835
836    // Nothing to publish only when neither a test nor the run as a whole
837    // reached anything. An empty list of outcomes on its own is not that: a Go
838    // package whose every test calls `t.Parallel()` announces no test by
839    // design, and refusing it there discarded a fully measured package --
840    // lines, branches and MC/DC alike -- over the fact that the evidence had
841    // no owner. The background record above is the owner it has.
842    if raw_results.is_empty() {
843        return Err(OwnedEvidenceError::NoTests);
844    }
845
846    // A declaration naming a runner that produced nothing claims something the
847    // run did not do, and the reader refuses it — rightly. A JVM frontend can
848    // drive JUnit and TestNG, but any one project usually runs one of them, so
849    // the run declares the ones it actually observed.
850    let observed = raw_results
851        .iter()
852        .map(|result| result.provenance.runner.as_str())
853        .collect::<BTreeSet<_>>();
854    let mut declaration = declaration;
855
856    // What the manifest says could not be measured, the declaration has to
857    // name too: the reader checks that the two agree, so a limitation cannot
858    // appear in one and be missing from the other. The ids are derived per
859    // obligation from the file and the node, so a static declaration could
860    // never have listed them and this is the only place that knows them.
861    declaration.structural_limitations = manifest
862        .limitations
863        .iter()
864        .filter_map(|limitation| limitation.get("id")?.as_str().map(str::to_owned))
865        .collect::<BTreeSet<_>>()
866        .into_iter()
867        .collect();
868
869    if declaration
870        .runners
871        .iter()
872        .any(|runner| observed.contains(runner.runner.as_str()))
873    {
874        declaration
875            .runners
876            .retain(|runner| observed.contains(runner.runner.as_str()));
877    }
878    Ok(OwnedFrontendRun {
879        declaration,
880        tests: raw_results.len(),
881        request: CoverageReportRequest {
882            run_id: run_id.to_owned(),
883            manifest: manifest.clone(),
884            raw_results,
885            generated_at: generated_at.to_owned(),
886            coverage_model: Some(coverage_model),
887            integrity: None,
888            test_exit_code: ExitCodeInput::Present(Some(test_exit_code)),
889        },
890    })
891}
892
893#[cfg(test)]
894mod tests {
895    use super::*;
896
897    fn write(values: &[u64]) -> Vec<u8> {
898        values.iter().flat_map(|v| v.to_le_bytes()).collect()
899    }
900
901    #[test]
902    fn an_unevaluated_condition_is_absent_rather_than_false() {
903        // The distinction the whole measurement rests on. `a && b` with `a`
904        // false says nothing about `b`; recording it as false would claim it
905        // had been tested, and independence would be judged from a fact that
906        // never happened.
907        let short_circuited = unpack_vector(0b01, 2);
908        assert_eq!(short_circuited.values, [Some(false), None]);
909        assert!(!short_circuited.outcome);
910
911        let both = unpack_vector(0b11 | (0b11 << 24) | (1 << 48), 2);
912        assert_eq!(both.values, [Some(true), Some(true)]);
913        assert!(both.outcome);
914
915        // A condition evaluated and false is Some(false), which is a different
916        // vector from the one above.
917        let first_false = unpack_vector(0b11 | (0b01 << 24), 2);
918        assert_eq!(first_false.values, [Some(true), Some(false)]);
919    }
920
921    #[test]
922    fn a_test_carries_the_tags_its_framework_gave_it() {
923        let mut bytes = Vec::new();
924        let mut put = |value: u64| bytes.extend_from_slice(&value.to_le_bytes());
925        put(0); // no probes
926        put(1); // one test
927        let text = |bytes: &mut Vec<u8>, value: &str| {
928            bytes.extend_from_slice(&(value.len() as u64).to_le_bytes());
929            bytes.extend_from_slice(value.as_bytes());
930        };
931        text(&mut bytes, "ShopTest.discounts()");
932        text(&mut bytes, "passed");
933        bytes.extend_from_slice(&0_u64.to_le_bytes()); // attributed
934        text(&mut bytes, "junit-platform");
935        bytes.extend_from_slice(&3_u64.to_le_bytes());
936        for tag in ["api", "e2e", "api"] {
937            text(&mut bytes, tag);
938        }
939        for value in [0_u64, 0, 0] {
940            // no probes, no vectors, no decisions
941            bytes.extend_from_slice(&value.to_le_bytes());
942        }
943        let evidence = read_evidence(&bytes).unwrap();
944        assert_eq!(evidence.tests[0].tags, ["api", "e2e"]);
945        assert_eq!(evidence.tests[0].runner, "junit-platform");
946    }
947
948    #[test]
949    fn a_truncated_file_is_refused_rather_than_read_short() {
950        // A run killed part way through has partial evidence. Reading what
951        // survived and reporting it as complete would understate coverage as
952        // though it were a fact about the tests.
953        let full = write(&[2, 0, 2, 1, 4, u64::from_le_bytes(*b"Test\0\0\0\0"), 0, 0, 0]);
954        for cut in [0, 8, 16, 24, 32] {
955            assert!(
956                read_evidence(&full[..cut.min(full.len())]).is_err(),
957                "a file cut at {cut} bytes must not decode"
958            );
959        }
960        assert!(matches!(
961            read_evidence(&write(&[5, 1])),
962            Err(OwnedEvidenceError::Truncated("probe totals"))
963        ));
964    }
965
966    #[test]
967    fn a_run_with_no_tests_is_an_error_not_an_empty_report() {
968        // Nothing announced itself and nothing was reached: there is no run
969        // here, and an empty report would claim a floor it never established.
970        // What makes this an error is the second half, not the first -- see
971        // the test below.
972        let evidence = OwnedEvidence::default();
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            assertion_sites: Vec::new(),
981        };
982        assert_eq!(
983            build_frontend_run(OwnedRunInputs {
984                declaration: go_declaration(),
985                environment: "go",
986                manifest: &manifest,
987                probes: &BTreeMap::new(),
988                evidence: &evidence,
989                outcomes: &[],
990                run_id: "run",
991                generated_at: "now",
992                test_exit_code: 0,
993                coverage_model: go_coverage_model(),
994            })
995            .err(),
996            Some(OwnedEvidenceError::NoTests)
997        );
998    }
999
1000    #[test]
1001    fn a_run_nothing_could_be_attributed_to_is_still_a_run() {
1002        // A Go package where every test calls `t.Parallel()` announces no test
1003        // by design: probes are a store into one shared array, so what a test
1004        // reaches while others run beside it cannot be credited to it. The
1005        // coverage is real and has no owner, and refusing the run over that
1006        // discarded a fully measured package -- lines, branches and MC/DC
1007        // alike -- which is how an idiomatic Go suite came to publish nothing
1008        // at all.
1009        let evidence = OwnedEvidence {
1010            global: vec![1],
1011            ..OwnedEvidence::default()
1012        };
1013        let manifest = CoverageManifest {
1014            decisions: Vec::new(),
1015            points: vec![crate::coverage_report::PointMeta {
1016                id: "go:statement:one".into(),
1017                kind: crate::coverage_analysis::PointKind::Statement,
1018                file: "lib.go".into(),
1019                line: 1,
1020                column: 1,
1021                source: "return 1".into(),
1022                label: None,
1023            }],
1024            branches: Vec::new(),
1025            limitations: Vec::new(),
1026            unmeasured: Vec::new(),
1027            scope: None,
1028            assertion_sites: Vec::new(),
1029        };
1030        let probes = BTreeMap::from([(
1031            0_u64,
1032            GoProbe {
1033                id: 0,
1034                target: GoProbeTarget::Statement {
1035                    id: "go:statement:one".into(),
1036                },
1037                at: 0,
1038            },
1039        )]);
1040        let run = build_frontend_run(OwnedRunInputs {
1041            declaration: go_declaration(),
1042            environment: "go",
1043            manifest: &manifest,
1044            probes: &probes,
1045            evidence: &evidence,
1046            outcomes: &[],
1047            run_id: "run",
1048            generated_at: "now",
1049            test_exit_code: 0,
1050            coverage_model: go_coverage_model(),
1051        })
1052        .expect("a measured package publishes even when no test can claim it");
1053        assert_eq!(run.tests, 1, "the one record is what nobody could claim");
1054        assert_eq!(run.request.raw_results[0].role, "background");
1055        assert_eq!(
1056            run.request.raw_results[0].status.as_deref(),
1057            Some("passed"),
1058            "it counts as coverage because the run passed"
1059        );
1060    }
1061
1062    #[test]
1063    fn each_declaration_says_what_its_runner_can_and_cannot_attribute() {
1064        // The JVM's differs in substance, not just in name: it lists TestNG as
1065        // a gap because TestNG is not a platform engine, and parallel
1066        // execution as another.
1067        let jvm = jvm_declaration();
1068        let jvm_runner = &jvm.runners[0];
1069        assert_eq!(jvm_runner.runner, "junit-platform");
1070        let gaps = jvm_runner
1071            .limitations
1072            .iter()
1073            .map(|limitation| limitation.id.as_str())
1074            .collect::<Vec<_>>();
1075        assert!(
1076            gaps.contains(&"junit-platform-parallel-execution"),
1077            "{gaps:?}"
1078        );
1079        // TestNG is a runner of its own rather than a gap in another: it is
1080        // not a platform engine, so it reports through its own listener.
1081        let named = jvm
1082            .runners
1083            .iter()
1084            .map(|runner| runner.runner.as_str())
1085            .collect::<Vec<_>>();
1086        assert_eq!(named, ["junit-platform", "testng"]);
1087    }
1088
1089    #[test]
1090    fn the_declaration_says_what_go_can_and_cannot_attribute() {
1091        // A frontend that overstates its precision is worse than one that
1092        // admits a gap: the report would present guesses as measurements.
1093        let declared = go_declaration();
1094        let runner = &declared.runners[0];
1095        assert_eq!(runner.execution_model, ExecutionModel::SerialInProcess);
1096        assert_eq!(runner.attribution.test, AttributionPrecision::Exact);
1097        assert_eq!(
1098            runner.attribution.assertion,
1099            AttributionPrecision::Unavailable
1100        );
1101        let gaps = runner
1102            .limitations
1103            .iter()
1104            .map(|limitation| limitation.id.as_str())
1105            .collect::<Vec<_>>();
1106        assert!(gaps.contains(&"go-parallel-tests"), "{gaps:?}");
1107        // And every precision below Exact is accounted for, which is what the
1108        // contract requires before a reader will accept the run at all.
1109        for gap in [
1110            "go-phase-linkage-aggregate",
1111            "go-action-linkage-unavailable",
1112            "go-assertion-linkage-unavailable",
1113        ] {
1114            assert!(gaps.contains(&gap), "{gaps:?}");
1115        }
1116    }
1117
1118    #[test]
1119    fn a_test_nothing_can_credit_is_named_without_being_handed_the_run() {
1120        // This is the whole of the fix in one place. A test that called
1121        // `t.Parallel()` is announced by name and with its real outcome, and a
1122        // record for it exists -- but the probes in that record are a store
1123        // into one shared array, so they are whatever ran beside it. Handing
1124        // them over would read as a measurement of this test, which is the
1125        // failure the run-wide label exists to prevent: it must take no hits
1126        // at all and say why, while the test beside it keeps its own.
1127        let manifest = CoverageManifest {
1128            decisions: Vec::new(),
1129            points: vec![
1130                crate::coverage_report::PointMeta {
1131                    id: "go:statement:one".into(),
1132                    kind: crate::coverage_analysis::PointKind::Statement,
1133                    file: "lib.go".into(),
1134                    line: 1,
1135                    column: 1,
1136                    source: "return 1".into(),
1137                    label: None,
1138                },
1139                crate::coverage_report::PointMeta {
1140                    id: "go:statement:two".into(),
1141                    kind: crate::coverage_analysis::PointKind::Statement,
1142                    file: "lib.go".into(),
1143                    line: 2,
1144                    column: 1,
1145                    source: "return 2".into(),
1146                    label: None,
1147                },
1148            ],
1149            branches: Vec::new(),
1150            limitations: Vec::new(),
1151            unmeasured: Vec::new(),
1152            scope: None,
1153            assertion_sites: Vec::new(),
1154        };
1155        let probes = BTreeMap::from([
1156            (
1157                0_u64,
1158                GoProbe {
1159                    id: 0,
1160                    target: GoProbeTarget::Statement {
1161                        id: "go:statement:one".into(),
1162                    },
1163                    at: 0,
1164                },
1165            ),
1166            (
1167                1_u64,
1168                GoProbe {
1169                    id: 1,
1170                    target: GoProbeTarget::Statement {
1171                        id: "go:statement:two".into(),
1172                    },
1173                    at: 0,
1174                },
1175            ),
1176        ]);
1177        // Both records hold probes. Only one of them owns what it holds.
1178        let evidence = OwnedEvidence {
1179            global: vec![1, 1],
1180            tests: vec![
1181                OwnedTestEvidence {
1182                    name: "TestSerial".into(),
1183                    status: "passed".into(),
1184                    runner: String::new(),
1185                    tags: Vec::new(),
1186                    unattributed: false,
1187                    probes: BTreeMap::from([(0, 1)]),
1188                    vectors: Vec::new(),
1189                },
1190                OwnedTestEvidence {
1191                    name: "TestParallel".into(),
1192                    status: "passed".into(),
1193                    runner: String::new(),
1194                    tags: Vec::new(),
1195                    unattributed: true,
1196                    probes: BTreeMap::from([(0, 1), (1, 1)]),
1197                    vectors: Vec::new(),
1198                },
1199            ],
1200            ..OwnedEvidence::default()
1201        };
1202        let outcome = |name: &str, attributed| OwnedTestOutcome {
1203            name: name.into(),
1204            runner: String::new(),
1205            package: "example.com/p".into(),
1206            file: Some("p/x_test.go".into()),
1207            status: "passed".into(),
1208            attributed,
1209        };
1210        let run = build_frontend_run(OwnedRunInputs {
1211            declaration: go_declaration(),
1212            environment: "go",
1213            manifest: &manifest,
1214            probes: &probes,
1215            evidence: &evidence,
1216            outcomes: &[outcome("TestSerial", true), outcome("TestParallel", false)],
1217            run_id: "run",
1218            generated_at: "now",
1219            test_exit_code: 0,
1220            coverage_model: go_coverage_model(),
1221        })
1222        .expect("run");
1223
1224        let result = |name: &str| {
1225            run.request
1226                .raw_results
1227                .iter()
1228                .find(|result| result.test == name)
1229                .unwrap_or_else(|| panic!("{name} is missing from the run"))
1230        };
1231        let serial = result("TestSerial");
1232        assert_eq!(
1233            serial.attribution,
1234            crate::coverage_report::ATTRIBUTION_EXACT
1235        );
1236        assert_eq!(
1237            serial.runtime[0].hits,
1238            ["go:statement:one"],
1239            "a test that ran alone keeps what it reached"
1240        );
1241
1242        let parallel = result("TestParallel");
1243        assert_eq!(
1244            parallel.attribution,
1245            crate::coverage_report::ATTRIBUTION_RUN_WIDE,
1246            "it ran, and what it reached belongs to the run rather than to it"
1247        );
1248        assert_eq!(
1249            parallel.status.as_deref(),
1250            Some("passed"),
1251            "unattributed is not unknown: the runner said how it ended"
1252        );
1253        assert!(
1254            parallel.runtime[0].hits.is_empty(),
1255            "it was handed the record's probes and they are not its own: {:?}",
1256            parallel.runtime[0].hits
1257        );
1258    }
1259
1260    #[test]
1261    fn a_test_the_runner_saw_but_that_recorded_nothing_still_appears() {
1262        // Dropping it would make the suite look smaller than it is, and a test
1263        // that reached no measured line is a fact worth seeing.
1264        let manifest = CoverageManifest {
1265            decisions: Vec::new(),
1266            points: Vec::new(),
1267            branches: Vec::new(),
1268            limitations: Vec::new(),
1269            unmeasured: Vec::new(),
1270            scope: None,
1271            assertion_sites: Vec::new(),
1272        };
1273        let run = build_frontend_run(OwnedRunInputs {
1274            declaration: go_declaration(),
1275            environment: "go",
1276            manifest: &manifest,
1277            probes: &BTreeMap::new(),
1278            evidence: &OwnedEvidence::default(),
1279            outcomes: &[OwnedTestOutcome {
1280                name: "TestSilent".into(),
1281                runner: String::new(),
1282                package: "example.com/p".into(),
1283                file: Some("p/x_test.go".into()),
1284                status: "passed".into(),
1285                attributed: true,
1286            }],
1287            run_id: "run",
1288            generated_at: "now",
1289            test_exit_code: 0,
1290            coverage_model: go_coverage_model(),
1291        })
1292        .expect("run");
1293        assert_eq!(run.tests, 1);
1294        let result = &run.request.raw_results[0];
1295        assert_eq!(result.test, "TestSilent");
1296        assert_eq!(result.test_id.as_deref(), Some("example.com/p::TestSilent"));
1297        assert!(result.runtime[0].hits.is_empty());
1298    }
1299
1300    #[test]
1301    fn both_owned_declarations_satisfy_the_contract_they_are_read_back_through() {
1302        // A declaration is written at publication and checked at read. The two
1303        // owned frontends once hardcoded a protocol version while the contract
1304        // moved on, so every Go and JVM run published cleanly and then failed
1305        // the moment anyone asked for its report.
1306        for declaration in [go_declaration(), jvm_declaration()] {
1307            let language = declaration.language.clone();
1308            supercov_contracts::validate_frontend_run_declaration(&declaration)
1309                .unwrap_or_else(|error| panic!("{language} declaration is unreadable: {error}"));
1310        }
1311    }
1312    #[test]
1313    fn merging_processes_unions_the_run_and_keeps_every_test() {
1314        let left = OwnedEvidence {
1315            global: vec![0b01, 0b00],
1316            tests: vec![OwnedTestEvidence {
1317                name: "TestA".into(),
1318                status: "passed".into(),
1319                ..Default::default()
1320            }],
1321            widths: vec![2],
1322            decision_vectors: vec![vec![0b01]],
1323        };
1324        let right = OwnedEvidence {
1325            global: vec![0b10, 0b10],
1326            tests: vec![OwnedTestEvidence {
1327                name: "TestB".into(),
1328                status: "failed".into(),
1329                ..Default::default()
1330            }],
1331            widths: vec![2],
1332            decision_vectors: vec![vec![0b01, 0b11]],
1333        };
1334        let merged = merge_evidence(vec![left, right]);
1335        // Run-wide totals are a union: a probe any package reached is reached.
1336        assert_eq!(merged.global, [0b11, 0b10]);
1337        // Tests accumulate, because each belongs to exactly one binary.
1338        assert_eq!(
1339            merged
1340                .tests
1341                .iter()
1342                .map(|test| test.name.as_str())
1343                .collect::<Vec<_>>(),
1344            ["TestA", "TestB"]
1345        );
1346        // And a vector seen in both packages is one vector, not two.
1347        assert_eq!(merged.decision_vectors, [vec![0b01, 0b11]]);
1348    }
1349}