1#![crate_name = "compiletest"]
2#![warn(unreachable_pub)]
3
4#[cfg(test)]
5mod tests;
6
7pub mod cli;
9pub mod rustdoc_gui_test;
10
11mod common;
12mod debuggers;
13mod diagnostics;
14mod directives;
15mod edition;
16mod errors;
17mod executor;
18mod json;
19mod output_capture;
20mod panic_hook;
21mod raise_fd_limit;
22mod read2;
23mod runtest;
24mod util;
25
26use core::panic;
27use std::collections::HashSet;
28use std::fmt::Write;
29use std::io::{self, ErrorKind};
30use std::sync::Arc;
31use std::time::SystemTime;
32use std::{env, fs, vec};
33
34use build_helper::git::{get_git_modified_files, get_git_untracked_files};
35use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
36use rayon::iter::{ParallelBridge, ParallelIterator};
37use tracing::debug;
38use walkdir::WalkDir;
39
40use self::directives::{EarlyProps, make_test_description};
41use crate::common::{
42 CodegenBackend, Config, Debugger, TestMode, TestPaths, UI_EXTENSIONS, expected_output_path,
43 output_base_dir, output_relative_path,
44};
45use crate::directives::{AuxProps, DirectivesCache, FileDirectives};
46use crate::executor::{CollectedTest, TestVariant};
47
48fn run_tests(config: Arc<Config>) {
50 debug!(?config, "run_tests");
51
52 panic_hook::install_panic_hook();
53
54 if config.rustfix_coverage {
58 let mut coverage_file_path = config.build_test_suite_root.clone();
59 coverage_file_path.push("rustfix_missing_coverage.txt");
60 if coverage_file_path.exists() {
61 if let Err(e) = fs::remove_file(&coverage_file_path) {
62 panic!("Could not delete {} due to {}", coverage_file_path, e)
63 }
64 }
65 }
66
67 unsafe {
71 raise_fd_limit::raise_fd_limit();
72 }
73 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
78
79 let ignore_tests = config.mode == TestMode::DebugInfo && config.target.contains("emscripten");
81
82 if let TestMode::DebugInfo = config.mode {
83 if config.target.contains("android") {
85 println!("{} debug-info test uses tcp 5039 port. please reserve it", config.target);
86
87 unsafe { env::set_var("RUST_TEST_THREADS", "1") };
96 }
97 };
98
99 let mut tests = Vec::new();
102 if !ignore_tests {
103 tests.extend(collect_and_make_tests(config.clone()));
104 }
105
106 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
107
108 let ok = executor::run_tests(&config, tests);
112
113 if !ok {
115 let mut msg = String::from("Some tests failed in compiletest");
123 write!(msg, " suite={}", config.suite).unwrap();
124
125 if let Some(compare_mode) = config.compare_mode.as_ref() {
126 write!(msg, " compare_mode={}", compare_mode).unwrap();
127 }
128
129 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
130 write!(msg, " pass_mode={}", pass_mode).unwrap();
131 }
132
133 write!(msg, " mode={}", config.mode).unwrap();
134 write!(msg, " host={}", config.host).unwrap();
135 write!(msg, " target={}", config.target).unwrap();
136
137 println!("{msg}");
138
139 std::process::exit(1);
140 }
141}
142
143struct TestCollectorCx {
145 config: Arc<Config>,
146 cache: DirectivesCache,
147 common_inputs_stamp: Stamp,
148 modified_tests: Vec<Utf8PathBuf>,
149}
150
151struct TestCollector {
153 tests: Vec<CollectedTest>,
154 found_path_stems: HashSet<Utf8PathBuf>,
155 poisoned: bool,
156}
157
158impl TestCollector {
159 fn new() -> Self {
160 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
161 }
162
163 fn merge(&mut self, mut other: Self) {
164 self.tests.append(&mut other.tests);
165 self.found_path_stems.extend(other.found_path_stems);
166 self.poisoned |= other.poisoned;
167 }
168}
169
170fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
180 debug!("making tests from {}", config.src_test_suite_root);
181 let common_inputs_stamp = common_inputs_stamp(&config);
182 let modified_tests =
183 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
184 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
185 });
186 let cache = DirectivesCache::load(&config);
187
188 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
189 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
190 .unwrap_or_else(|reason| {
191 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
192 });
193
194 let TestCollector { tests, found_path_stems, poisoned } = collector;
195
196 if poisoned {
197 eprintln!();
198 panic!("there are errors in tests");
199 }
200
201 check_for_overlapping_test_paths(&found_path_stems);
202
203 tests
204}
205
206fn common_inputs_stamp(config: &Config) -> Stamp {
214 let src_root = &config.src_root;
215
216 let mut stamp = Stamp::from_path(&config.rustc_path);
217
218 let pretty_printer_files = [
220 "src/etc/rust_types.py",
221 "src/etc/gdb_load_rust_pretty_printers.py",
222 "src/etc/gdb_lookup.py",
223 "src/etc/gdb_providers.py",
224 "src/etc/lldb_lookup.py",
225 "src/etc/lldb_providers.py",
226 ];
227 for file in &pretty_printer_files {
228 let path = src_root.join(file);
229 stamp.add_path(&path);
230 }
231
232 stamp.add_dir(&src_root.join("src/etc/natvis"));
233 stamp.add_dir(&src_root.join("src/etc/lldb_batchmode"));
234
235 stamp.add_dir(&config.target_run_lib_path);
236
237 if let Some(ref rustdoc_path) = config.rustdoc_path {
238 stamp.add_path(&rustdoc_path);
239 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
240 }
241
242 if let Some(coverage_dump_path) = &config.coverage_dump_path {
245 stamp.add_path(coverage_dump_path)
246 }
247
248 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
249
250 stamp.add_dir(&src_root.join("src/tools/compiletest"));
252
253 stamp
254}
255
256fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
261 if !config.only_modified {
264 return Ok(vec![]);
265 }
266
267 let files = get_git_modified_files(
268 &config.git_config(),
269 Some(dir.as_std_path()),
270 &vec!["rs", "stderr", "fixed"],
271 )?;
272 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
274
275 let all_paths = [&files[..], &untracked_files[..]].concat();
276 let full_paths = {
277 let mut full_paths: Vec<Utf8PathBuf> = all_paths
278 .into_iter()
279 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
280 .filter_map(
281 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
282 )
283 .collect();
284 full_paths.dedup();
285 full_paths.sort_unstable();
286 full_paths
287 };
288 Ok(full_paths)
289}
290
291fn collect_tests_from_dir(
294 cx: &TestCollectorCx,
295 dir: &Utf8Path,
296 relative_dir_path: &Utf8Path,
297) -> io::Result<TestCollector> {
298 if dir.join("compiletest-ignore-dir").exists() {
300 return Ok(TestCollector::new());
301 }
302
303 let mut components = dir.components().rev();
304 if let Some(Utf8Component::Normal(last)) = components.next()
305 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
306 && let Some(Utf8Component::Normal(parent)) = components.next()
307 && parent == "tests"
308 && let Ok(backend) = backend.parse::<CodegenBackend>()
309 && backend != cx.config.default_codegen_backend
310 {
311 warning!(
313 "Ignoring tests in `{dir}` because they don't match the configured codegen \
314 backend (`{}`)",
315 cx.config.default_codegen_backend.as_str(),
316 );
317 return Ok(TestCollector::new());
318 }
319
320 if cx.config.mode == TestMode::RunMake {
322 let mut collector = TestCollector::new();
323 if dir.join("rmake.rs").exists() {
324 let paths = TestPaths {
325 file: dir.to_path_buf(),
326 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
327 };
328 make_test(cx, &mut collector, &paths);
329 return Ok(collector);
331 }
332 }
333
334 let build_dir = output_relative_path(&cx.config, relative_dir_path);
341 fs::create_dir_all(&build_dir).unwrap();
342
343 fs::read_dir(dir.as_std_path())?
348 .par_bridge()
349 .map(|file| {
350 let mut collector = TestCollector::new();
351 let file = file?;
352 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
353 let file_name = file_path.file_name().unwrap();
354
355 if is_test(file_name)
356 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
357 {
358 debug!(%file_path, "found test file");
360
361 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
363 collector.found_path_stems.insert(rel_test_path);
364
365 let paths =
366 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
367 make_test(cx, &mut collector, &paths);
368 } else if file_path.is_dir() {
369 let relative_file_path = relative_dir_path.join(file_name);
371 if file_name != "auxiliary" {
372 debug!(%file_path, "found directory");
373 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
374 }
375 } else {
376 debug!(%file_path, "found other file/directory");
377 }
378 Ok(collector)
379 })
380 .reduce(
381 || Ok(TestCollector::new()),
382 |a, b| {
383 let mut a = a?;
384 a.merge(b?);
385 Ok(a)
386 },
387 )
388}
389
390fn is_test(file_name: &str) -> bool {
392 if !file_name.ends_with(".rs") {
393 return false;
394 }
395
396 let invalid_prefixes = &[".", "#", "~"];
398 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
399}
400
401fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
404 let test_path = if cx.config.mode == TestMode::RunMake {
408 testpaths.file.join("rmake.rs")
409 } else {
410 testpaths.file.clone()
411 };
412
413 let file_contents =
415 fs::read_to_string(&test_path).expect("reading test file for directives should succeed");
416 let file_directives = FileDirectives::from_file_contents(&test_path, &file_contents);
417
418 if let Err(message) = directives::do_early_directives_check(cx.config.mode, &file_directives) {
419 panic!("directives check failed:\n{message}");
422 }
423 let early_props = EarlyProps::from_file_directives(&cx.config, &file_directives);
424
425 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
432 vec![None]
433 } else {
434 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
435 };
436
437 let debuggers = if cx.config.mode == TestMode::DebugInfo {
440 vec![Some(Debugger::Cdb), Some(Debugger::Gdb), Some(Debugger::Lldb)]
441 } else {
442 vec![None]
443 };
444
445 for debugger in debuggers {
448 collector.tests.extend(revisions.iter().map(|&revision| {
449 let revision = revision.map(str::to_owned);
450 let variant = TestVariant { revision, debugger };
451
452 let (test_name, filterable_path) =
454 make_test_name_and_filterable_path(&cx.config, testpaths, &variant);
455
456 let mut aux_props = AuxProps::default();
459
460 let mut desc = make_test_description(
464 &cx.config,
465 &cx.cache,
466 test_name,
467 &test_path,
468 &filterable_path,
469 &file_directives,
470 &variant,
471 &mut collector.poisoned,
472 &mut aux_props,
473 );
474
475 if !desc.is_ignored()
478 && !cx.config.force_rerun
479 && is_up_to_date(cx, testpaths, &aux_props, &variant)
480 {
481 desc.ignore_message = Some("up-to-date".into());
485 }
486
487 let config = Arc::clone(&cx.config);
488 let testpaths = testpaths.clone();
489
490 CollectedTest { desc, config, testpaths, variant }
491 }));
492 }
493}
494
495fn stamp_file_path(config: &Config, testpaths: &TestPaths, variant: &TestVariant) -> Utf8PathBuf {
498 output_base_dir(config, testpaths, variant).join("stamp")
499}
500
501fn files_related_to_test(
506 config: &Config,
507 testpaths: &TestPaths,
508 aux_props: &AuxProps,
509 variant: &TestVariant,
510) -> Vec<Utf8PathBuf> {
511 let mut related = vec![];
512
513 if testpaths.file.is_dir() {
514 for entry in WalkDir::new(&testpaths.file) {
516 let path = entry.unwrap().into_path();
517 if path.is_file() {
518 related.push(Utf8PathBuf::try_from(path).unwrap());
519 }
520 }
521 } else {
522 related.push(testpaths.file.clone());
523 }
524
525 for aux in aux_props.all_aux_path_strings() {
526 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
531 related.push(path);
532 }
533
534 for extension in UI_EXTENSIONS {
536 let path =
537 expected_output_path(testpaths, variant.revision(), &config.compare_mode, extension);
538 related.push(path);
539 }
540
541 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
543
544 match variant.debugger {
546 Some(debugger @ Debugger::Lldb | debugger @ Debugger::Gdb) => {
547 let bless_path: Utf8PathBuf =
548 testpaths.file.parent().unwrap().join(format!("{}_input", debugger.to_str()));
549 if bless_path.is_dir() {
550 related.extend(
551 WalkDir::new(bless_path)
552 .into_iter()
553 .map(|entry| Utf8PathBuf::from(entry.unwrap().path().to_str().unwrap())),
554 );
555 }
556 }
557 Some(Debugger::Cdb) | None => {}
558 }
559
560 related
561}
562
563fn is_up_to_date(
569 cx: &TestCollectorCx,
570 testpaths: &TestPaths,
571 aux_props: &AuxProps,
572 variant: &TestVariant,
573) -> bool {
574 let stamp_file_path = stamp_file_path(&cx.config, testpaths, variant);
575 let contents = match fs::read_to_string(&stamp_file_path) {
577 Ok(f) => f,
578 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
579 Err(_) => return false,
581 };
582 let expected_hash = runtest::compute_stamp_hash(&cx.config, variant);
583 if contents != expected_hash {
584 return false;
587 }
588
589 let mut inputs_stamp = cx.common_inputs_stamp.clone();
592 for path in files_related_to_test(&cx.config, testpaths, aux_props, variant) {
593 inputs_stamp.add_path(&path);
594 }
595
596 inputs_stamp < Stamp::from_path(&stamp_file_path)
599}
600
601#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
603struct Stamp {
604 time: SystemTime,
605}
606
607impl Stamp {
608 fn from_path(path: &Utf8Path) -> Self {
610 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
611 stamp.add_path(path);
612 stamp
613 }
614
615 fn add_path(&mut self, path: &Utf8Path) {
618 let modified = fs::metadata(path.as_std_path())
619 .and_then(|metadata| metadata.modified())
620 .unwrap_or(SystemTime::UNIX_EPOCH);
621 self.time = self.time.max(modified);
622 }
623
624 fn add_dir(&mut self, path: &Utf8Path) {
628 let path = path.as_std_path();
629 for entry in WalkDir::new(path) {
630 let entry = entry.unwrap();
631 if entry.file_type().is_file() {
632 let modified = entry
633 .metadata()
634 .ok()
635 .and_then(|metadata| metadata.modified().ok())
636 .unwrap_or(SystemTime::UNIX_EPOCH);
637 self.time = self.time.max(modified);
638 }
639 }
640 }
641}
642
643fn make_test_name_and_filterable_path(
645 config: &Config,
646 testpaths: &TestPaths,
647 variant: &TestVariant,
648) -> (String, Utf8PathBuf) {
649 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
651 let debugger = match variant.debugger.as_ref() {
652 Some(d) => format!("-{d}"),
653 None => String::new(),
654 };
655 let mode_suffix = match config.compare_mode {
656 Some(ref mode) => format!(" ({})", mode.to_str()),
657 None => String::new(),
658 };
659
660 let name = format!(
661 "[{}{}{}] {}{}",
662 config.mode,
663 debugger,
664 mode_suffix,
665 path,
666 variant.revision().map_or("".to_string(), |rev| format!("#{}", rev))
667 );
668
669 let mut filterable_path = path.strip_prefix("tests").unwrap().to_owned();
673 filterable_path = filterable_path.components().skip(1).collect();
675
676 (name, filterable_path)
677}
678
679fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
697 let mut collisions = Vec::new();
698 for path in found_path_stems {
699 for ancestor in path.ancestors().skip(1) {
700 if found_path_stems.contains(ancestor) {
701 collisions.push((path, ancestor));
702 }
703 }
704 }
705 if !collisions.is_empty() {
706 collisions.sort();
707 let collisions: String = collisions
708 .into_iter()
709 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
710 .collect();
711 panic!(
712 "{collisions}\n\
713 Tests cannot have overlapping names. Make sure they use unique prefixes."
714 );
715 }
716}
717
718fn early_config_check(config: &Config) {
719 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
720 let actioned = if config.bless { "blessed" } else { "checked" };
721 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
722 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
723 }
724
725 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
727 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
728 }
729}