hkalbasi_rustc_ap_compiletest/runtest/
debugger.rs1use crate::common::Config;
2use crate::header::line_directive;
3use crate::runtest::ProcRes;
4
5use std::fmt::Write;
6use std::fs::File;
7use std::io::{BufRead, BufReader};
8use std::path::{Path, PathBuf};
9
10pub(super) struct DebuggerCommands {
12    pub commands: Vec<String>,
14    pub breakpoint_lines: Vec<usize>,
16    check_lines: Vec<(usize, String)>,
18    file: PathBuf,
20}
21
22impl DebuggerCommands {
23    pub fn parse_from(
24        file: &Path,
25        config: &Config,
26        debugger_prefixes: &[&str],
27        rev: Option<&str>,
28    ) -> Result<Self, String> {
29        let directives = debugger_prefixes
30            .iter()
31            .map(|prefix| (format!("{prefix}-command"), format!("{prefix}-check")))
32            .collect::<Vec<_>>();
33
34        let mut breakpoint_lines = vec![];
35        let mut commands = vec![];
36        let mut check_lines = vec![];
37        let mut counter = 0;
38        let reader = BufReader::new(File::open(file).unwrap());
39        for (line_no, line) in reader.lines().enumerate() {
40            counter += 1;
41            let line = line.map_err(|e| format!("Error while parsing debugger commands: {}", e))?;
42            let (lnrev, line) = line_directive("//", &line).unwrap_or((None, &line));
43
44            if lnrev.is_some() && lnrev != rev {
47                continue;
48            }
49
50            if line.contains("#break") {
51                breakpoint_lines.push(counter);
52            }
53
54            for &(ref command_directive, ref check_directive) in &directives {
55                config
56                    .parse_name_value_directive(&line, command_directive)
57                    .map(|cmd| commands.push(cmd));
58
59                config
60                    .parse_name_value_directive(&line, check_directive)
61                    .map(|cmd| check_lines.push((line_no, cmd)));
62            }
63        }
64
65        Ok(Self { commands, breakpoint_lines, check_lines, file: file.to_owned() })
66    }
67
68    pub fn check_output(&self, debugger_run_result: &ProcRes) -> Result<(), String> {
72        let mut found = vec![];
74        let mut missing = vec![];
76        let mut last_idx = 0;
78        let dbg_lines: Vec<&str> = debugger_run_result.stdout.lines().collect();
79
80        for (src_lineno, ck_line) in &self.check_lines {
81            if let Some(offset) = dbg_lines
82                .iter()
83                .skip(last_idx)
84                .position(|out_line| check_single_line(out_line, &ck_line))
85            {
86                last_idx += offset;
87                found.push((src_lineno, dbg_lines[last_idx]));
88            } else {
89                missing.push((src_lineno, ck_line));
90            }
91        }
92
93        if missing.is_empty() {
94            Ok(())
95        } else {
96            let fname = self.file.file_name().unwrap().to_string_lossy();
97            let mut msg = format!(
98                "check directive(s) from `{}` not found in debugger output. errors:",
99                self.file.display()
100            );
101
102            for (src_lineno, err_line) in missing {
103                write!(msg, "\n    ({fname}:{num}) `{err_line}`", num = src_lineno + 1).unwrap();
104            }
105
106            if !found.is_empty() {
107                let init = "\nthe following subset of check directive(s) was found successfully:";
108                msg.push_str(init);
109                for (src_lineno, found_line) in found {
110                    write!(msg, "\n    ({fname}:{num}) `{found_line}`", num = src_lineno + 1)
111                        .unwrap();
112                }
113            }
114
115            Err(msg)
116        }
117    }
118}
119
120fn check_single_line(line: &str, check_line: &str) -> bool {
122    let line = line.trim();
125    let check_line = check_line.trim();
126    let can_start_anywhere = check_line.starts_with("[...]");
127    let can_end_anywhere = check_line.ends_with("[...]");
128
129    let check_fragments: Vec<&str> =
130        check_line.split("[...]").filter(|frag| !frag.is_empty()).collect();
131    if check_fragments.is_empty() {
132        return true;
133    }
134
135    let (mut rest, first_fragment) = if can_start_anywhere {
136        let Some(pos) = line.find(check_fragments[0]) else {
137            return false;
138        };
139        (&line[pos + check_fragments[0].len()..], 1)
140    } else {
141        (line, 0)
142    };
143
144    for current_fragment in &check_fragments[first_fragment..] {
145        let Some(pos) = rest.find(current_fragment) else {
146            return false;
147        };
148        rest = &rest[pos + current_fragment.len()..];
149    }
150
151    if !can_end_anywhere && !rest.is_empty() { false } else { true }
152}