]> git.lizzy.rs Git - rust.git/blob - src/tools/compiletest/src/json.rs
d9da1bdc3485837d8024036f23ff5be81f5e7c71
[rust.git] / src / tools / compiletest / src / json.rs
1 // Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use errors::{Error, ErrorKind};
12 use rustc_serialize::json;
13 use std::str::FromStr;
14 use std::path::Path;
15 use runtest::ProcRes;
16
17 // These structs are a subset of the ones found in
18 // `syntax::json`.
19
20 #[derive(RustcEncodable, RustcDecodable)]
21 struct Diagnostic {
22     message: String,
23     code: Option<DiagnosticCode>,
24     level: String,
25     spans: Vec<DiagnosticSpan>,
26     children: Vec<Diagnostic>,
27     rendered: Option<String>,
28 }
29
30 #[derive(RustcEncodable, RustcDecodable, Clone)]
31 struct DiagnosticSpan {
32     file_name: String,
33     line_start: usize,
34     line_end: usize,
35     column_start: usize,
36     column_end: usize,
37     is_primary: bool,
38     label: Option<String>,
39     expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
40 }
41
42 #[derive(RustcEncodable, RustcDecodable, Clone)]
43 struct DiagnosticSpanMacroExpansion {
44     /// span where macro was applied to generate this code
45     span: DiagnosticSpan,
46
47     /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
48     macro_decl_name: String,
49 }
50
51 #[derive(RustcEncodable, RustcDecodable, Clone)]
52 struct DiagnosticCode {
53     /// The code itself.
54     code: String,
55     /// An explanation for the code.
56     explanation: Option<String>,
57 }
58
59 pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
60     output.lines()
61         .flat_map(|line| parse_line(file_name, line, output, proc_res))
62         .collect()
63 }
64
65 fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
66     // The compiler sometimes intermingles non-JSON stuff into the
67     // output.  This hack just skips over such lines. Yuck.
68     if line.chars().next() == Some('{') {
69         match json::decode::<Diagnostic>(line) {
70             Ok(diagnostic) => {
71                 let mut expected_errors = vec![];
72                 push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name);
73                 expected_errors
74             }
75             Err(error) => {
76                 proc_res.fatal(Some(&format!("failed to decode compiler output as json: \
77                                               `{}`\noutput: {}\nline: {}",
78                                              error,
79                                              line,
80                                              output)));
81             }
82         }
83     } else {
84         vec![]
85     }
86 }
87
88 fn push_expected_errors(expected_errors: &mut Vec<Error>,
89                         diagnostic: &Diagnostic,
90                         default_spans: &[&DiagnosticSpan],
91                         file_name: &str) {
92     let spans_in_this_file: Vec<_> = diagnostic.spans
93         .iter()
94         .filter(|span| Path::new(&span.file_name) == Path::new(&file_name))
95         .collect();
96
97     let primary_spans: Vec<_> = spans_in_this_file.iter()
98         .cloned()
99         .filter(|span| span.is_primary)
100         .collect();
101     let primary_spans = if primary_spans.is_empty() {
102         // subdiagnostics often don't have a span of their own;
103         // inherit the span from the parent in that case
104         default_spans
105     } else {
106         &primary_spans
107     };
108
109     // We break the output into multiple lines, and then append the
110     // [E123] to every line in the output. This may be overkill.  The
111     // intention was to match existing tests that do things like "//|
112     // found `i32` [E123]" and expect to match that somewhere, and yet
113     // also ensure that `//~ ERROR E123` *always* works. The
114     // assumption is that these multi-line error messages are on their
115     // way out anyhow.
116     let with_code = |span: &DiagnosticSpan, text: &str| {
117         match diagnostic.code {
118             Some(ref code) =>
119                 // FIXME(#33000) -- it'd be better to use a dedicated
120                 // UI harness than to include the line/col number like
121                 // this, but some current tests rely on it.
122                 //
123                 // Note: Do NOT include the filename. These can easily
124                 // cause false matches where the expected message
125                 // appears in the filename, and hence the message
126                 // changes but the test still passes.
127                 format!("{}:{}: {}:{}: {} [{}]",
128                         span.line_start, span.column_start,
129                         span.line_end, span.column_end,
130                         text, code.code.clone()),
131             None =>
132                 // FIXME(#33000) -- it'd be better to use a dedicated UI harness
133                 format!("{}:{}: {}:{}: {}",
134                         span.line_start, span.column_start,
135                         span.line_end, span.column_end,
136                         text),
137         }
138     };
139
140     // Convert multi-line messages into multiple expected
141     // errors. We expect to replace these with something
142     // more structured shortly anyhow.
143     let mut message_lines = diagnostic.message.lines();
144     if let Some(first_line) = message_lines.next() {
145         for span in primary_spans {
146             let msg = with_code(span, first_line);
147             let kind = ErrorKind::from_str(&diagnostic.level).ok();
148             expected_errors.push(Error {
149                 line_num: span.line_start,
150                 kind: kind,
151                 msg: msg,
152             });
153         }
154     }
155     for next_line in message_lines {
156         for span in primary_spans {
157             expected_errors.push(Error {
158                 line_num: span.line_start,
159                 kind: None,
160                 msg: with_code(span, next_line),
161             });
162         }
163     }
164
165     // If the message has a suggestion, register that.
166     if let Some(ref rendered) = diagnostic.rendered {
167         let start_line = primary_spans.iter().map(|s| s.line_start).min().expect("\
168             every suggestion should have at least one span");
169         for (index, line) in rendered.lines().enumerate() {
170             expected_errors.push(Error {
171                 line_num: start_line + index,
172                 kind: Some(ErrorKind::Suggestion),
173                 msg: line.to_string(),
174             });
175         }
176     }
177
178     // Add notes for the backtrace
179     for span in primary_spans {
180         for frame in &span.expansion {
181             push_backtrace(expected_errors, frame, file_name);
182         }
183     }
184
185     // Add notes for any labels that appear in the message.
186     for span in spans_in_this_file.iter()
187         .filter(|span| span.label.is_some()) {
188         expected_errors.push(Error {
189             line_num: span.line_start,
190             kind: Some(ErrorKind::Note),
191             msg: span.label.clone().unwrap(),
192         });
193     }
194
195     // Flatten out the children.
196     for child in &diagnostic.children {
197         push_expected_errors(expected_errors, child, primary_spans, file_name);
198     }
199 }
200
201 fn push_backtrace(expected_errors: &mut Vec<Error>,
202                   expansion: &DiagnosticSpanMacroExpansion,
203                   file_name: &str) {
204     if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
205         expected_errors.push(Error {
206             line_num: expansion.span.line_start,
207             kind: Some(ErrorKind::Note),
208             msg: format!("in this expansion of {}", expansion.macro_decl_name),
209         });
210     }
211
212     for previous_expansion in &expansion.span.expansion {
213         push_backtrace(expected_errors, previous_expansion, file_name);
214     }
215 }