compiletest/runtest/
debugger.rs

1use std::fmt::Write;
2use std::fs::File;
3use std::io::{BufRead, BufReader};
4
5use camino::{Utf8Path, Utf8PathBuf};
6
7use crate::common::Config;
8use crate::runtest::ProcRes;
9
10/// Representation of information to invoke a debugger and check its output
11pub(super) struct DebuggerCommands {
12    /// Commands for the debuuger
13    pub commands: Vec<String>,
14    /// Lines to insert breakpoints at
15    pub breakpoint_lines: Vec<usize>,
16    /// Contains the source line number to check and the line itself
17    check_lines: Vec<(usize, String)>,
18    /// Source file name
19    file: Utf8PathBuf,
20}
21
22impl DebuggerCommands {
23    pub fn parse_from(
24        file: &Utf8Path,
25        config: &Config,
26        debugger_prefix: &str,
27    ) -> Result<Self, String> {
28        let command_directive = format!("{debugger_prefix}-command");
29        let check_directive = format!("{debugger_prefix}-check");
30
31        let mut breakpoint_lines = vec![];
32        let mut commands = vec![];
33        let mut check_lines = vec![];
34        let mut counter = 0;
35        let reader = BufReader::new(File::open(file.as_std_path()).unwrap());
36        for (line_no, line) in reader.lines().enumerate() {
37            counter += 1;
38            let line = line.map_err(|e| format!("Error while parsing debugger commands: {}", e))?;
39
40            // Breakpoints appear on lines with actual code, typically at the end of the line.
41            if line.contains("#break") {
42                breakpoint_lines.push(counter);
43                continue;
44            }
45
46            let Some(line) = line.trim_start().strip_prefix("//").map(str::trim_start) else {
47                continue;
48            };
49
50            if let Some(command) = config.parse_name_value_directive(&line, &command_directive) {
51                commands.push(command);
52            }
53            if let Some(pattern) = config.parse_name_value_directive(&line, &check_directive) {
54                check_lines.push((line_no, pattern));
55            }
56        }
57
58        Ok(Self { commands, breakpoint_lines, check_lines, file: file.to_path_buf() })
59    }
60
61    /// Given debugger output and lines to check, ensure that every line is
62    /// contained in the debugger output. The check lines need to be found in
63    /// order, but there can be extra lines between.
64    pub fn check_output(&self, debugger_run_result: &ProcRes) -> Result<(), String> {
65        // (src_lineno, ck_line)  that we did find
66        let mut found = vec![];
67        // (src_lineno, ck_line) that we couldn't find
68        let mut missing = vec![];
69        //  We can find our any current match anywhere after our last match
70        let mut last_idx = 0;
71        let dbg_lines: Vec<&str> = debugger_run_result.stdout.lines().collect();
72
73        for (src_lineno, ck_line) in &self.check_lines {
74            if let Some(offset) = dbg_lines
75                .iter()
76                .skip(last_idx)
77                .position(|out_line| check_single_line(out_line, &ck_line))
78            {
79                last_idx += offset;
80                found.push((src_lineno, dbg_lines[last_idx]));
81            } else {
82                missing.push((src_lineno, ck_line));
83            }
84        }
85
86        if missing.is_empty() {
87            Ok(())
88        } else {
89            let fname = self.file.file_name().unwrap();
90            let mut msg = format!(
91                "check directive(s) from `{}` not found in debugger output. errors:",
92                self.file
93            );
94
95            for (src_lineno, err_line) in missing {
96                write!(msg, "\n    ({fname}:{num}) `{err_line}`", num = src_lineno + 1).unwrap();
97            }
98
99            if !found.is_empty() {
100                let init = "\nthe following subset of check directive(s) was found successfully:";
101                msg.push_str(init);
102                for (src_lineno, found_line) in found {
103                    write!(msg, "\n    ({fname}:{num}) `{found_line}`", num = src_lineno + 1)
104                        .unwrap();
105                }
106            }
107
108            Err(msg)
109        }
110    }
111}
112
113/// Check that the pattern in `check_line` applies to `line`. Returns `true` if they do match.
114fn check_single_line(line: &str, check_line: &str) -> bool {
115    // Allow check lines to leave parts unspecified (e.g., uninitialized
116    // bits in the  wrong case of an enum) with the notation "[...]".
117    let line = line.trim();
118    let check_line = check_line.trim();
119    let can_start_anywhere = check_line.starts_with("[...]");
120    let can_end_anywhere = check_line.ends_with("[...]");
121
122    let check_fragments: Vec<&str> =
123        check_line.split("[...]").filter(|frag| !frag.is_empty()).collect();
124    if check_fragments.is_empty() {
125        return true;
126    }
127
128    let (mut rest, first_fragment) = if can_start_anywhere {
129        let Some(pos) = line.find(check_fragments[0]) else {
130            return false;
131        };
132        (&line[pos + check_fragments[0].len()..], 1)
133    } else {
134        (line, 0)
135    };
136
137    for current_fragment in &check_fragments[first_fragment..] {
138        let Some(pos) = rest.find(current_fragment) else {
139            return false;
140        };
141        rest = &rest[pos + current_fragment.len()..];
142    }
143
144    can_end_anywhere || rest.is_empty()
145}