Skip to main content

clitest_lib/
failure.rs

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/// One pattern invocation in the output-match trace: a single tree node with nested child patterns.
73#[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
141/// Pre-order walk with two spaces of indentation per tree level.
142pub 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}