1use crate::{
2 output::{Line, OutputPatternType},
3 script::{IfCondition, ScriptLocation},
4};
5
6#[derive(Clone, Debug, thiserror::Error, derive_more::Display, PartialEq, Eq)]
7#[display("pattern {pattern_type} at line {location} {verb} output line {line:?}", verb = self.verb(), line = self.line())]
8pub struct OutputPatternMatchFailure {
9 pub location: ScriptLocation,
10 pub pattern_type: &'static str,
11 pub output_line: Option<Line>,
12}
13
14impl OutputPatternMatchFailure {
15 fn verb(&self) -> &'static str {
16 if self.pattern_type == "reject" {
17 "rejected"
18 } else {
19 "does not match"
20 }
21 }
22
23 fn line(&self) -> String {
24 self.output_line
25 .as_ref()
26 .map(|l| l.text.clone())
27 .unwrap_or("<eof>".to_string())
28 }
29}
30
31fn trace_shows_output_line(pattern: &OutputPatternType, success: bool) -> bool {
32 match (pattern, success) {
33 (OutputPatternType::Pattern(_), _)
34 | (OutputPatternType::Literal(_), false)
35 | (OutputPatternType::End, false) => true,
36 _ => false,
37 }
38}
39
40#[derive(Debug, Clone)]
41pub enum PatternTraceNote {
42 AliasMismatch(String, String),
43 IfConditionMet(IfCondition),
44 IfConditionSkipped(IfCondition),
45}
46
47#[derive(Debug, Clone)]
49pub struct OutputMatchTraceNode {
50 pub ignore: bool,
51 pub pattern: OutputPatternType,
52 pub succeeded: bool,
53 pub output_line: Option<Line>,
54 pub note: Option<PatternTraceNote>,
55 pub children: Vec<OutputMatchTraceNode>,
56}
57
58impl OutputMatchTraceNode {
59 fn fmt_lines(&self, depth: usize, out: &mut String) {
60 use std::fmt::Write;
61
62 let indent = depth * 2;
63 _ = write!(out, "{:indent$}", "", indent = indent);
64
65 if self.succeeded {
66 _ = write!(out, "[OK]");
67 } else {
68 _ = write!(out, "[XX]");
69 }
70
71 if self.ignore {
72 _ = write!(out, "-");
73 }
74
75 if self.succeeded {
76 _ = write!(out, " matched ");
77 } else {
78 _ = write!(out, " missed ");
79 }
80 _ = write!(out, "{}", self.pattern.trace_string());
81
82 let show_line = trace_shows_output_line(&self.pattern, self.succeeded);
83 if show_line {
84 if self.succeeded {
85 _ = write!(out, " = ");
86 } else {
87 _ = write!(out, " != ");
88 }
89
90 if let Some(line) = &self.output_line {
91 _ = write!(out, "{:?}", line.text);
92 } else {
93 _ = write!(out, "<eof>");
94 }
95 }
96
97 match &self.note {
98 Some(PatternTraceNote::AliasMismatch(a, b)) => {
99 _ = write!(out, " (alias conflict: {a:?} != {b:?})");
100 }
101 Some(PatternTraceNote::IfConditionMet(c)) => _ = write!(out, " (if {c:?})"),
102 Some(PatternTraceNote::IfConditionSkipped(c)) => _ = write!(out, " (if skipped {c:?})"),
103 None => (),
104 };
105
106 if self.ignore {
107 _ = write!(out, " (ignore)");
108 }
109 _ = writeln!(out);
110 for child in &self.children {
111 child.fmt_lines(depth + 1, out);
112 }
113 }
114}
115
116pub fn format_match_trace_tree(nodes: &[OutputMatchTraceNode]) -> String {
118 let mut out = String::new();
119 for node in nodes {
120 node.fmt_lines(0, &mut out);
121 }
122 out
123}