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] |
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 | |
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 | |
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 | |
161 | 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 | |
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 | |