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