1use std::collections::HashMap;
2use std::time::Duration;
3
4use serde::Deserialize;
5use serde::Serialize;
6use ts_rs::TS;
7
8use super::SourceLocation;
9use super::span::SpanId;
10
11pub type EventSeq = u64;
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
17#[cfg_attr(
18 feature = "ts-export",
19 ts(export, export_to = "../../../viewer/src/types/")
20)]
21#[serde(tag = "type", rename_all = "kebab-case")]
22pub enum TimeoutValue {
23 Tolerance {
24 duration: String,
25 multiplier: String,
26 total_duration: String,
27 source: Option<SourceLocation>,
28 },
29 Assertion {
30 duration: String,
31 source: Option<SourceLocation>,
32 },
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, TS)]
41#[cfg_attr(
42 feature = "ts-export",
43 ts(export, export_to = "../../../viewer/src/types/")
44)]
45pub struct MultiMatchPattern {
46 pub pattern: String,
47 pub is_regex: bool,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, TS)]
51#[cfg_attr(
52 feature = "ts-export",
53 ts(export, export_to = "../../../viewer/src/types/")
54)]
55pub struct Event {
56 pub seq: EventSeq,
57 #[serde(with = "super::ts_duration_ms")]
58 #[ts(as = "f64")]
59 pub ts: Duration,
60 pub span: SpanId,
61 pub shell: Option<String>,
62 pub shell_marker: Option<String>,
66 pub source: Option<SourceLocation>,
69 #[serde(flatten)]
70 pub kind: EventKind,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, TS)]
74#[cfg_attr(
75 feature = "ts-export",
76 ts(export, export_to = "../../../viewer/src/types/")
77)]
78#[serde(tag = "kind", rename_all = "kebab-case")]
79pub enum EventKind {
80 ShellSpawn {
82 name: String,
83 command: String,
84 },
85 ShellReady {
86 name: String,
87 },
88 ShellSwitch {
89 name: String,
90 },
91 ShellTerminate {
92 name: String,
93 },
94
95 EffectExposeShell {
99 name: String,
102 target: String,
105 qualifier: Option<String>,
108 },
109 EffectExposeVar {
110 name: String,
111 target: String,
112 qualifier: Option<String>,
113 value: String,
115 },
116
117 Send {
119 data: String,
120 },
121 Recv {
122 data: String,
123 },
124
125 MatchStart {
127 pattern: String,
128 is_regex: bool,
129 effective: TimeoutValue,
131 },
132 MatchDone {
133 matched: String,
134 #[serde(with = "super::ts_duration_ms")]
135 #[ts(as = "f64")]
136 elapsed: Duration,
137 captures: Option<HashMap<String, String>>,
138 buffer_seq: EventSeq,
139 },
140 Timeout {
141 pattern: String,
142 buffer_seq: Option<EventSeq>,
145 effective: TimeoutValue,
147 },
148
149 FailPatternSet {
151 pattern: String,
152 is_regex: bool,
153 },
154 FailPatternCleared,
155 FailPatternTriggered {
156 pattern: String,
157 is_regex: bool,
158 matched_line: String,
159 buffer_seq: Option<EventSeq>,
162 },
163
164 SleepStart {
166 #[serde(with = "super::ts_duration_ms")]
167 #[ts(as = "f64")]
168 duration: Duration,
169 },
170 SleepDone,
171 TimeoutSet {
172 timeout: TimeoutValue,
173 previous: TimeoutValue,
174 },
175
176 VarLet {
178 name: String,
179 value: String,
180 },
181 VarAssign {
182 name: String,
183 value: String,
184 previous: String,
185 },
186 StringEval {
187 result: String,
188 },
189 Interpolation {
190 template: String,
191 result: String,
192 bindings: Vec<(String, String)>,
193 },
194 PureMatchStart {
197 value: String,
198 pattern: String,
199 is_regex: bool,
200 },
201 PureMatchDone {
204 matched: String,
205 captures: HashMap<String, String>,
206 },
207 PureMatchFailed {},
210 VarRead {
215 name: String,
216 value: String,
217 },
218 BoolCheck {
223 evaluation: super::span::MarkerEvalDetail,
224 },
225
226 Annotate {
228 text: String,
229 },
230 Log {
231 message: String,
232 },
233 Warning {
234 message: String,
235 },
236 Error {
237 message: String,
238 },
239
240 Cancelled {
244 reason: CancelReasonRecord,
245 },
246
247 MultiMatchStart {
257 effective: TimeoutValue,
259 patterns: Vec<MultiMatchPattern>,
263 },
264 MultiMatchPatternDone {
265 index: usize,
267 #[serde(with = "super::ts_duration_ms")]
269 #[ts(as = "f64")]
270 elapsed: Duration,
271 buffer_seq: EventSeq,
275 },
276 MultiMatchDone {
277 advance_to: EventSeq,
281 },
282 MultiMatchTimeout {
283 unmatched: Vec<usize>,
286 },
287}
288
289#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
290#[cfg_attr(
291 feature = "ts-export",
292 ts(export, export_to = "../../../viewer/src/types/")
293)]
294#[serde(tag = "type", rename_all = "kebab-case")]
295pub enum CancelReasonRecord {
296 TestTimeout { duration_ms: u64 },
297 SuiteTimeout { duration_ms: u64 },
298 FailFast { trigger_test: String },
299 Sigint,
300}
301
302impl CancelReasonRecord {
303 pub fn tag(&self) -> &'static str {
304 match self {
305 Self::TestTimeout { .. } => "test-timeout",
306 Self::SuiteTimeout { .. } => "suite-timeout",
307 Self::FailFast { .. } => "fail-fast",
308 Self::Sigint => "sigint",
309 }
310 }
311}
312
313impl From<&crate::cancel::CancelReason> for CancelReasonRecord {
314 fn from(r: &crate::cancel::CancelReason) -> Self {
315 use crate::cancel::CancelReason;
316 match r {
317 CancelReason::TestTimeout { duration } => Self::TestTimeout {
318 duration_ms: duration.as_millis() as u64,
319 },
320 CancelReason::SuiteTimeout { duration } => Self::SuiteTimeout {
321 duration_ms: duration.as_millis() as u64,
322 },
323 CancelReason::FailFast { trigger_test } => Self::FailFast {
324 trigger_test: trigger_test.clone(),
325 },
326 CancelReason::Sigint => Self::Sigint,
327 }
328 }
329}
330
331#[cfg(test)]
332mod pure_match_tests {
333 use super::*;
334 use std::collections::HashMap;
335
336 #[test]
337 fn pure_match_trio_serialises() {
338 let mut caps = HashMap::new();
339 caps.insert("0".to_string(), "abc".to_string());
340 let start = EventKind::PureMatchStart {
341 value: "abc".into(),
342 pattern: "^a.c$".into(),
343 is_regex: true,
344 };
345 let done = EventKind::PureMatchDone {
346 matched: "abc".into(),
347 captures: caps,
348 };
349 let failed = EventKind::PureMatchFailed {};
350 assert_eq!(
351 serde_json::to_value(&start).unwrap()["kind"],
352 serde_json::json!("pure-match-start")
353 );
354 assert_eq!(
355 serde_json::to_value(&start).unwrap()["is_regex"],
356 serde_json::json!(true)
357 );
358 assert_eq!(
359 serde_json::to_value(&done).unwrap()["kind"],
360 serde_json::json!("pure-match-done")
361 );
362 assert_eq!(
363 serde_json::to_value(&failed).unwrap()["kind"],
364 serde_json::json!("pure-match-failed")
365 );
366 }
367
368 #[test]
369 fn multimatch_start_event_kind_serialises() {
370 let k = EventKind::MultiMatchStart {
371 effective: TimeoutValue::Assertion {
372 duration: "5s".into(),
373 source: None,
374 },
375 patterns: vec![
376 MultiMatchPattern {
377 pattern: "^ok$".into(),
378 is_regex: true,
379 },
380 MultiMatchPattern {
381 pattern: "batch complete".into(),
382 is_regex: false,
383 },
384 ],
385 };
386 let v = serde_json::to_value(&k).unwrap();
387 assert_eq!(v["kind"], serde_json::json!("multi-match-start"));
388 assert_eq!(v["patterns"][0]["is_regex"], serde_json::json!(true));
389 assert_eq!(
390 v["patterns"][1]["pattern"],
391 serde_json::json!("batch complete")
392 );
393 }
394
395 #[test]
396 fn multimatch_pattern_done_event_kind_serialises() {
397 let k = EventKind::MultiMatchPatternDone {
398 index: 1,
399 elapsed: Duration::from_millis(42),
400 buffer_seq: 17,
401 };
402 let v = serde_json::to_value(&k).unwrap();
403 assert_eq!(v["kind"], serde_json::json!("multi-match-pattern-done"));
404 assert_eq!(v["index"], serde_json::json!(1));
405 assert_eq!(v["buffer_seq"], serde_json::json!(17));
406 }
407
408 #[test]
409 fn multimatch_done_event_kind_serialises() {
410 let k = EventKind::MultiMatchDone { advance_to: 23 };
411 let v = serde_json::to_value(&k).unwrap();
412 assert_eq!(v["kind"], serde_json::json!("multi-match-done"));
413 assert_eq!(v["advance_to"], serde_json::json!(23));
414 }
415
416 #[test]
417 fn multimatch_timeout_event_kind_serialises() {
418 let k = EventKind::MultiMatchTimeout {
419 unmatched: vec![0, 2],
420 };
421 let v = serde_json::to_value(&k).unwrap();
422 assert_eq!(v["kind"], serde_json::json!("multi-match-timeout"));
423 assert_eq!(v["unmatched"], serde_json::json!([0, 2]));
424 }
425}