debugger_test_parser/
lib.rs

1use regex::Regex;
2
3enum OutputParsingStyle {
4    LiteralMatch(String),
5    PatternMatch(Regex),
6}
7
8const PATTERN_PREFIX: &str = "pattern:";
9
10/// Parse the output of a debugger and verify that the expected contents
11/// are found. If content was expected in the debugger output that is not
12/// found, stop and return an error.
13pub fn parse(debugger_output: String, expected_contents: Vec<&str>) -> anyhow::Result<()> {
14    // If there are no check statements, return early.
15    if expected_contents.len() == 0 {
16        log::info!("No expected contents found.");
17        return anyhow::Ok(());
18    }
19
20    // Trim whitespace at the beginning and end of output lines.
21    let debugger_output_lines = debugger_output
22        .trim()
23        .lines()
24        .map(|line| line.trim())
25        .collect::<Vec<&str>>();
26
27    // Trim whitespace at the beginning and end of expected contents.
28    let expected_contents = expected_contents
29        .iter()
30        .filter_map(|line| {
31            let str = line.trim();
32            match str.is_empty() {
33                false => Some(str),
34                true => None,
35            }
36        })
37        .map(|line| line.trim())
38        .collect::<Vec<&str>>();
39
40    let mut index = 0;
41
42    for expected in expected_contents {
43        let parsing_style = get_output_parsing_style(expected)?;
44        loop {
45            if index >= debugger_output_lines.len() {
46                let error_msg = format_error_message(&parsing_style);
47                anyhow::bail!(
48                    "Unable to find expected content in the debugger output. {}",
49                    error_msg
50                );
51            }
52
53            let debugger_output_line = debugger_output_lines[index];
54            index += 1;
55
56            // Search for the expected line or pattern within the current debugger output line.
57            match &parsing_style {
58                OutputParsingStyle::LiteralMatch(literal_str) => {
59                    let str = literal_str.as_str();
60                    if debugger_output_line.contains(&str) {
61                        log::info!(
62                            "Expected content found: `{}` at line `{}`",
63                            str,
64                            debugger_output_line
65                        );
66                        break;
67                    }
68                }
69                OutputParsingStyle::PatternMatch(re) => {
70                    if re.is_match(&debugger_output_line) {
71                        log::info!("Expected pattern found: `{}`", debugger_output_line);
72                        break;
73                    }
74                }
75            }
76        }
77    }
78
79    anyhow::Ok(())
80}
81
82fn format_error_message(parsing_style: &OutputParsingStyle) -> String {
83    match parsing_style {
84        OutputParsingStyle::LiteralMatch(literal_string) => {
85            format!("Missing line: `{}`", literal_string)
86        }
87        OutputParsingStyle::PatternMatch(pattern) => {
88            format!("Found 0 matches for pattern: `{}`", pattern.to_string())
89        }
90    }
91}
92
93/// Get the parsing style for the given expected statement.
94fn get_output_parsing_style(expected_output: &str) -> anyhow::Result<OutputParsingStyle> {
95    let parsing_style = if expected_output.starts_with(PATTERN_PREFIX) {
96        let re_pattern = expected_output
97            .strip_prefix(PATTERN_PREFIX)
98            .expect("string starts with `pattern:`");
99        let re = match Regex::new(re_pattern) {
100            Ok(re) => re,
101            Err(error) => anyhow::bail!("Invalid regex pattern: {}\n{}", re_pattern, error),
102        };
103
104        OutputParsingStyle::PatternMatch(re)
105    } else {
106        OutputParsingStyle::LiteralMatch(String::from(expected_output))
107    };
108
109    Ok(parsing_style)
110}