1use 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
30fn 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#[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 pub status: String,
72 pub runner: String,
75 pub probes: BTreeMap<u32, u32>,
77 pub vectors: Vec<PackedVector>,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Default)]
81pub struct OwnedEvidence {
82 pub global: Vec<u32>,
84 pub tests: Vec<OwnedTestEvidence>,
85 pub widths: Vec<u8>,
87 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
118pub 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
183pub 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#[derive(Debug, Clone, PartialEq, Eq)]
205pub struct OwnedTestOutcome {
206 pub name: String,
207 pub package: String,
208 pub file: Option<String>,
209 pub status: String,
211 pub runner: String,
214}
215
216pub 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 structural_source: supercov_contracts::StructuralSource::OwnedProbes,
230 runners: vec![FrontendRunnerDeclaration {
231 runner: "go-test".into(),
232 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
252pub 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
274pub 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
313pub 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
335fn 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 action: AttributionPrecision::Unavailable,
377 assertion: AttributionPrecision::Unavailable,
378 }
379}
380
381pub 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
419fn 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
439fn 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 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
562pub struct OwnedRunInputs<'a> {
570 pub declaration: FrontendRunDeclaration,
571 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 pub coverage_model: CoverageModelDeclaration,
584}
585
586pub 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 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 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 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 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 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 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 let observed = raw_results
796 .iter()
797 .map(|result| result.provenance.runner.as_str())
798 .collect::<BTreeSet<_>>();
799 let mut declaration = declaration;
800
801 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 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 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 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 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 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 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 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 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 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 assert_eq!(merged.global, [0b11, 0b10]);
1044 assert_eq!(
1046 merged
1047 .tests
1048 .iter()
1049 .map(|test| test.name.as_str())
1050 .collect::<Vec<_>>(),
1051 ["TestA", "TestB"]
1052 );
1053 assert_eq!(merged.decision_vectors, [vec![0b01, 0b11]]);
1055 }
1056}