about summary refs log tree commit diff
path: root/src/tools/compiletest
diff options
context:
space:
mode:
authorNiko Matsakis <niko@alum.mit.edu>2016-04-19 11:24:41 -0400
committerNiko Matsakis <niko@alum.mit.edu>2016-04-21 04:42:25 -0400
commit6ce5e770d2b456fd44dce50703afd980ffe2ff33 (patch)
treee8d2e2e4c376ba27a38dddba5590467d6c95156e /src/tools/compiletest
parent28a3c881576ee09fe8ab9dfff4a15ffb89e1c149 (diff)
move json.rs file
Diffstat (limited to 'src/tools/compiletest')
-rw-r--r--src/tools/compiletest/src/json.rs194
1 files changed, 194 insertions, 0 deletions
diff --git a/src/tools/compiletest/src/json.rs b/src/tools/compiletest/src/json.rs
new file mode 100644
index 00000000000..bbe038509e3
--- /dev/null
+++ b/src/tools/compiletest/src/json.rs
@@ -0,0 +1,194 @@
+// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use errors::{Error, ErrorKind};
+use rustc_serialize::json;
+use std::str::FromStr;
+
+// These structs are a subset of the ones found in
+// `syntax::errors::json`.
+
+#[derive(RustcEncodable, RustcDecodable)]
+struct Diagnostic {
+    message: String,
+    code: Option<DiagnosticCode>,
+    level: String,
+    spans: Vec<DiagnosticSpan>,
+    children: Vec<Diagnostic>,
+    rendered: Option<String>,
+}
+
+#[derive(RustcEncodable, RustcDecodable, Clone)]
+struct DiagnosticSpan {
+    file_name: String,
+    line_start: usize,
+    line_end: usize,
+    column_start: usize,
+    column_end: usize,
+    expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
+}
+
+#[derive(RustcEncodable, RustcDecodable, Clone)]
+struct DiagnosticSpanMacroExpansion {
+    /// span where macro was applied to generate this code
+    span: DiagnosticSpan,
+
+    /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
+    macro_decl_name: String,
+}
+
+#[derive(RustcEncodable, RustcDecodable, Clone)]
+struct DiagnosticCode {
+    /// The code itself.
+    code: String,
+    /// An explanation for the code.
+    explanation: Option<String>,
+}
+
+pub fn parse_output(file_name: &str, output: &str) -> Vec<Error> {
+    output.lines()
+          .flat_map(|line| parse_line(file_name, line))
+          .collect()
+}
+
+fn parse_line(file_name: &str, line: &str) -> Vec<Error> {
+    // The compiler sometimes intermingles non-JSON stuff into the
+    // output.  This hack just skips over such lines. Yuck.
+    if line.chars().next() == Some('{') {
+        match json::decode::<Diagnostic>(line) {
+            Ok(diagnostic) => {
+                let mut expected_errors = vec![];
+                push_expected_errors(&mut expected_errors, &diagnostic, file_name);
+                expected_errors
+            }
+            Err(error) => {
+                panic!("failed to decode compiler output as json: `{}`", error);
+            }
+        }
+    } else {
+        vec![]
+    }
+}
+
+fn push_expected_errors(expected_errors: &mut Vec<Error>,
+                        diagnostic: &Diagnostic,
+                        file_name: &str) {
+    // We only consider messages pertaining to the current file.
+    let matching_spans = || {
+        diagnostic.spans.iter().filter(|span| span.file_name == file_name)
+    };
+
+    // We break the output into multiple lines, and then append the
+    // [E123] to every line in the output. This may be overkill.  The
+    // intention was to match existing tests that do things like "//|
+    // found `i32` [E123]" and expect to match that somewhere, and yet
+    // also ensure that `//~ ERROR E123` *always* works. The
+    // assumption is that these multi-line error messages are on their
+    // way out anyhow.
+    let with_code = |span: &DiagnosticSpan, text: &str| {
+        match diagnostic.code {
+            Some(ref code) =>
+                // FIXME(#33000) -- it'd be better to use a dedicated
+                // UI harness than to include the line/col number like
+                // this, but some current tests rely on it.
+                //
+                // Note: Do NOT include the filename. These can easily
+                // cause false matches where the expected message
+                // appears in the filename, and hence the message
+                // changes but the test still passes.
+                format!("{}:{}: {}:{}: {} [{}]",
+                        span.line_start, span.column_start,
+                        span.line_end, span.column_end,
+                        text, code.code.clone()),
+            None =>
+                // FIXME(#33000) -- it'd be better to use a dedicated UI harness
+                format!("{}:{}: {}:{}: {}",
+                        span.line_start, span.column_start,
+                        span.line_end, span.column_end,
+                        text),
+        }
+    };
+
+    // Convert multi-line messages into multiple expected
+    // errors. We expect to replace these with something
+    // more structured shortly anyhow.
+    let mut message_lines = diagnostic.message.lines();
+    if let Some(first_line) = message_lines.next() {
+        for span in matching_spans() {
+            let msg = with_code(span, first_line);
+            let kind = ErrorKind::from_str(&diagnostic.level).ok();
+            expected_errors.push(
+                Error {
+                    line_num: span.line_start,
+                    kind: kind,
+                    msg: msg,
+                }
+            );
+        }
+    }
+    for next_line in message_lines {
+        for span in matching_spans() {
+            expected_errors.push(
+                Error {
+                    line_num: span.line_start,
+                    kind: None,
+                    msg: with_code(span, next_line),
+                }
+            );
+        }
+    }
+
+    // If the message has a suggestion, register that.
+    if let Some(ref rendered) = diagnostic.rendered {
+        let start_line = matching_spans().map(|s| s.line_start).min().expect("\
+            every suggestion should have at least one span");
+        for (index, line) in rendered.lines().enumerate() {
+            expected_errors.push(
+                Error {
+                    line_num: start_line + index,
+                    kind: Some(ErrorKind::Suggestion),
+                    msg: line.to_string()
+                }
+            );
+        }
+    }
+
+    // Add notes for the backtrace
+    for span in matching_spans() {
+        for frame in &span.expansion {
+            push_backtrace(expected_errors,
+                           frame,
+                           file_name);
+        }
+    }
+
+    // Flatten out the children.
+    for child in &diagnostic.children {
+        push_expected_errors(expected_errors, child, file_name);
+    }
+}
+
+fn push_backtrace(expected_errors: &mut Vec<Error>,
+                  expansion: &DiagnosticSpanMacroExpansion,
+                  file_name: &str) {
+    if expansion.span.file_name == file_name {
+        expected_errors.push(
+            Error {
+                line_num: expansion.span.line_start,
+                kind: Some(ErrorKind::Note),
+                msg: format!("in this expansion of {}", expansion.macro_decl_name),
+            }
+        );
+    }
+
+    for previous_expansion in &expansion.span.expansion {
+        push_backtrace(expected_errors, previous_expansion, file_name);
+    }
+}