1use serde::Serialize;
4
5use crate::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 test_file_compile_ms: u64,
87 pub test_files_compiled: usize,
88 pub test_entries_compiled: usize,
89 pub execute_ms: u64,
90 pub teardown_ms: u64,
91 pub modules: harn_vm::ModulePhaseStats,
94}
95
96#[derive(Clone, Copy, Debug, Default)]
97#[doc(hidden)]
98pub struct SuiteModulePreparation {
99 pub duration_ms: u64,
100 pub modules: harn_vm::ModulePhaseStats,
101}
102
103#[derive(Clone, Copy, Debug, Default)]
104#[doc(hidden)]
105pub struct SuiteCallablePreparation {
106 pub duration_ms: u64,
107 pub files: usize,
108 pub entries: usize,
109}
110
111impl AggregateTimings {
112 #[doc(hidden)]
113 pub fn from_results(
114 collection_ms: u64,
115 module_preparation: SuiteModulePreparation,
116 callable_preparation: SuiteCallablePreparation,
117 results: &[TestResult],
118 ) -> Self {
119 results.iter().filter_map(|result| result.phases).fold(
120 Self {
121 collection_ms,
122 compile_ms: module_preparation
123 .duration_ms
124 .saturating_add(callable_preparation.duration_ms),
125 test_file_compile_ms: callable_preparation.duration_ms,
126 test_files_compiled: callable_preparation.files,
127 test_entries_compiled: callable_preparation.entries,
128 modules: module_preparation.modules,
129 ..Self::default()
130 },
131 |acc, phases| Self {
132 collection_ms: acc.collection_ms,
133 setup_ms: acc.setup_ms.saturating_add(phases.setup_ms),
134 compile_ms: acc.compile_ms.saturating_add(phases.compile_ms),
135 test_file_compile_ms: acc.test_file_compile_ms,
136 test_files_compiled: acc.test_files_compiled,
137 test_entries_compiled: acc.test_entries_compiled,
138 execute_ms: acc.execute_ms.saturating_add(phases.execute_ms),
139 teardown_ms: acc.teardown_ms.saturating_add(phases.teardown_ms),
140 modules: acc.modules.saturating_add(phases.modules),
141 },
142 )
143 }
144}
145
146impl TestResult {
147 #[doc(hidden)]
151 pub fn emit_diagnose(&self) {
152 let outcome = if self.passed { "ok" } else { "FAIL" };
153 let phases = self
154 .phases
155 .expect("diagnostics are emitted only for executed cases");
156 eprintln!(
157 "[harn test diag] {} {} setup={}ms compile={}ms execute={}ms teardown={}ms module_compile={}ms module_load={}ms modules_compiled={} modules_loaded={} total={}ms",
158 outcome,
159 self.name,
160 phases.setup_ms,
161 phases.compile_ms,
162 phases.execute_ms,
163 phases.teardown_ms,
164 phases.modules.module_compile_ms,
165 phases.modules.module_load_ms,
166 phases.modules.modules_compiled,
167 phases.modules.modules_loaded,
168 self.duration_ms,
169 );
170 }
171}