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) =
51                config.parse_name_value_directive(&line, &command_directive, file, line_no)
52            {
53                commands.push(command);
54            }
55            if let Some(pattern) =
56                config.parse_name_value_directive(&line, &check_directive, file, line_no)
57            {
58                check_lines.push((line_no, pattern));
59            }
60        }
61
62        Ok(Self { commands, breakpoint_lines, check_lines, file: file.to_path_buf() })
63    }
64
65    /// Given debugger output and lines to check, ensure that every line is
66    /// contained in the debugger output. The check lines need to be found in
67    /// order, but there can be extra lines between.
68    pub fn check_output(&self, debugger_run_result: &ProcRes) -> Result<(), String> {
69        // (src_lineno, ck_line)  that we did find
70        let mut found = vec![];
71        // (src_lineno, ck_line) that we couldn't find
72        let mut missing = vec![];
73        //  We can find our any current match anywhere after our last match
74        let mut last_idx = 0;
75        let dbg_lines: Vec<&str> = debugger_run_result.stdout.lines().collect();
76
77        for (src_lineno, ck_line) in &self.check_lines {
78            if let Some(offset) = dbg_lines
79                .iter()
80                .skip(last_idx)
81                .position(|out_line| check_single_line(out_line, &ck_line))
82            {
83                last_idx += offset;
84                found.push((src_lineno, dbg_lines[last_idx]));
85            } else {
86                missing.push((src_lineno, ck_line));
87            }
88        }
89
90        if missing.is_empty() {
91            Ok(())
92        } else {
93            let fname = self.file.file_name().unwrap();
94            let mut msg = format!(
95                "check directive(s) from `{}` not found in debugger output. errors:",
96                self.file
97            );
98
99            for (src_lineno, err_line) in missing {
100                write!(msg, "\n    ({fname}:{num}) `{err_line}`", num = src_lineno + 1).unwrap();
101            }
102
103            if !found.is_empty() {
104                let init = "\nthe following subset of check directive(s) was found successfully:";
105                msg.push_str(init);
106                for (src_lineno, found_line) in found {
107                    write!(msg, "\n    ({fname}:{num}) `{found_line}`", num = src_lineno + 1)
108                        .unwrap();
109                }
110            }
111
112            Err(msg)
113        }
114    }
115}
116
117/// Check that the pattern in `check_line` applies to `line`. Returns `true` if they do match.
118fn check_single_line(line: &str, check_line: &str) -> bool {
119    // Allow check lines to leave parts unspecified (e.g., uninitialized
120    // bits in the  wrong case of an enum) with the notation "[...]".
121    let line = line.trim();
122    let check_line = check_line.trim();
123    let can_start_anywhere = check_line.starts_with("[...]");
124    let can_end_anywhere = check_line.ends_with("[...]");
125
126    let check_fragments: Vec<&str> =
127        check_line.split("[...]").filter(|frag| !frag.is_empty()).collect();
128    if check_fragments.is_empty() {
129        return true;
130    }
131
132    let (mut rest, first_fragment) = if can_start_anywhere {
133        let Some(pos) = line.find(check_fragments[0]) else {
134            return false;
135        };
136        (&line[pos + check_fragments[0].len()..], 1)
137    } else {
138        (line, 0)
139    };
140
141    for current_fragment in &check_fragments[first_fragment..] {
142        let Some(pos) = rest.find(current_fragment) else {
143            return false;
144        };
145        rest = &rest[pos + current_fragment.len()..];
146    }
147
148    can_end_anywhere || rest.is_empty()
149}