Skip to main content

clitest_lib/
failure.rs

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