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