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
7extern crate rustc_ast;
8extern crate rustc_data_structures;
9extern crate rustc_error_messages;
10extern crate rustc_errors;
11extern crate rustc_expand;
12extern crate rustc_parse as parse;
13extern crate rustc_session;
14extern crate rustc_span;
15
16use crate::common::eq::SpanlessEq;
17use quote::quote;
18use rayon::iter::{IntoParallelIterator, ParallelIterator};
19use rustc_ast::ast::{
20 AngleBracketedArg, AngleBracketedArgs, Crate, GenericArg, GenericParamKind, Generics,
21 WhereClause,
22};
23use rustc_ast::mut_visit::{self, MutVisitor};
24use rustc_error_messages::{DiagnosticMessage, LazyFallbackBundle};
25use rustc_errors::{translation, Diagnostic, PResult};
26use rustc_session::parse::ParseSess;
27use rustc_span::source_map::FilePathMapping;
28use rustc_span::FileName;
29use std::fs;
30use std::panic;
31use std::path::Path;
32use std::process;
33use std::sync::atomic::{AtomicUsize, Ordering};
34use std::time::Instant;
35use walkdir::{DirEntry, WalkDir};
36
37#[macro_use]
38mod macros;
39
40#[allow(dead_code)]
41mod common;
42
43mod repo;
44
45#[test]
46fn 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
76fn 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
154fn 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
161fn 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
197fn 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