1use std::convert::identity;
4use std::thread::Builder;
5use std::time::{Duration, Instant};
6use std::{cell::RefCell, fs::read_to_string, panic::AssertUnwindSafe, path::PathBuf};
7
8use hir::{ChangeWithProcMacros, Crate};
9use ide::{AnalysisHost, DiagnosticCode, DiagnosticsConfig};
10use ide_db::base_db;
11use itertools::Either;
12use profile::StopWatch;
13use project_model::toolchain_info::{QueryConfig, target_data};
14use project_model::{
15 CargoConfig, ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, RustLibSource,
16 RustSourceWorkspaceConfig, Sysroot,
17};
18
19use load_cargo::{LoadCargoConfig, ProcMacroServerChoice, load_workspace};
20use rustc_hash::FxHashMap;
21use vfs::{AbsPathBuf, FileId};
22use walkdir::WalkDir;
23
24use crate::cli::{Result, flags, report_metric};
25
26struct Tester {
27 host: AnalysisHost,
28 root_file: FileId,
29 pass_count: u64,
30 ignore_count: u64,
31 fail_count: u64,
32 stopwatch: StopWatch,
33}
34
35fn string_to_diagnostic_code_leaky(code: &str) -> DiagnosticCode {
36 thread_local! {
37 static LEAK_STORE: RefCell<FxHashMap<String, DiagnosticCode>> = RefCell::new(FxHashMap::default());
38 }
39 LEAK_STORE.with_borrow_mut(|s| match s.get(code) {
40 Some(c) => *c,
41 None => {
42 let v = DiagnosticCode::RustcHardError(format!("E{code}").leak());
43 s.insert(code.to_owned(), v);
44 v
45 }
46 })
47}
48
49fn detect_errors_from_rustc_stderr_file(p: PathBuf) -> FxHashMap<DiagnosticCode, usize> {
50 let text = read_to_string(p).unwrap();
51 let mut result = FxHashMap::default();
52 {
53 let mut text = &*text;
54 while let Some(p) = text.find("error[E") {
55 text = &text[p + 7..];
56 let code = string_to_diagnostic_code_leaky(&text[..4]);
57 *result.entry(code).or_insert(0) += 1;
58 }
59 }
60 result
61}
62
63impl Tester {
64 fn new() -> Result<Self> {
65 let mut path = AbsPathBuf::assert_utf8(std::env::temp_dir());
66 path.push("ra-rustc-test");
67 std::fs::create_dir_all(&path)?;
68 let tmp_file = path.join("ra-rustc-test.rs");
69 std::fs::write(&tmp_file, "")?;
70 let cargo_config = CargoConfig {
71 sysroot: Some(RustLibSource::Discover),
72 all_targets: true,
73 set_test: true,
74 ..Default::default()
75 };
76
77 let mut sysroot = Sysroot::discover(tmp_file.parent().unwrap(), &cargo_config.extra_env);
78 let loaded_sysroot =
79 sysroot.load_workspace(&RustSourceWorkspaceConfig::default_cargo(), false, &|_| ());
80 if let Some(loaded_sysroot) = loaded_sysroot {
81 sysroot.set_workspace(loaded_sysroot);
82 }
83
84 let target_data = target_data::get(
85 QueryConfig::Rustc(&sysroot, tmp_file.parent().unwrap().as_ref()),
86 None,
87 &cargo_config.extra_env,
88 );
89
90 let workspace = ProjectWorkspace {
91 kind: ProjectWorkspaceKind::DetachedFile {
92 file: ManifestPath::try_from(tmp_file).unwrap(),
93 cargo: None,
94 },
95 sysroot,
96 rustc_cfg: vec![],
97 toolchain: None,
98 target: target_data.map_err(|it| it.to_string().into()),
99 cfg_overrides: Default::default(),
100 extra_includes: vec![],
101 set_test: true,
102 };
103 let load_cargo_config = LoadCargoConfig {
104 load_out_dirs_from_check: false,
105 with_proc_macro_server: ProcMacroServerChoice::Sysroot,
106 prefill_caches: false,
107 num_worker_threads: 1,
108 proc_macro_processes: 1,
109 };
110 let (db, _vfs, _proc_macro) =
111 load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?;
112 let host = AnalysisHost::with_database(db);
113 let db = host.raw_database();
114 let krates = Crate::all(db);
115 let root_crate = krates.iter().cloned().find(|krate| krate.origin(db).is_local()).unwrap();
116 let root_file = root_crate.root_file(db);
117 Ok(Self {
118 host,
119 root_file,
120 pass_count: 0,
121 ignore_count: 0,
122 fail_count: 0,
123 stopwatch: StopWatch::start(),
124 })
125 }
126
127 fn test(&mut self, p: PathBuf) {
128 println!("{}", p.display());
129 if p.parent().unwrap().file_name().unwrap() == "auxiliary" {
130 return;
132 }
133 if IGNORED_TESTS.iter().any(|ig| p.file_name().is_some_and(|x| x == *ig)) {
134 println!("{p:?} IGNORE");
135 self.ignore_count += 1;
136 return;
137 }
138 let stderr_path = p.with_extension("stderr");
139 let expected = if stderr_path.exists() {
140 detect_errors_from_rustc_stderr_file(stderr_path)
141 } else {
142 FxHashMap::default()
143 };
144 let text = read_to_string(&p).unwrap();
145 let mut change = ChangeWithProcMacros::default();
146 let mut ignore_test = text.contains("#![feature");
148 ignore_test |= text.contains("// aux-build:") || text.contains("// aux-crate:");
150 ignore_test |= text.contains("mod ");
152 ignore_test |= text.contains("extern crate proc_macro");
154 let should_have_no_error = text.contains("// check-pass")
155 || text.contains("// build-pass")
156 || text.contains("// run-pass");
157 change.change_file(self.root_file, Some(text));
158 self.host.apply_change(change);
159 let diagnostic_config = DiagnosticsConfig::test_sample();
160
161 let res = std::thread::scope(|s| {
162 let worker = Builder::new()
163 .stack_size(40 * 1024 * 1024)
164 .spawn_scoped(s, {
165 let diagnostic_config = &diagnostic_config;
166 let main = std::thread::current();
167 let analysis = self.host.analysis();
168 let root_file = self.root_file;
169 move || {
170 let res = std::panic::catch_unwind(AssertUnwindSafe(move || {
171 analysis.full_diagnostics(
172 diagnostic_config,
173 ide::AssistResolveStrategy::None,
174 root_file,
175 )
176 }));
177 main.unpark();
178 res
179 }
180 })
181 .unwrap();
182
183 let timeout = Duration::from_secs(5);
184 let now = Instant::now();
185 while now.elapsed() <= timeout && !worker.is_finished() {
186 std::thread::park_timeout(timeout - now.elapsed());
187 }
188
189 if !worker.is_finished() {
190 self.host.trigger_garbage_collection();
192 }
193 worker.join().and_then(identity)
194 });
195 let mut actual = FxHashMap::default();
196 let panicked = match res {
197 Err(e) => Some(Either::Left(e)),
198 Ok(Ok(diags)) => {
199 for diag in diags {
200 if !matches!(diag.code, DiagnosticCode::RustcHardError(_)) {
201 continue;
202 }
203 if !should_have_no_error && !SUPPORTED_DIAGNOSTICS.contains(&diag.code) {
204 continue;
205 }
206 *actual.entry(diag.code).or_insert(0) += 1;
207 }
208 None
209 }
210 Ok(Err(e)) => Some(Either::Right(e)),
211 };
212 ignore_test |= expected.keys().any(|k| !SUPPORTED_DIAGNOSTICS.contains(k));
214 if ignore_test {
215 println!("{p:?} IGNORE");
216 self.ignore_count += 1;
217 } else if let Some(panic) = panicked {
218 match panic {
219 Either::Left(panic) => {
220 if let Some(msg) = panic
221 .downcast_ref::<String>()
222 .map(String::as_str)
223 .or_else(|| panic.downcast_ref::<&str>().copied())
224 {
225 println!("{msg:?} ")
226 }
227 println!("{p:?} PANIC");
228 }
229 Either::Right(_) => println!("{p:?} CANCELLED"),
230 }
231 self.fail_count += 1;
232 } else if actual == expected {
233 println!("{p:?} PASS");
234 self.pass_count += 1;
235 } else {
236 println!("{p:?} FAIL");
237 println!("actual (r-a) = {actual:?}");
238 println!("expected (rustc) = {expected:?}");
239 self.fail_count += 1;
240 }
241 }
242
243 fn report(&mut self) {
244 println!(
245 "Pass count = {}, Fail count = {}, Ignore count = {}",
246 self.pass_count, self.fail_count, self.ignore_count
247 );
248 println!("Testing time and memory = {}", self.stopwatch.elapsed());
249 report_metric("rustc failed tests", self.fail_count, "#");
250 report_metric("rustc testing time", self.stopwatch.elapsed().time.as_millis() as u64, "ms");
251 }
252}
253
254const IGNORED_TESTS: &[&str] = &[
256 "trait-with-missing-associated-type-restriction.rs", "trait-with-missing-associated-type-restriction-fixable.rs", "resolve-self-in-impl.rs",
259 "basic.rs", "issue-26056.rs",
261 "float-field.rs",
262 "invalid_operator_trait.rs",
263 "type-alias-impl-trait-assoc-dyn.rs",
264 "deeply-nested_closures.rs", "hang-on-deeply-nested-dyn.rs", "dyn-rpit-and-let.rs", "issue-16098.rs", "issue-83471.rs", ];
270
271const SUPPORTED_DIAGNOSTICS: &[DiagnosticCode] = &[
272 DiagnosticCode::RustcHardError("E0023"),
273 DiagnosticCode::RustcHardError("E0046"),
274 DiagnosticCode::RustcHardError("E0063"),
275 DiagnosticCode::RustcHardError("E0107"),
276 DiagnosticCode::RustcHardError("E0117"),
277 DiagnosticCode::RustcHardError("E0133"),
278 DiagnosticCode::RustcHardError("E0210"),
279 DiagnosticCode::RustcHardError("E0268"),
280 DiagnosticCode::RustcHardError("E0308"),
281 DiagnosticCode::RustcHardError("E0384"),
282 DiagnosticCode::RustcHardError("E0407"),
283 DiagnosticCode::RustcHardError("E0432"),
284 DiagnosticCode::RustcHardError("E0451"),
285 DiagnosticCode::RustcHardError("E0507"),
286 DiagnosticCode::RustcHardError("E0583"),
287 DiagnosticCode::RustcHardError("E0559"),
288 DiagnosticCode::RustcHardError("E0616"),
289 DiagnosticCode::RustcHardError("E0618"),
290 DiagnosticCode::RustcHardError("E0624"),
291 DiagnosticCode::RustcHardError("E0774"),
292 DiagnosticCode::RustcHardError("E0767"),
293 DiagnosticCode::RustcHardError("E0777"),
294];
295
296impl flags::RustcTests {
297 pub fn run(self) -> Result<()> {
298 let mut tester = Tester::new()?;
299 let walk_dir = WalkDir::new(self.rustc_repo.join("tests/ui"));
300 eprintln!("Running tests for tests/ui");
301 for i in walk_dir {
302 let i = i?;
303 let p = i.into_path();
304 if let Some(f) = &self.filter
305 && !p.as_os_str().to_string_lossy().contains(f)
306 {
307 continue;
308 }
309 if p.extension().is_none_or(|x| x != "rs") {
310 continue;
311 }
312 if let Err(e) = std::panic::catch_unwind({
313 let tester = AssertUnwindSafe(&mut tester);
314 let p = p.clone();
315 move || {
316 let _guard = base_db::DbPanicContext::enter(p.display().to_string());
317 { tester }.0.test(p);
318 }
319 }) {
320 std::panic::resume_unwind(e);
321 }
322 }
323 tester.report();
324 Ok(())
325 }
326}