Skip to content

Commit 01d2b4a

Browse files
committed
port compiletest to use JSON output
This uncovered a lot of bugs in compiletest and also some shortcomings of our existing JSON output. We had to add information to the JSON output, such as suggested text and macro backtraces. We also had to fix various bugs in the existing tests. Joint work with jntrnr.
1 parent 95545e7 commit 01d2b4a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+606
-474
lines changed

mk/crates.mk

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ DEPS_rustdoc := rustc rustc_driver native:hoedown serialize getopts \
128128
test rustc_lint rustc_const_eval
129129

130130

131-
TOOL_DEPS_compiletest := test getopts log
131+
TOOL_DEPS_compiletest := test getopts log serialize
132132
TOOL_DEPS_rustdoc := rustdoc
133133
TOOL_DEPS_rustc := rustc_driver
134134
TOOL_DEPS_rustbook := std rustdoc

src/compiletest/json.rs

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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+
15+
// These structs are a subset of the ones found in
16+
// `syntax::errors::json`.
17+
18+
#[derive(RustcEncodable, RustcDecodable)]
19+
struct Diagnostic {
20+
message: String,
21+
code: Option<DiagnosticCode>,
22+
level: String,
23+
spans: Vec<DiagnosticSpan>,
24+
children: Vec<Diagnostic>,
25+
rendered: Option<String>,
26+
}
27+
28+
#[derive(RustcEncodable, RustcDecodable, Clone)]
29+
struct DiagnosticSpan {
30+
file_name: String,
31+
line_start: usize,
32+
line_end: usize,
33+
column_start: usize,
34+
column_end: usize,
35+
expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
36+
}
37+
38+
#[derive(RustcEncodable, RustcDecodable, Clone)]
39+
struct DiagnosticSpanMacroExpansion {
40+
/// span where macro was applied to generate this code
41+
span: DiagnosticSpan,
42+
43+
/// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
44+
macro_decl_name: String,
45+
}
46+
47+
#[derive(RustcEncodable, RustcDecodable, Clone)]
48+
struct DiagnosticCode {
49+
/// The code itself.
50+
code: String,
51+
/// An explanation for the code.
52+
explanation: Option<String>,
53+
}
54+
55+
pub fn parse_output(file_name: &str, output: &str) -> Vec<Error> {
56+
output.lines()
57+
.flat_map(|line| parse_line(file_name, line))
58+
.collect()
59+
}
60+
61+
fn parse_line(file_name: &str, line: &str) -> Vec<Error> {
62+
// The compiler sometimes intermingles non-JSON stuff into the
63+
// output. This hack just skips over such lines. Yuck.
64+
if line.chars().next() == Some('{') {
65+
match json::decode::<Diagnostic>(line) {
66+
Ok(diagnostic) => {
67+
let mut expected_errors = vec![];
68+
push_expected_errors(&mut expected_errors, &diagnostic, file_name);
69+
expected_errors
70+
}
71+
Err(error) => {
72+
println!("failed to decode compiler output as json: `{}`", error);
73+
panic!("failed to decode compiler output as json");
74+
}
75+
}
76+
} else {
77+
vec![]
78+
}
79+
}
80+
81+
fn push_expected_errors(expected_errors: &mut Vec<Error>,
82+
diagnostic: &Diagnostic,
83+
file_name: &str) {
84+
// We only consider messages pertaining to the current file.
85+
let matching_spans =
86+
|| diagnostic.spans.iter().filter(|span| span.file_name == file_name);
87+
let with_code =
88+
|span: &DiagnosticSpan, text: &str| match diagnostic.code {
89+
Some(ref code) =>
90+
// FIXME(#33000) -- it'd be better to use a dedicated
91+
// UI harness than to include the line/col number like
92+
// this, but some current tests rely on it.
93+
//
94+
// Note: Do NOT include the filename. These can easily
95+
// cause false matches where the expected message
96+
// appears in the filename, and hence the message
97+
// changes but the test still passes.
98+
format!("{}:{}: {}:{}: {} [{}]",
99+
span.line_start, span.column_start,
100+
span.line_end, span.column_end,
101+
text, code.code.clone()),
102+
None =>
103+
// FIXME(#33000) -- it'd be better to use a dedicated UI harness
104+
format!("{}:{}: {}:{}: {}",
105+
span.line_start, span.column_start,
106+
span.line_end, span.column_end,
107+
text),
108+
};
109+
110+
// Convert multi-line messages into multiple expected
111+
// errors. We expect to replace these with something
112+
// more structured shortly anyhow.
113+
let mut message_lines = diagnostic.message.lines();
114+
if let Some(first_line) = message_lines.next() {
115+
for span in matching_spans() {
116+
let msg = with_code(span, first_line);
117+
let kind = ErrorKind::from_str(&diagnostic.level).ok();
118+
expected_errors.push(
119+
Error {
120+
line_num: span.line_start,
121+
kind: kind,
122+
msg: msg,
123+
}
124+
);
125+
}
126+
}
127+
for next_line in message_lines {
128+
for span in matching_spans() {
129+
expected_errors.push(
130+
Error {
131+
line_num: span.line_start,
132+
kind: None,
133+
msg: with_code(span, next_line),
134+
}
135+
);
136+
}
137+
}
138+
139+
// If the message has a suggestion, register that.
140+
if let Some(ref rendered) = diagnostic.rendered {
141+
let start_line = matching_spans().map(|s| s.line_start).min().expect("\
142+
every suggestion should have at least one span");
143+
for (index, line) in rendered.lines().enumerate() {
144+
expected_errors.push(
145+
Error {
146+
line_num: start_line + index,
147+
kind: Some(ErrorKind::Suggestion),
148+
msg: line.to_string()
149+
}
150+
);
151+
}
152+
}
153+
154+
// Add notes for the backtrace
155+
for span in matching_spans() {
156+
for frame in &span.expansion {
157+
push_backtrace(expected_errors,
158+
frame,
159+
file_name);
160+
}
161+
}
162+
163+
// Flatten out the children.
164+
for child in &diagnostic.children {
165+
push_expected_errors(expected_errors, child, file_name);
166+
}
167+
}
168+
169+
fn push_backtrace(expected_errors: &mut Vec<Error>,
170+
expansion: &DiagnosticSpanMacroExpansion,
171+
file_name: &str) {
172+
if expansion.span.file_name == file_name {
173+
expected_errors.push(
174+
Error {
175+
line_num: expansion.span.line_start,
176+
kind: Some(ErrorKind::Note),
177+
msg: format!("in this expansion of {}", expansion.macro_decl_name),
178+
}
179+
);
180+
}
181+
182+
for previous_expansion in &expansion.span.expansion {
183+
push_backtrace(expected_errors, previous_expansion, file_name);
184+
}
185+
}

src/libsyntax/codemap.rs

+50
Original file line numberDiff line numberDiff line change
@@ -1394,6 +1394,56 @@ impl CodeMap {
13941394
pub fn count_lines(&self) -> usize {
13951395
self.files.borrow().iter().fold(0, |a, f| a + f.count_lines())
13961396
}
1397+
1398+
pub fn macro_backtrace(&self, span: Span) -> Vec<MacroBacktrace> {
1399+
let mut last_span = DUMMY_SP;
1400+
let mut span = span;
1401+
let mut result = vec![];
1402+
loop {
1403+
let span_name_span = self.with_expn_info(span.expn_id, |expn_info| {
1404+
expn_info.map(|ei| {
1405+
let (pre, post) = match ei.callee.format {
1406+
MacroAttribute(..) => ("#[", "]"),
1407+
MacroBang(..) => ("", "!"),
1408+
};
1409+
let macro_decl_name = format!("{}{}{}",
1410+
pre,
1411+
ei.callee.name(),
1412+
post);
1413+
let def_site_span = ei.callee.span;
1414+
(ei.call_site, macro_decl_name, def_site_span)
1415+
})
1416+
});
1417+
1418+
match span_name_span {
1419+
None => break,
1420+
Some((call_site, macro_decl_name, def_site_span)) => {
1421+
// Don't print recursive invocations
1422+
if !call_site.source_equal(&last_span) {
1423+
result.push(MacroBacktrace {
1424+
call_site: call_site,
1425+
macro_decl_name: macro_decl_name,
1426+
def_site_span: def_site_span,
1427+
});
1428+
}
1429+
last_span = span;
1430+
span = call_site;
1431+
}
1432+
}
1433+
}
1434+
result
1435+
}
1436+
}
1437+
1438+
pub struct MacroBacktrace {
1439+
/// span where macro was applied to generate this code
1440+
pub call_site: Span,
1441+
1442+
/// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
1443+
pub macro_decl_name: String,
1444+
1445+
/// span where macro was defined (if known)
1446+
pub def_site_span: Option<Span>,
13971447
}
13981448

13991449
// _____________________________________________________________________________

src/libsyntax/errors/emitter.rs

+9-38
Original file line numberDiff line numberDiff line change
@@ -577,46 +577,17 @@ impl EmitterWriter {
577577
fn print_macro_backtrace(&mut self,
578578
sp: Span)
579579
-> io::Result<()> {
580-
let mut last_span = codemap::DUMMY_SP;
581-
let mut span = sp;
582-
583-
loop {
584-
let span_name_span = self.cm.with_expn_info(span.expn_id, |expn_info| {
585-
expn_info.map(|ei| {
586-
let (pre, post) = match ei.callee.format {
587-
codemap::MacroAttribute(..) => ("#[", "]"),
588-
codemap::MacroBang(..) => ("", "!"),
589-
};
590-
let macro_decl_name = format!("in this expansion of {}{}{}",
591-
pre,
592-
ei.callee.name(),
593-
post);
594-
let def_site_span = ei.callee.span;
595-
(ei.call_site, macro_decl_name, def_site_span)
596-
})
597-
});
598-
let (macro_decl_name, def_site_span) = match span_name_span {
599-
None => break,
600-
Some((sp, macro_decl_name, def_site_span)) => {
601-
span = sp;
602-
(macro_decl_name, def_site_span)
603-
}
604-
};
605-
606-
// Don't print recursive invocations
607-
if !span.source_equal(&last_span) {
608-
let mut diag_string = macro_decl_name;
609-
if let Some(def_site_span) = def_site_span {
610-
diag_string.push_str(&format!(" (defined in {})",
611-
self.cm.span_to_filename(def_site_span)));
612-
}
613-
614-
let snippet = self.cm.span_to_string(span);
615-
print_diagnostic(&mut self.dst, &snippet, Note, &diag_string, None)?;
580+
for trace in self.cm.macro_backtrace(sp) {
581+
let mut diag_string =
582+
format!("in this expansion of {}", trace.macro_decl_name);
583+
if let Some(def_site_span) = trace.def_site_span {
584+
diag_string.push_str(
585+
&format!(" (defined in {})",
586+
self.cm.span_to_filename(def_site_span)));
616587
}
617-
last_span = span;
588+
let snippet = self.cm.span_to_string(sp);
589+
print_diagnostic(&mut self.dst, &snippet, Note, &diag_string, None)?;
618590
}
619-
620591
Ok(())
621592
}
622593
}

0 commit comments

Comments
 (0)