compiletest/runtest/
debugger.rs1use std::fmt::Write;
2use std::fs::File;
3use std::io::{BufRead, BufReader};
4
5use camino::{Utf8Path, Utf8PathBuf};
6
7use crate::runtest::ProcRes;
8
9pub(super) struct DebuggerCommands {
11 pub commands: Vec<String>,
13 pub breakpoint_lines: Vec<usize>,
15 check_lines: Vec<(usize, String)>,
17 file: Utf8PathBuf,
19}
20
21impl DebuggerCommands {
22 pub fn parse_from(file: &Utf8Path, debugger_prefix: &str) -> Result<Self, String> {
23 let command_directive = format!("{debugger_prefix}-command");
24 let check_directive = format!("{debugger_prefix}-check");
25
26 let mut breakpoint_lines = vec![];
27 let mut commands = vec![];
28 let mut check_lines = vec![];
29 let mut counter = 0;
30 let reader = BufReader::new(File::open(file.as_std_path()).unwrap());
31 for (line_no, line) in reader.lines().enumerate() {
32 counter += 1;
33 let line = line.map_err(|e| format!("Error while parsing debugger commands: {}", e))?;
34
35 if line.contains("#break") {
37 breakpoint_lines.push(counter);
38 continue;
39 }
40
41 let Some(line) = line.trim_start().strip_prefix("//").map(str::trim_start) else {
42 continue;
43 };
44
45 if let Some(command) = parse_name_value(&line, &command_directive) {
46 commands.push(command);
47 }
48 if let Some(pattern) = parse_name_value(&line, &check_directive) {
49 check_lines.push((line_no, pattern));
50 }
51 }
52
53 Ok(Self { commands, breakpoint_lines, check_lines, file: file.to_path_buf() })
54 }
55
56 pub fn check_output(&self, debugger_run_result: &ProcRes) -> Result<(), String> {
60 let mut found = vec![];
62 let mut missing = vec![];
64 let mut last_idx = 0;
66 let dbg_lines: Vec<&str> = debugger_run_result.stdout.lines().collect();
67
68 for (src_lineno, ck_line) in &self.check_lines {
69 if let Some(offset) = dbg_lines
70 .iter()
71 .skip(last_idx)
72 .position(|out_line| check_single_line(out_line, &ck_line))
73 {
74 last_idx += offset;
75 found.push((src_lineno, dbg_lines[last_idx]));
76 } else {
77 missing.push((src_lineno, ck_line));
78 }
79 }
80
81 if missing.is_empty() {
82 Ok(())
83 } else {
84 let fname = self.file.file_name().unwrap();
85 let mut msg = format!(
86 "check directive(s) from `{}` not found in debugger output. errors:",
87 self.file
88 );
89
90 for (src_lineno, err_line) in missing {
91 write!(msg, "\n ({fname}:{num}) `{err_line}`", num = src_lineno + 1).unwrap();
92 }
93
94 if !found.is_empty() {
95 let init = "\nthe following subset of check directive(s) was found successfully:";
96 msg.push_str(init);
97 for (src_lineno, found_line) in found {
98 write!(msg, "\n ({fname}:{num}) `{found_line}`", num = src_lineno + 1)
99 .unwrap();
100 }
101 }
102
103 Err(msg)
104 }
105 }
106}
107
108fn parse_name_value(line: &str, name: &str) -> Option<String> {
111 if let Some(after_name) = line.strip_prefix(name)
112 && let Some(value) = after_name.strip_prefix(':')
113 {
114 Some(value.to_owned())
115 } else {
116 None
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 can_end_anywhere || rest.is_empty()
152}