compiletest/runtest/
debugger.rs1use std::fmt::Write;
2use std::fs::File;
3use std::io::{BufRead, BufReader};
4
5use camino::{Utf8Path, Utf8PathBuf};
6
7use crate::directives::{LineNumber, line_directive};
8use crate::runtest::ProcRes;
9
10pub(crate) struct DebuggerCommands {
12 pub(crate) commands: Vec<String>,
14 pub(crate) breakpoint_lines: Vec<LineNumber>,
16 check_lines: Vec<(LineNumber, String)>,
18 file: Utf8PathBuf,
20 revision: Option<String>,
22}
23
24impl DebuggerCommands {
25 pub(crate) fn parse_from(
26 file: &Utf8Path,
27 debugger_prefix: &str,
28 test_revision: Option<&str>,
29 ) -> Result<Self, String> {
30 let mut breakpoint_lines = vec![];
31 let mut commands = vec![];
32 let mut check_lines = vec![];
33 let reader = BufReader::new(File::open(file.as_std_path()).unwrap());
34 for (line_number, line) in LineNumber::enumerate().zip(reader.lines()) {
35 let line = line.map_err(|e| format!("Error while parsing debugger commands: {}", e))?;
36
37 if line.contains("#break") {
39 breakpoint_lines.push(line_number);
40 continue;
41 }
42
43 let Some(directive) = line_directive(file, line_number, &line) else {
44 continue;
45 };
46
47 if !directive.applies_to_test_revision(test_revision) {
48 continue;
49 }
50
51 let Some(directive_kind) = directive.name.strip_prefix(debugger_prefix) else {
52 continue;
53 };
54
55 match (directive_kind, directive.value_after_colon()) {
56 ("-command", Some(command)) => commands.push(command.to_string()),
57 ("-check", Some(pattern)) => check_lines.push((line_number, pattern.to_string())),
58 ("-repr", Some(var_name)) => {
59 commands.push(format!("repr {}", var_name.trim()));
62 check_lines.push((line_number, format!("{var_name}: Ok")));
65 }
66 _ => continue,
67 }
68 }
69
70 Ok(Self {
71 commands,
72 breakpoint_lines,
73 check_lines,
74 file: file.to_path_buf(),
75 revision: test_revision.map(str::to_owned),
76 })
77 }
78
79 pub(crate) fn check_output(&self, debugger_run_result: &ProcRes) -> Result<(), String> {
83 let mut found = vec![];
85 let mut missing = vec![];
87 let mut last_idx = 0;
89 let dbg_lines: Vec<&str> = debugger_run_result.stdout.lines().collect();
90
91 for (src_lineno, ck_line) in &self.check_lines {
92 if let Some(offset) = dbg_lines
93 .iter()
94 .skip(last_idx)
95 .position(|out_line| check_single_line(out_line, &ck_line))
96 {
97 last_idx += offset;
98 found.push((src_lineno, dbg_lines[last_idx]));
99 } else {
100 missing.push((src_lineno, ck_line));
101 }
102 }
103
104 if missing.is_empty() {
105 Ok(())
106 } else {
107 let fname = self.file.file_name().unwrap();
108 let revision_suffix =
109 self.revision.as_ref().map_or(String::new(), |r| format!("#{}", r));
110 let mut msg = format!(
111 "check directive(s) from `{}{}` not found in debugger output. errors:",
112 self.file, revision_suffix
113 );
114
115 for (src_lineno, err_line) in missing {
116 write!(msg, "\n ({fname}:{src_lineno}) `{err_line}`").unwrap();
117 }
118
119 if !found.is_empty() {
120 let init = "\nthe following subset of check directive(s) was found successfully:";
121 msg.push_str(init);
122 for (src_lineno, found_line) in found {
123 write!(msg, "\n ({fname}:{src_lineno}) `{found_line}`").unwrap();
124 }
125 }
126
127 Err(msg)
128 }
129 }
130}
131
132fn check_single_line(line: &str, check_line: &str) -> bool {
134 let line = line.trim();
137 let check_line = check_line.trim();
138 let can_start_anywhere = check_line.starts_with("[...]");
139 let can_end_anywhere = check_line.ends_with("[...]");
140
141 let check_fragments: Vec<&str> =
142 check_line.split("[...]").filter(|frag| !frag.is_empty()).collect();
143 if check_fragments.is_empty() {
144 return true;
145 }
146
147 let (mut rest, first_fragment) = if can_start_anywhere {
148 let Some(pos) = line.find(check_fragments[0]) else {
149 return false;
150 };
151 (&line[pos + check_fragments[0].len()..], 1)
152 } else {
153 (line, 0)
154 };
155
156 for current_fragment in &check_fragments[first_fragment..] {
157 let Some(pos) = rest.find(current_fragment) else {
158 return false;
159 };
160 rest = &rest[pos + current_fragment.len()..];
161 }
162
163 can_end_anywhere || rest.is_empty()
164}