]>
Commit | Line | Data |
---|---|---|
1a4d82fc | 1 | use self::WhichLine::*; |
223e47cc | 2 | |
54a0048b | 3 | use std::fmt; |
c34b1796 | 4 | use std::fs::File; |
c34b1796 | 5 | use std::io::prelude::*; |
94b46f34 | 6 | use std::io::BufReader; |
c34b1796 | 7 | use std::path::Path; |
54a0048b SL |
8 | use std::str::FromStr; |
9 | ||
60c5eb7d | 10 | use lazy_static::lazy_static; |
48663c56 | 11 | use log::*; |
60c5eb7d | 12 | use regex::Regex; |
48663c56 | 13 | |
54a0048b SL |
14 | #[derive(Clone, Debug, PartialEq)] |
15 | pub enum ErrorKind { | |
16 | Help, | |
17 | Error, | |
18 | Note, | |
19 | Suggestion, | |
20 | Warning, | |
21 | } | |
22 | ||
23 | impl FromStr for ErrorKind { | |
24 | type Err = (); | |
25 | fn from_str(s: &str) -> Result<Self, Self::Err> { | |
a7813a04 XL |
26 | let s = s.to_uppercase(); |
27 | let part0: &str = s.split(':').next().unwrap(); | |
28 | match part0 { | |
54a0048b SL |
29 | "HELP" => Ok(ErrorKind::Help), |
30 | "ERROR" => Ok(ErrorKind::Error), | |
31 | "NOTE" => Ok(ErrorKind::Note), | |
32 | "SUGGESTION" => Ok(ErrorKind::Suggestion), | |
94b46f34 | 33 | "WARN" | "WARNING" => Ok(ErrorKind::Warning), |
54a0048b SL |
34 | _ => Err(()), |
35 | } | |
36 | } | |
37 | } | |
38 | ||
39 | impl fmt::Display for ErrorKind { | |
9fa01778 | 40 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
54a0048b | 41 | match *self { |
abe05a73 | 42 | ErrorKind::Help => write!(f, "help message"), |
54a0048b SL |
43 | ErrorKind::Error => write!(f, "error"), |
44 | ErrorKind::Note => write!(f, "note"), | |
45 | ErrorKind::Suggestion => write!(f, "suggestion"), | |
46 | ErrorKind::Warning => write!(f, "warning"), | |
47 | } | |
48 | } | |
49 | } | |
223e47cc | 50 | |
a7813a04 XL |
51 | #[derive(Debug)] |
52 | pub struct Error { | |
54a0048b | 53 | pub line_num: usize, |
0731742a | 54 | /// What kind of message we expect (e.g., warning, error, suggestion). |
54a0048b SL |
55 | /// `None` if not specified or unknown message kind. |
56 | pub kind: Option<ErrorKind>, | |
1a4d82fc JJ |
57 | pub msg: String, |
58 | } | |
223e47cc | 59 | |
85aaf69f | 60 | #[derive(PartialEq, Debug)] |
5bcae85e SL |
61 | enum WhichLine { |
62 | ThisLine, | |
63 | FollowPrevious(usize), | |
64 | AdjustBackward(usize), | |
65 | } | |
85aaf69f | 66 | |
1a4d82fc JJ |
67 | /// Looks for either "//~| KIND MESSAGE" or "//~^^... KIND MESSAGE" |
68 | /// The former is a "follow" that inherits its target from the preceding line; | |
69 | /// the latter is an "adjusts" that goes that many lines up. | |
70 | /// | |
71 | /// Goal is to enable tests both like: //~^^^ ERROR go up three | |
72 | /// and also //~^ ERROR message one for the preceding line, and | |
73 | /// //~| ERROR message two for that same line. | |
54a0048b SL |
74 | /// |
75 | /// If cfg is not None (i.e., in an incremental test), then we look | |
76 | /// for `//[X]~` instead, where `X` is the current `cfg`. | |
a7813a04 | 77 | pub fn load_errors(testfile: &Path, cfg: Option<&str>) -> Vec<Error> { |
c34b1796 | 78 | let rdr = BufReader::new(File::open(testfile).unwrap()); |
223e47cc | 79 | |
1a4d82fc JJ |
80 | // `last_nonfollow_error` tracks the most recently seen |
81 | // line with an error template that did not use the | |
82 | // follow-syntax, "//~| ...". | |
83 | // | |
84 | // (pnkfelix could not find an easy way to compose Iterator::scan | |
85 | // and Iterator::filter_map to pass along this information into | |
86 | // `parse_expected`. So instead I am storing that state here and | |
87 | // updating it in the map callback below.) | |
88 | let mut last_nonfollow_error = None; | |
223e47cc | 89 | |
54a0048b | 90 | rdr.lines() |
5bcae85e SL |
91 | .enumerate() |
92 | .filter_map(|(line_num, line)| { | |
60c5eb7d | 93 | parse_expected(last_nonfollow_error, line_num + 1, &line.unwrap(), cfg).map( |
94b46f34 | 94 | |(which, error)| { |
5bcae85e SL |
95 | match which { |
96 | FollowPrevious(_) => {} | |
97 | _ => last_nonfollow_error = Some(error.line_num), | |
98 | } | |
60c5eb7d | 99 | |
5bcae85e | 100 | error |
94b46f34 XL |
101 | }, |
102 | ) | |
5bcae85e SL |
103 | }) |
104 | .collect() | |
1a4d82fc | 105 | } |
223e47cc | 106 | |
94b46f34 XL |
107 | fn parse_expected( |
108 | last_nonfollow_error: Option<usize>, | |
109 | line_num: usize, | |
110 | line: &str, | |
60c5eb7d | 111 | cfg: Option<&str>, |
94b46f34 | 112 | ) -> Option<(WhichLine, Error)> { |
60c5eb7d XL |
113 | // Matches comments like: |
114 | // //~ | |
115 | // //~| | |
116 | // //~^ | |
117 | // //~^^^^^ | |
118 | // //[cfg1]~ | |
119 | // //[cfg1,cfg2]~^^ | |
120 | lazy_static! { | |
121 | static ref RE: Regex = | |
122 | Regex::new(r"//(?:\[(?P<cfgs>[\w,]+)])?~(?P<adjust>\||\^*)").unwrap(); | |
123 | } | |
124 | ||
125 | let captures = RE.captures(line)?; | |
126 | ||
127 | match (cfg, captures.name("cfgs")) { | |
128 | // Only error messages that contain our `cfg` betweeen the square brackets apply to us. | |
dfeec247 | 129 | (Some(cfg), Some(filter)) if !filter.as_str().split(',').any(|s| s == cfg) => return None, |
60c5eb7d XL |
130 | (Some(_), Some(_)) => {} |
131 | ||
132 | (None, Some(_)) => panic!("Only tests with revisions should use `//[X]~`"), | |
133 | ||
134 | // If an error has no list of revisions, it applies to all revisions. | |
135 | (Some(_), None) | (None, None) => {} | |
136 | } | |
137 | ||
138 | let (follow, adjusts) = match &captures["adjust"] { | |
139 | "|" => (true, 0), | |
140 | circumflexes => (false, circumflexes.len()), | |
85aaf69f | 141 | }; |
60c5eb7d XL |
142 | |
143 | // Get the part of the comment after the sigil (e.g. `~^^` or ~|). | |
144 | let whole_match = captures.get(0).unwrap(); | |
145 | let (_, mut msg) = line.split_at(whole_match.end()); | |
146 | ||
dfeec247 | 147 | let first_word = msg.split_whitespace().next().expect("Encountered unexpected empty comment"); |
60c5eb7d XL |
148 | |
149 | // If we find `//~ ERROR foo` or something like that, skip the first word. | |
150 | let kind = first_word.parse::<ErrorKind>().ok(); | |
151 | if let Some(_) = kind { | |
152 | msg = &msg.trim_start().split_at(first_word.len()).1; | |
a7813a04 | 153 | } |
60c5eb7d | 154 | |
a7813a04 | 155 | let msg = msg.trim().to_owned(); |
223e47cc | 156 | |
54a0048b | 157 | let (which, line_num) = if follow { |
041b39d2 | 158 | assert_eq!(adjusts, 0, "use either //~| or //~^, not both."); |
94b46f34 XL |
159 | let line_num = last_nonfollow_error.expect( |
160 | "encountered //~| without \ | |
161 | preceding //~^ line.", | |
162 | ); | |
54a0048b | 163 | (FollowPrevious(line_num), line_num) |
85aaf69f | 164 | } else { |
dfeec247 | 165 | let which = if adjusts > 0 { AdjustBackward(adjusts) } else { ThisLine }; |
54a0048b SL |
166 | let line_num = line_num - adjusts; |
167 | (which, line_num) | |
85aaf69f | 168 | }; |
223e47cc | 169 | |
94b46f34 XL |
170 | debug!( |
171 | "line={} tag={:?} which={:?} kind={:?} msg={:?}", | |
dfeec247 XL |
172 | line_num, |
173 | whole_match.as_str(), | |
94b46f34 | 174 | which, |
dfeec247 XL |
175 | kind, |
176 | msg | |
177 | ); | |
178 | Some((which, Error { line_num, kind, msg })) | |
223e47cc | 179 | } |