compiletest/runtest/
debugger.rs1use 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
10pub(super) struct DebuggerCommands {
12 pub commands: Vec<String>,
14 pub breakpoint_lines: Vec<usize>,
16 check_lines: Vec<(usize, String)>,
18 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 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 pub fn check_output(&self, debugger_run_result: &ProcRes) -> Result<(), String> {
69 let mut found = vec![];
71 let mut missing = vec![];
73 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
117fn check_single_line(line: &str, check_line: &str) -> bool {
119 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}