1use super::buffer::TraceBuffer;
6use super::canonicalize::{canonicalize, trace_event_key, trace_fingerprint, TraceEventKey};
7use super::event::TraceEvent;
8use serde::{Deserialize, Serialize};
9use std::io::{self, Write};
10
11pub const GOLDEN_TRACE_SCHEMA_VERSION: u32 = 1;
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct GoldenTraceConfig {
17 pub seed: u64,
19 pub entropy_seed: u64,
21 pub worker_count: usize,
23 pub trace_capacity: usize,
25 pub max_steps: Option<u64>,
27 pub canonical_prefix_layers: usize,
29 pub canonical_prefix_events: usize,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub struct GoldenTraceOracleSummary {
36 pub violations: Vec<String>,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub struct GoldenTraceFixture {
43 pub schema_version: u32,
45 pub config: GoldenTraceConfig,
47 pub fingerprint: u64,
49 pub event_count: u64,
51 pub canonical_prefix: Vec<Vec<TraceEventKey>>,
53 pub oracle_summary: GoldenTraceOracleSummary,
55}
56
57impl GoldenTraceFixture {
58 #[must_use]
60 pub fn from_events(
61 config: GoldenTraceConfig,
62 events: &[TraceEvent],
63 oracle_violations: impl IntoIterator<Item = impl Into<String>>,
64 ) -> Self {
65 let canonical_prefix = canonical_prefix(
66 events,
67 config.canonical_prefix_layers,
68 config.canonical_prefix_events,
69 );
70 let mut violations: Vec<String> = oracle_violations.into_iter().map(Into::into).collect();
71 violations.sort();
72 violations.dedup();
73
74 Self {
75 schema_version: GOLDEN_TRACE_SCHEMA_VERSION,
76 fingerprint: trace_fingerprint(events),
77 event_count: u64::try_from(events.len()).unwrap_or(u64::MAX),
78 canonical_prefix,
79 oracle_summary: GoldenTraceOracleSummary { violations },
80 config,
81 }
82 }
83
84 pub fn verify(&self, actual: &Self) -> Result<(), GoldenTraceDiff> {
86 GoldenTraceDiff::from_fixtures(self, actual).into_result()
87 }
88}
89
90#[derive(Debug, Default)]
92pub struct GoldenTraceDiff {
93 mismatches: Vec<GoldenTraceMismatch>,
94}
95
96impl GoldenTraceDiff {
97 #[must_use]
99 pub fn is_empty(&self) -> bool {
100 self.mismatches.is_empty()
101 }
102
103 fn push(&mut self, mismatch: GoldenTraceMismatch) {
104 self.mismatches.push(mismatch);
105 }
106
107 fn from_fixtures(expected: &GoldenTraceFixture, actual: &GoldenTraceFixture) -> Self {
108 let mut diff = Self::default();
109 if expected.schema_version != actual.schema_version {
110 diff.push(GoldenTraceMismatch::SchemaVersion {
111 expected: expected.schema_version,
112 actual: actual.schema_version,
113 });
114 }
115 if expected.config != actual.config {
116 diff.push(GoldenTraceMismatch::Config {
117 expected: expected.config.clone(),
118 actual: actual.config.clone(),
119 });
120 }
121 if expected.fingerprint != actual.fingerprint {
122 diff.push(GoldenTraceMismatch::Fingerprint {
123 expected: expected.fingerprint,
124 actual: actual.fingerprint,
125 });
126 }
127 if expected.event_count != actual.event_count {
128 diff.push(GoldenTraceMismatch::EventCount {
129 expected: expected.event_count,
130 actual: actual.event_count,
131 });
132 }
133 if expected.canonical_prefix != actual.canonical_prefix {
134 diff.push(GoldenTraceMismatch::CanonicalPrefix {
135 expected_layers: expected.canonical_prefix.len(),
136 actual_layers: actual.canonical_prefix.len(),
137 first_mismatch: first_prefix_mismatch(
138 &expected.canonical_prefix,
139 &actual.canonical_prefix,
140 ),
141 });
142 }
143 if expected.oracle_summary != actual.oracle_summary {
144 diff.push(GoldenTraceMismatch::OracleViolations {
145 expected: expected.oracle_summary.violations.clone(),
146 actual: actual.oracle_summary.violations.clone(),
147 });
148 }
149 diff
150 }
151
152 fn into_result(self) -> Result<(), Self> {
153 if self.is_empty() {
154 Ok(())
155 } else {
156 Err(self)
157 }
158 }
159}
160
161impl std::fmt::Display for GoldenTraceDiff {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 for mismatch in &self.mismatches {
164 writeln!(f, "{mismatch}")?;
165 }
166 Ok(())
167 }
168}
169
170impl std::error::Error for GoldenTraceDiff {}
171
172#[derive(Debug)]
173enum GoldenTraceMismatch {
174 SchemaVersion {
175 expected: u32,
176 actual: u32,
177 },
178 Config {
179 expected: GoldenTraceConfig,
180 actual: GoldenTraceConfig,
181 },
182 Fingerprint {
183 expected: u64,
184 actual: u64,
185 },
186 EventCount {
187 expected: u64,
188 actual: u64,
189 },
190 CanonicalPrefix {
191 expected_layers: usize,
192 actual_layers: usize,
193 first_mismatch: Option<(usize, usize)>,
194 },
195 OracleViolations {
196 expected: Vec<String>,
197 actual: Vec<String>,
198 },
199}
200
201impl std::fmt::Display for GoldenTraceMismatch {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 match self {
204 Self::SchemaVersion { expected, actual } => {
205 write!(
206 f,
207 "schema_version changed (expected {expected}, actual {actual})"
208 )
209 }
210 Self::Config { expected, actual } => {
211 write!(
212 f,
213 "config changed (expected {expected:?}, actual {actual:?})"
214 )
215 }
216 Self::Fingerprint { expected, actual } => {
217 write!(
218 f,
219 "fingerprint changed (expected 0x{expected:016X}, actual 0x{actual:016X})"
220 )
221 }
222 Self::EventCount { expected, actual } => write!(
223 f,
224 "event_count changed (expected {expected}, actual {actual})"
225 ),
226 Self::CanonicalPrefix {
227 expected_layers,
228 actual_layers,
229 first_mismatch,
230 } => {
231 if let Some((layer, index)) = first_mismatch {
232 write!(
233 f,
234 "canonical_prefix mismatch (layer {layer}, index {index}; expected_layers={expected_layers}, actual_layers={actual_layers})"
235 )
236 } else {
237 write!(
238 f,
239 "canonical_prefix mismatch (expected_layers={expected_layers}, actual_layers={actual_layers})"
240 )
241 }
242 }
243 Self::OracleViolations { expected, actual } => {
244 write!(
245 f,
246 "oracle violations changed (expected {expected:?}, actual {actual:?})"
247 )
248 }
249 }
250 }
251}
252
253fn canonical_prefix(
254 events: &[TraceEvent],
255 max_layers: usize,
256 max_events: usize,
257) -> Vec<Vec<TraceEventKey>> {
258 let foata = canonicalize(events);
259 let mut remaining = max_events;
260 let mut prefix = Vec::new();
261
262 for layer in foata.layers().iter().take(max_layers) {
263 if remaining == 0 {
264 break;
265 }
266 let mut keys = Vec::new();
267 for event in layer {
268 if remaining == 0 {
269 break;
270 }
271 keys.push(trace_event_key(event));
272 remaining = remaining.saturating_sub(1);
273 }
274 if !keys.is_empty() {
275 prefix.push(keys);
276 }
277 }
278
279 prefix
280}
281
282fn first_prefix_mismatch(
283 expected: &[Vec<TraceEventKey>],
284 actual: &[Vec<TraceEventKey>],
285) -> Option<(usize, usize)> {
286 let layers = expected.len().min(actual.len());
287 for layer_idx in 0..layers {
288 let expected_layer = &expected[layer_idx];
289 let actual_layer = &actual[layer_idx];
290 let events = expected_layer.len().min(actual_layer.len());
291 for event_idx in 0..events {
292 if expected_layer[event_idx] != actual_layer[event_idx] {
293 return Some((layer_idx, event_idx));
294 }
295 }
296 if expected_layer.len() != actual_layer.len() {
297 return Some((layer_idx, events));
298 }
299 }
300 if expected.len() != actual.len() {
301 return Some((layers, 0));
302 }
303 None
304}
305
306pub fn format_trace(buffer: &TraceBuffer, w: &mut impl Write) -> io::Result<()> {
308 writeln!(w, "=== Trace ({} events) ===", buffer.len())?;
309 for event in buffer.iter() {
310 writeln!(w, "{event}")?;
311 }
312 writeln!(w, "=== End Trace ===")?;
313 Ok(())
314}
315
316#[must_use]
318pub fn trace_to_string(buffer: &TraceBuffer) -> String {
319 let mut s = Vec::new();
320 format_trace(buffer, &mut s).expect("writing to Vec should not fail");
321 String::from_utf8(s).expect("trace should be valid UTF-8")
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use crate::trace::event::{TraceData, TraceEvent, TraceEventKind};
328 use crate::types::Time;
329
330 #[test]
331 fn format_empty_trace() {
332 let buffer = TraceBuffer::new(10);
333 let output = trace_to_string(&buffer);
334 assert!(output.contains("0 events"));
335 }
336
337 #[test]
338 fn format_with_events() {
339 let mut buffer = TraceBuffer::new(10);
340 buffer.push(TraceEvent::new(
341 1,
342 Time::from_millis(100),
343 TraceEventKind::UserTrace,
344 TraceData::Message("test".to_string()),
345 ));
346 let output = trace_to_string(&buffer);
347 assert!(output.contains("1 events"));
348 assert!(output.contains("test"));
349 }
350}