| 1 | #![cfg (not(syn_disable_nightly_tests))] |
| 2 | #![cfg (not(miri))] |
| 3 | #![recursion_limit = "1024" ] |
| 4 | #![feature (rustc_private)] |
| 5 | #![allow ( |
| 6 | clippy::manual_assert, |
| 7 | clippy::manual_let_else, |
| 8 | clippy::match_like_matches_macro, |
| 9 | clippy::uninlined_format_args |
| 10 | )] |
| 11 | |
| 12 | extern crate rustc_ast; |
| 13 | extern crate rustc_ast_pretty; |
| 14 | extern crate rustc_data_structures; |
| 15 | extern crate rustc_driver; |
| 16 | extern crate rustc_error_messages; |
| 17 | extern crate rustc_errors; |
| 18 | extern crate rustc_expand; |
| 19 | extern crate rustc_parse as parse; |
| 20 | extern crate rustc_session; |
| 21 | extern crate rustc_span; |
| 22 | |
| 23 | use crate::common::eq::SpanlessEq; |
| 24 | use quote::quote; |
| 25 | use rustc_ast::ast::{ |
| 26 | AngleBracketedArg, AngleBracketedArgs, Crate, GenericArg, GenericParamKind, Generics, |
| 27 | WhereClause, |
| 28 | }; |
| 29 | use rustc_ast::mut_visit::{self, MutVisitor}; |
| 30 | use rustc_ast_pretty::pprust; |
| 31 | use rustc_error_messages::{DiagnosticMessage, LazyFallbackBundle}; |
| 32 | use rustc_errors::{translation, Diagnostic, PResult}; |
| 33 | use rustc_session::parse::ParseSess; |
| 34 | use rustc_span::source_map::FilePathMapping; |
| 35 | use rustc_span::FileName; |
| 36 | use std::borrow::Cow; |
| 37 | use std::fs; |
| 38 | use std::panic; |
| 39 | use std::path::Path; |
| 40 | use std::process; |
| 41 | use std::sync::atomic::{AtomicUsize, Ordering}; |
| 42 | use std::time::Instant; |
| 43 | |
| 44 | #[macro_use ] |
| 45 | mod macros; |
| 46 | |
| 47 | #[allow (dead_code)] |
| 48 | mod common; |
| 49 | |
| 50 | mod repo; |
| 51 | |
| 52 | #[test] |
| 53 | fn test_round_trip() { |
| 54 | common::rayon_init(); |
| 55 | repo::clone_rust(); |
| 56 | let abort_after = common::abort_after(); |
| 57 | if abort_after == 0 { |
| 58 | panic!("Skipping all round_trip tests" ); |
| 59 | } |
| 60 | |
| 61 | let failed = AtomicUsize::new(0); |
| 62 | |
| 63 | repo::for_each_rust_file(|path| test(path, &failed, abort_after)); |
| 64 | |
| 65 | let failed = failed.load(Ordering::Relaxed); |
| 66 | if failed > 0 { |
| 67 | panic!("{} failures" , failed); |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | fn test(path: &Path, failed: &AtomicUsize, abort_after: usize) { |
| 72 | let content = fs::read_to_string(path).unwrap(); |
| 73 | |
| 74 | let start = Instant::now(); |
| 75 | let (krate, elapsed) = match syn::parse_file(&content) { |
| 76 | Ok(krate) => (krate, start.elapsed()), |
| 77 | Err(msg) => { |
| 78 | errorf!("=== {}: syn failed to parse \n{:?} \n" , path.display(), msg); |
| 79 | let prev_failed = failed.fetch_add(1, Ordering::Relaxed); |
| 80 | if prev_failed + 1 >= abort_after { |
| 81 | process::exit(1); |
| 82 | } |
| 83 | return; |
| 84 | } |
| 85 | }; |
| 86 | let back = quote!(#krate).to_string(); |
| 87 | let edition = repo::edition(path).parse().unwrap(); |
| 88 | |
| 89 | rustc_span::create_session_if_not_set_then(edition, |_| { |
| 90 | let equal = match panic::catch_unwind(|| { |
| 91 | let locale_resources = rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(); |
| 92 | let file_path_mapping = FilePathMapping::empty(); |
| 93 | let sess = ParseSess::new(locale_resources, file_path_mapping); |
| 94 | let before = match librustc_parse(content, &sess) { |
| 95 | Ok(before) => before, |
| 96 | Err(diagnostic) => { |
| 97 | errorf!( |
| 98 | "=== {}: ignore - librustc failed to parse original content: {} \n" , |
| 99 | path.display(), |
| 100 | translate_message(&diagnostic), |
| 101 | ); |
| 102 | diagnostic.cancel(); |
| 103 | return Err(true); |
| 104 | } |
| 105 | }; |
| 106 | let after = match librustc_parse(back, &sess) { |
| 107 | Ok(after) => after, |
| 108 | Err(mut diagnostic) => { |
| 109 | errorf!("=== {}: librustc failed to parse" , path.display()); |
| 110 | diagnostic.emit(); |
| 111 | return Err(false); |
| 112 | } |
| 113 | }; |
| 114 | Ok((before, after)) |
| 115 | }) { |
| 116 | Err(_) => { |
| 117 | errorf!("=== {}: ignoring librustc panic \n" , path.display()); |
| 118 | true |
| 119 | } |
| 120 | Ok(Err(equal)) => equal, |
| 121 | Ok(Ok((mut before, mut after))) => { |
| 122 | normalize(&mut before); |
| 123 | normalize(&mut after); |
| 124 | if SpanlessEq::eq(&before, &after) { |
| 125 | errorf!( |
| 126 | "=== {}: pass in {}ms \n" , |
| 127 | path.display(), |
| 128 | elapsed.as_secs() * 1000 + u64::from(elapsed.subsec_nanos()) / 1_000_000 |
| 129 | ); |
| 130 | true |
| 131 | } else { |
| 132 | errorf!( |
| 133 | "=== {}: FAIL \n{} \n!= \n{} \n" , |
| 134 | path.display(), |
| 135 | pprust::crate_to_string_for_macros(&before), |
| 136 | pprust::crate_to_string_for_macros(&after), |
| 137 | ); |
| 138 | false |
| 139 | } |
| 140 | } |
| 141 | }; |
| 142 | if !equal { |
| 143 | let prev_failed = failed.fetch_add(1, Ordering::Relaxed); |
| 144 | if prev_failed + 1 >= abort_after { |
| 145 | process::exit(1); |
| 146 | } |
| 147 | } |
| 148 | }); |
| 149 | } |
| 150 | |
| 151 | fn librustc_parse(content: String, sess: &ParseSess) -> PResult<Crate> { |
| 152 | static COUNTER: AtomicUsize = AtomicUsize::new(0); |
| 153 | let counter = COUNTER.fetch_add(1, Ordering::Relaxed); |
| 154 | let name = FileName::Custom(format!("test_round_trip{}" , counter)); |
| 155 | parse::parse_crate_from_source_str(name, content, sess) |
| 156 | } |
| 157 | |
| 158 | fn translate_message(diagnostic: &Diagnostic) -> Cow<'static, str> { |
| 159 | thread_local! { |
| 160 | static FLUENT_BUNDLE: LazyFallbackBundle = { |
| 161 | let locale_resources = rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(); |
| 162 | let with_directionality_markers = false; |
| 163 | rustc_error_messages::fallback_fluent_bundle(locale_resources, with_directionality_markers) |
| 164 | }; |
| 165 | } |
| 166 | |
| 167 | let message = &diagnostic.message[0].0; |
| 168 | let args = translation::to_fluent_args(diagnostic.args()); |
| 169 | |
| 170 | let (identifier, attr) = match message { |
| 171 | DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => return msg.clone(), |
| 172 | DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr), |
| 173 | }; |
| 174 | |
| 175 | FLUENT_BUNDLE.with(|fluent_bundle| { |
| 176 | let message = fluent_bundle |
| 177 | .get_message(identifier) |
| 178 | .expect("missing diagnostic in fluent bundle" ); |
| 179 | let value = match attr { |
| 180 | Some(attr) => message |
| 181 | .get_attribute(attr) |
| 182 | .expect("missing attribute in fluent message" ) |
| 183 | .value(), |
| 184 | None => message.value().expect("missing value in fluent message" ), |
| 185 | }; |
| 186 | |
| 187 | let mut err = Vec::new(); |
| 188 | let translated = fluent_bundle.format_pattern(value, Some(&args), &mut err); |
| 189 | assert!(err.is_empty()); |
| 190 | Cow::Owned(translated.into_owned()) |
| 191 | }) |
| 192 | } |
| 193 | |
| 194 | fn normalize(krate: &mut Crate) { |
| 195 | struct NormalizeVisitor; |
| 196 | |
| 197 | impl MutVisitor for NormalizeVisitor { |
| 198 | fn visit_angle_bracketed_parameter_data(&mut self, e: &mut AngleBracketedArgs) { |
| 199 | #[derive(Ord, PartialOrd, Eq, PartialEq)] |
| 200 | enum Group { |
| 201 | Lifetimes, |
| 202 | TypesAndConsts, |
| 203 | Constraints, |
| 204 | } |
| 205 | e.args.sort_by_key(|arg| match arg { |
| 206 | AngleBracketedArg::Arg(arg) => match arg { |
| 207 | GenericArg::Lifetime(_) => Group::Lifetimes, |
| 208 | GenericArg::Type(_) | GenericArg::Const(_) => Group::TypesAndConsts, |
| 209 | }, |
| 210 | AngleBracketedArg::Constraint(_) => Group::Constraints, |
| 211 | }); |
| 212 | mut_visit::noop_visit_angle_bracketed_parameter_data(e, self); |
| 213 | } |
| 214 | |
| 215 | fn visit_generics(&mut self, e: &mut Generics) { |
| 216 | #[derive(Ord, PartialOrd, Eq, PartialEq)] |
| 217 | enum Group { |
| 218 | Lifetimes, |
| 219 | TypesAndConsts, |
| 220 | } |
| 221 | e.params.sort_by_key(|param| match param.kind { |
| 222 | GenericParamKind::Lifetime => Group::Lifetimes, |
| 223 | GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => { |
| 224 | Group::TypesAndConsts |
| 225 | } |
| 226 | }); |
| 227 | mut_visit::noop_visit_generics(e, self); |
| 228 | } |
| 229 | |
| 230 | fn visit_where_clause(&mut self, e: &mut WhereClause) { |
| 231 | if e.predicates.is_empty() { |
| 232 | e.has_where_token = false; |
| 233 | } |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | NormalizeVisitor.visit_crate(krate); |
| 238 | } |
| 239 | |