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