compiletest/
errors.rs
1use std::fmt;
2use std::fs::File;
3use std::io::BufReader;
4use std::io::prelude::*;
5use std::path::Path;
6use std::str::FromStr;
7use std::sync::OnceLock;
8
9use regex::Regex;
10use tracing::*;
11
12#[derive(Copy, Clone, Debug, PartialEq)]
13pub enum ErrorKind {
14 Help,
15 Error,
16 Note,
17 Suggestion,
18 Warning,
19}
20
21impl FromStr for ErrorKind {
22 type Err = ();
23 fn from_str(s: &str) -> Result<Self, Self::Err> {
24 let s = s.to_uppercase();
25 let part0: &str = s.split(':').next().unwrap();
26 match part0 {
27 "HELP" => Ok(ErrorKind::Help),
28 "ERROR" => Ok(ErrorKind::Error),
29 "NOTE" => Ok(ErrorKind::Note),
30 "SUGGESTION" => Ok(ErrorKind::Suggestion),
31 "WARN" | "WARNING" => Ok(ErrorKind::Warning),
32 _ => Err(()),
33 }
34 }
35}
36
37impl fmt::Display for ErrorKind {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match *self {
40 ErrorKind::Help => write!(f, "help message"),
41 ErrorKind::Error => write!(f, "error"),
42 ErrorKind::Note => write!(f, "note"),
43 ErrorKind::Suggestion => write!(f, "suggestion"),
44 ErrorKind::Warning => write!(f, "warning"),
45 }
46 }
47}
48
49#[derive(Debug)]
50pub struct Error {
51 pub line_num: Option<usize>,
52 pub kind: Option<ErrorKind>,
55 pub msg: String,
56}
57
58impl Error {
59 pub fn render_for_expected(&self) -> String {
60 use colored::Colorize;
61 format!(
62 "{: <10}line {: >3}: {}",
63 self.kind.map(|kind| kind.to_string()).unwrap_or_default().to_uppercase(),
64 self.line_num_str(),
65 self.msg.cyan(),
66 )
67 }
68
69 pub fn line_num_str(&self) -> String {
70 self.line_num.map_or("?".to_string(), |line_num| line_num.to_string())
71 }
72}
73
74pub fn load_errors(testfile: &Path, revision: Option<&str>) -> Vec<Error> {
85 let rdr = BufReader::new(File::open(testfile).unwrap());
86
87 let mut last_nonfollow_error = None;
96
97 rdr.lines()
98 .enumerate()
99 .filter(|(_, line)| line.is_ok())
101 .filter_map(|(line_num, line)| {
102 parse_expected(last_nonfollow_error, line_num + 1, &line.unwrap(), revision).map(
103 |(follow_prev, error)| {
104 if !follow_prev {
105 last_nonfollow_error = error.line_num;
106 }
107 error
108 },
109 )
110 })
111 .collect()
112}
113
114fn parse_expected(
115 last_nonfollow_error: Option<usize>,
116 line_num: usize,
117 line: &str,
118 test_revision: Option<&str>,
119) -> Option<(bool, Error)> {
120 static RE: OnceLock<Regex> = OnceLock::new();
129
130 let captures = RE
131 .get_or_init(|| Regex::new(r"//(?:\[(?P<revs>[\w\-,]+)])?~(?P<adjust>\?|\||\^*)").unwrap())
132 .captures(line)?;
133
134 match (test_revision, captures.name("revs")) {
135 (Some(test_revision), Some(revision_filters)) => {
137 if !revision_filters.as_str().split(',').any(|r| r == test_revision) {
138 return None;
139 }
140 }
141
142 (None, Some(_)) => panic!("Only tests with revisions should use `//[X]~`"),
143
144 (Some(_), None) | (None, None) => {}
146 }
147
148 let whole_match = captures.get(0).unwrap();
150 let (_, mut msg) = line.split_at(whole_match.end());
151
152 let first_word = msg.split_whitespace().next().expect("Encountered unexpected empty comment");
153
154 let kind = first_word.parse::<ErrorKind>().ok();
156 if kind.is_some() {
157 msg = &msg.trim_start().split_at(first_word.len()).1;
158 }
159
160 let msg = msg.trim().to_owned();
161
162 let line_num_adjust = &captures["adjust"];
163 let (follow_prev, line_num) = if line_num_adjust == "|" {
164 (true, Some(last_nonfollow_error.expect("encountered //~| without preceding //~^ line")))
165 } else if line_num_adjust == "?" {
166 (false, None)
167 } else {
168 (false, Some(line_num - line_num_adjust.len()))
169 };
170
171 debug!(
172 "line={:?} tag={:?} follow_prev={:?} kind={:?} msg={:?}",
173 line_num,
174 whole_match.as_str(),
175 follow_prev,
176 kind,
177 msg
178 );
179 Some((follow_prev, Error { line_num, kind, msg }))
180}
181
182#[cfg(test)]
183mod tests;