240 lines
8.0 KiB
Rust
240 lines
8.0 KiB
Rust
|
#![cfg(not(syn_disable_nightly_tests))]
|
||
|
#![cfg(not(miri))]
|
||
|
#![recursion_limit = "1024"]
|
||
|
#![feature(rustc_private)]
|
||
|
#![allow(
|
||
|
clippy::blocks_in_conditions,
|
||
|
clippy::manual_assert,
|
||
|
clippy::manual_let_else,
|
||
|
clippy::match_like_matches_macro,
|
||
|
clippy::uninlined_format_args
|
||
|
)]
|
||
|
|
||
|
extern crate rustc_ast;
|
||
|
extern crate rustc_ast_pretty;
|
||
|
extern crate rustc_data_structures;
|
||
|
extern crate rustc_driver;
|
||
|
extern crate rustc_error_messages;
|
||
|
extern crate rustc_errors;
|
||
|
extern crate rustc_expand;
|
||
|
extern crate rustc_parse as parse;
|
||
|
extern crate rustc_session;
|
||
|
extern crate rustc_span;
|
||
|
|
||
|
use crate::common::eq::SpanlessEq;
|
||
|
use quote::quote;
|
||
|
use rustc_ast::ast::{
|
||
|
AngleBracketedArg, AngleBracketedArgs, Crate, GenericArg, GenericParamKind, Generics,
|
||
|
WhereClause,
|
||
|
};
|
||
|
use rustc_ast::mut_visit::{self, MutVisitor};
|
||
|
use rustc_ast_pretty::pprust;
|
||
|
use rustc_error_messages::{DiagnosticMessage, LazyFallbackBundle};
|
||
|
use rustc_errors::{translation, Diagnostic, PResult};
|
||
|
use rustc_session::parse::ParseSess;
|
||
|
use rustc_span::source_map::FilePathMapping;
|
||
|
use rustc_span::FileName;
|
||
|
use std::borrow::Cow;
|
||
|
use std::fs;
|
||
|
use std::panic;
|
||
|
use std::path::Path;
|
||
|
use std::process;
|
||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||
|
use std::time::Instant;
|
||
|
|
||
|
#[macro_use]
|
||
|
mod macros;
|
||
|
|
||
|
#[allow(dead_code)]
|
||
|
mod common;
|
||
|
|
||
|
mod repo;
|
||
|
|
||
|
#[test]
|
||
|
fn test_round_trip() {
|
||
|
common::rayon_init();
|
||
|
repo::clone_rust();
|
||
|
let abort_after = common::abort_after();
|
||
|
if abort_after == 0 {
|
||
|
panic!("Skipping all round_trip tests");
|
||
|
}
|
||
|
|
||
|
let failed = AtomicUsize::new(0);
|
||
|
|
||
|
repo::for_each_rust_file(|path| test(path, &failed, abort_after));
|
||
|
|
||
|
let failed = failed.load(Ordering::Relaxed);
|
||
|
if failed > 0 {
|
||
|
panic!("{} failures", failed);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn test(path: &Path, failed: &AtomicUsize, abort_after: usize) {
|
||
|
let content = fs::read_to_string(path).unwrap();
|
||
|
|
||
|
let start = Instant::now();
|
||
|
let (krate, elapsed) = match syn::parse_file(&content) {
|
||
|
Ok(krate) => (krate, start.elapsed()),
|
||
|
Err(msg) => {
|
||
|
errorf!("=== {}: syn failed to parse\n{:?}\n", path.display(), msg);
|
||
|
let prev_failed = failed.fetch_add(1, Ordering::Relaxed);
|
||
|
if prev_failed + 1 >= abort_after {
|
||
|
process::exit(1);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
};
|
||
|
let back = quote!(#krate).to_string();
|
||
|
let edition = repo::edition(path).parse().unwrap();
|
||
|
|
||
|
rustc_span::create_session_if_not_set_then(edition, |_| {
|
||
|
let equal = match panic::catch_unwind(|| {
|
||
|
let locale_resources = rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec();
|
||
|
let file_path_mapping = FilePathMapping::empty();
|
||
|
let sess = ParseSess::new(locale_resources, file_path_mapping);
|
||
|
let before = match librustc_parse(content, &sess) {
|
||
|
Ok(before) => before,
|
||
|
Err(diagnostic) => {
|
||
|
errorf!(
|
||
|
"=== {}: ignore - librustc failed to parse original content: {}\n",
|
||
|
path.display(),
|
||
|
translate_message(&diagnostic),
|
||
|
);
|
||
|
diagnostic.cancel();
|
||
|
return Err(true);
|
||
|
}
|
||
|
};
|
||
|
let after = match librustc_parse(back, &sess) {
|
||
|
Ok(after) => after,
|
||
|
Err(mut diagnostic) => {
|
||
|
errorf!("=== {}: librustc failed to parse", path.display());
|
||
|
diagnostic.emit();
|
||
|
return Err(false);
|
||
|
}
|
||
|
};
|
||
|
Ok((before, after))
|
||
|
}) {
|
||
|
Err(_) => {
|
||
|
errorf!("=== {}: ignoring librustc panic\n", path.display());
|
||
|
true
|
||
|
}
|
||
|
Ok(Err(equal)) => equal,
|
||
|
Ok(Ok((mut before, mut after))) => {
|
||
|
normalize(&mut before);
|
||
|
normalize(&mut after);
|
||
|
if SpanlessEq::eq(&before, &after) {
|
||
|
errorf!(
|
||
|
"=== {}: pass in {}ms\n",
|
||
|
path.display(),
|
||
|
elapsed.as_secs() * 1000 + u64::from(elapsed.subsec_nanos()) / 1_000_000
|
||
|
);
|
||
|
true
|
||
|
} else {
|
||
|
errorf!(
|
||
|
"=== {}: FAIL\n{}\n!=\n{}\n",
|
||
|
path.display(),
|
||
|
pprust::crate_to_string_for_macros(&before),
|
||
|
pprust::crate_to_string_for_macros(&after),
|
||
|
);
|
||
|
false
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
if !equal {
|
||
|
let prev_failed = failed.fetch_add(1, Ordering::Relaxed);
|
||
|
if prev_failed + 1 >= abort_after {
|
||
|
process::exit(1);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
fn librustc_parse(content: String, sess: &ParseSess) -> PResult<Crate> {
|
||
|
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||
|
let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
|
||
|
let name = FileName::Custom(format!("test_round_trip{}", counter));
|
||
|
parse::parse_crate_from_source_str(name, content, sess)
|
||
|
}
|
||
|
|
||
|
fn translate_message(diagnostic: &Diagnostic) -> Cow<'static, str> {
|
||
|
thread_local! {
|
||
|
static FLUENT_BUNDLE: LazyFallbackBundle = {
|
||
|
let locale_resources = rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec();
|
||
|
let with_directionality_markers = false;
|
||
|
rustc_error_messages::fallback_fluent_bundle(locale_resources, with_directionality_markers)
|
||
|
};
|
||
|
}
|
||
|
|
||
|
let message = &diagnostic.messages[0].0;
|
||
|
let args = translation::to_fluent_args(diagnostic.args());
|
||
|
|
||
|
let (identifier, attr) = match message {
|
||
|
DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => return msg.clone(),
|
||
|
DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
|
||
|
};
|
||
|
|
||
|
FLUENT_BUNDLE.with(|fluent_bundle| {
|
||
|
let message = fluent_bundle
|
||
|
.get_message(identifier)
|
||
|
.expect("missing diagnostic in fluent bundle");
|
||
|
let value = match attr {
|
||
|
Some(attr) => message
|
||
|
.get_attribute(attr)
|
||
|
.expect("missing attribute in fluent message")
|
||
|
.value(),
|
||
|
None => message.value().expect("missing value in fluent message"),
|
||
|
};
|
||
|
|
||
|
let mut err = Vec::new();
|
||
|
let translated = fluent_bundle.format_pattern(value, Some(&args), &mut err);
|
||
|
assert!(err.is_empty());
|
||
|
Cow::Owned(translated.into_owned())
|
||
|
})
|
||
|
}
|
||
|
|
||
|
fn normalize(krate: &mut Crate) {
|
||
|
struct NormalizeVisitor;
|
||
|
|
||
|
impl MutVisitor for NormalizeVisitor {
|
||
|
fn visit_angle_bracketed_parameter_data(&mut self, e: &mut AngleBracketedArgs) {
|
||
|
#[derive(Ord, PartialOrd, Eq, PartialEq)]
|
||
|
enum Group {
|
||
|
Lifetimes,
|
||
|
TypesAndConsts,
|
||
|
Constraints,
|
||
|
}
|
||
|
e.args.sort_by_key(|arg| match arg {
|
||
|
AngleBracketedArg::Arg(arg) => match arg {
|
||
|
GenericArg::Lifetime(_) => Group::Lifetimes,
|
||
|
GenericArg::Type(_) | GenericArg::Const(_) => Group::TypesAndConsts,
|
||
|
},
|
||
|
AngleBracketedArg::Constraint(_) => Group::Constraints,
|
||
|
});
|
||
|
mut_visit::noop_visit_angle_bracketed_parameter_data(e, self);
|
||
|
}
|
||
|
|
||
|
fn visit_generics(&mut self, e: &mut Generics) {
|
||
|
#[derive(Ord, PartialOrd, Eq, PartialEq)]
|
||
|
enum Group {
|
||
|
Lifetimes,
|
||
|
TypesAndConsts,
|
||
|
}
|
||
|
e.params.sort_by_key(|param| match param.kind {
|
||
|
GenericParamKind::Lifetime => Group::Lifetimes,
|
||
|
GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => {
|
||
|
Group::TypesAndConsts
|
||
|
}
|
||
|
});
|
||
|
mut_visit::noop_visit_generics(e, self);
|
||
|
}
|
||
|
|
||
|
fn visit_where_clause(&mut self, e: &mut WhereClause) {
|
||
|
if e.predicates.is_empty() {
|
||
|
e.has_where_token = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
NormalizeVisitor.visit_crate(krate);
|
||
|
}
|