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