summaryrefslogtreecommitdiffstats
path: root/vendor/ui_test/src/rustc_stderr.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/ui_test/src/rustc_stderr.rs')
-rw-r--r--vendor/ui_test/src/rustc_stderr.rs160
1 files changed, 160 insertions, 0 deletions
diff --git a/vendor/ui_test/src/rustc_stderr.rs b/vendor/ui_test/src/rustc_stderr.rs
new file mode 100644
index 000000000..64e5928c5
--- /dev/null
+++ b/vendor/ui_test/src/rustc_stderr.rs
@@ -0,0 +1,160 @@
+use std::path::{Path, PathBuf};
+
+use bstr::ByteSlice;
+use regex::Regex;
+
+#[derive(serde::Deserialize, Debug)]
+struct RustcMessage {
+ rendered: Option<String>,
+ spans: Vec<Span>,
+ level: String,
+ message: String,
+ children: Vec<RustcMessage>,
+}
+
+#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+pub(crate) enum Level {
+ Ice = 5,
+ Error = 4,
+ Warn = 3,
+ Help = 2,
+ Note = 1,
+ /// Only used for "For more information about this error, try `rustc --explain EXXXX`".
+ FailureNote = 0,
+}
+
+#[derive(Debug)]
+/// A diagnostic message.
+pub struct Message {
+ pub(crate) level: Level,
+ pub(crate) message: String,
+}
+
+/// Information about macro expansion.
+#[derive(serde::Deserialize, Debug)]
+struct Expansion {
+ span: Span,
+}
+
+#[derive(serde::Deserialize, Debug)]
+struct Span {
+ line_start: usize,
+ file_name: PathBuf,
+ is_primary: bool,
+ expansion: Option<Box<Expansion>>,
+}
+
+impl std::str::FromStr for Level {
+ type Err = String;
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "ERROR" | "error" => Ok(Self::Error),
+ "WARN" | "warning" => Ok(Self::Warn),
+ "HELP" | "help" => Ok(Self::Help),
+ "NOTE" | "note" => Ok(Self::Note),
+ "failure-note" => Ok(Self::FailureNote),
+ "error: internal compiler error" => Ok(Self::Ice),
+ _ => Err(format!("unknown level `{s}`")),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct Diagnostics {
+ /// Rendered and concatenated version of all diagnostics.
+ /// This is equivalent to non-json diagnostics.
+ pub rendered: Vec<u8>,
+ /// Per line, a list of messages for that line.
+ pub messages: Vec<Vec<Message>>,
+ /// Messages not on any line (usually because they are from libstd)
+ pub messages_from_unknown_file_or_line: Vec<Message>,
+}
+
+impl RustcMessage {
+ fn line(&self, file: &Path) -> Option<usize> {
+ let span = |primary| self.spans.iter().find_map(|span| span.line(file, primary));
+ span(true).or_else(|| span(false))
+ }
+
+ /// Put the message and its children into the line-indexed list.
+ fn insert_recursive(
+ self,
+ file: &Path,
+ messages: &mut Vec<Vec<Message>>,
+ messages_from_unknown_file_or_line: &mut Vec<Message>,
+ line: Option<usize>,
+ ) {
+ let line = self.line(file).or(line);
+ let msg = Message {
+ level: self.level.parse().unwrap(),
+ message: self.message,
+ };
+ if let Some(line) = line {
+ if messages.len() <= line {
+ messages.resize_with(line + 1, Vec::new);
+ }
+ messages[line].push(msg);
+ // All other messages go into the general bin, unless they are specifically of the
+ // "aborting due to X previous errors" variety, as we never want to match those. They
+ // only count the number of errors and provide no useful information about the tests.
+ } else if !(msg.message.starts_with("aborting due to")
+ && msg.message.contains("previous error"))
+ {
+ messages_from_unknown_file_or_line.push(msg);
+ }
+ for child in self.children {
+ child.insert_recursive(file, messages, messages_from_unknown_file_or_line, line)
+ }
+ }
+}
+
+impl Span {
+ /// Returns the most expanded line number *in the given file*, if possible.
+ fn line(&self, file: &Path, primary: bool) -> Option<usize> {
+ if let Some(exp) = &self.expansion {
+ if let Some(line) = exp.span.line(file, primary && !self.is_primary) {
+ return Some(line);
+ }
+ }
+ ((!primary || self.is_primary) && self.file_name == file).then_some(self.line_start)
+ }
+}
+
+pub(crate) fn filter_annotations_from_rendered(rendered: &str) -> std::borrow::Cow<'_, str> {
+ let annotations = Regex::new(r" *//(\[[a-z,]+\])?~.*").unwrap();
+ annotations.replace_all(rendered, "")
+}
+
+pub(crate) fn process(file: &Path, stderr: &[u8]) -> Diagnostics {
+ let mut rendered = Vec::new();
+ let mut messages = vec![];
+ let mut messages_from_unknown_file_or_line = vec![];
+ for (line_number, line) in stderr.lines_with_terminator().enumerate() {
+ if line.starts_with_str(b"{") {
+ match serde_json::from_slice::<RustcMessage>(line) {
+ Ok(msg) => {
+ rendered.extend(
+ filter_annotations_from_rendered(msg.rendered.as_ref().unwrap()).as_bytes(),
+ );
+ msg.insert_recursive(
+ file,
+ &mut messages,
+ &mut messages_from_unknown_file_or_line,
+ None,
+ );
+ }
+ Err(err) => {
+ panic!("failed to parse rustc JSON output at line {line_number}: {err}")
+ }
+ }
+ } else {
+ // FIXME: do we want to throw interpreter stderr into a separate file?
+ rendered.extend(line);
+ }
+ }
+ Diagnostics {
+ rendered,
+ messages,
+ messages_from_unknown_file_or_line,
+ }
+}