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