harn_cli/test_runner/
reporting.rs1use serde::Serialize;
4
5use crate::test_timing::DurationSummary;
6
7#[derive(Clone, Debug, Serialize)]
8pub struct TestResult {
9 pub name: String,
10 pub file: String,
11 pub passed: bool,
12 pub error: Option<String>,
13 #[serde(skip_serializing_if = "Option::is_none")]
18 pub captured_output: Option<String>,
19 pub timeout: Option<TestTimeout>,
22 pub duration_ms: u64,
23 #[serde(skip_serializing_if = "Option::is_none")]
26 pub phases: Option<PhaseTimings>,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
30pub struct TestTimeout {
31 pub phase: TestPhase,
32 pub limit_ms: u64,
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
36#[serde(rename_all = "snake_case")]
37pub enum TestPhase {
38 Execute,
39}
40
41#[derive(Clone, Debug, Serialize)]
42pub struct TestSummary {
43 pub results: Vec<TestResult>,
44 pub passed: usize,
45 pub failed: usize,
46 pub total: usize,
47 pub duration_ms: u64,
48 pub timing: DurationSummary,
50 pub aggregate: AggregateTimings,
52}
53
54#[derive(Debug, Default, Clone, Copy, Serialize)]
60pub struct PhaseTimings {
61 pub setup_ms: u64,
64 pub compile_ms: u64,
66 pub execute_ms: u64,
68 pub teardown_ms: u64,
70 pub modules: harn_vm::ModulePhaseStats,
73}
74
75#[derive(Debug, Default, Clone, Copy, Serialize)]
79pub struct AggregateTimings {
80 pub collection_ms: u64,
81 pub setup_ms: u64,
82 pub compile_ms: u64,
83 pub execute_ms: u64,
84 pub teardown_ms: u64,
85 pub modules: harn_vm::ModulePhaseStats,
87}
88
89impl AggregateTimings {
90 pub(super) fn from_results(collection_ms: u64, results: &[TestResult]) -> Self {
91 results.iter().filter_map(|result| result.phases).fold(
92 Self {
93 collection_ms,
94 ..Self::default()
95 },
96 |acc, phases| Self {
97 collection_ms: acc.collection_ms,
98 setup_ms: acc.setup_ms.saturating_add(phases.setup_ms),
99 compile_ms: acc.compile_ms.saturating_add(phases.compile_ms),
100 execute_ms: acc.execute_ms.saturating_add(phases.execute_ms),
101 teardown_ms: acc.teardown_ms.saturating_add(phases.teardown_ms),
102 modules: acc.modules.saturating_add(phases.modules),
103 },
104 )
105 }
106}
107
108impl TestResult {
109 pub(super) fn emit_diagnose(&self) {
113 let outcome = if self.passed { "ok" } else { "FAIL" };
114 let phases = self
115 .phases
116 .expect("diagnostics are emitted only for executed cases");
117 eprintln!(
118 "[harn test diag] {} {} setup={}ms compile={}ms execute={}ms teardown={}ms module_compile={}ms module_load={}ms modules_compiled={} modules_loaded={} total={}ms",
119 outcome,
120 self.name,
121 phases.setup_ms,
122 phases.compile_ms,
123 phases.execute_ms,
124 phases.teardown_ms,
125 phases.modules.module_compile_ms,
126 phases.modules.module_load_ms,
127 phases.modules.modules_compiled,
128 phases.modules.modules_loaded,
129 self.duration_ms,
130 );
131 }
132}