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, OnceLock};
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 clap::Parser;
37use rayon::iter::{ParallelBridge, ParallelIterator};
38use tracing::debug;
39use walkdir::WalkDir;
40
41use self::directives::{EarlyProps, make_test_description};
42use crate::common::{
43 CodegenBackend, CompareMode, Config, Debugger, ForcePassMode, TestMode, TestPaths, TestSuite,
44 UI_EXTENSIONS, expected_output_path, output_base_dir, output_relative_path,
45};
46use crate::directives::{AuxProps, DirectivesCache, FileDirectives};
47use crate::edition::Edition;
48use crate::executor::CollectedTest;
49
50#[derive(clap::Parser)]
52struct Args {
53 #[arg(long)]
56 compile_lib_path: Utf8PathBuf,
57 #[arg(long)]
59 run_lib_path: Utf8PathBuf,
60 #[arg(long)]
62 rustc_path: Utf8PathBuf,
63 #[arg(long)]
65 python: String,
66 #[arg(long)]
68 src_root: Utf8PathBuf,
69 #[arg(long)]
71 src_test_suite_root: Utf8PathBuf,
72 #[arg(long)]
74 build_root: Utf8PathBuf,
75 #[arg(long)]
77 build_test_suite_root: Utf8PathBuf,
78 #[arg(long)]
80 sysroot_base: Utf8PathBuf,
81 #[arg(long)]
83 stage: u32,
84 #[arg(long)]
86 stage_id: String,
87 #[arg(long)]
89 mode: TestMode,
90 #[arg(long)]
92 suite: TestSuite,
93 #[arg(long)]
95 cc: String,
96 #[arg(long)]
98 cxx: String,
99 #[arg(long, allow_hyphen_values = true)]
101 cflags: String,
102 #[arg(long, allow_hyphen_values = true)]
104 cxxflags: String,
105 #[arg(long)]
107 llvm_components: String,
108 #[arg(long)]
110 channel: String,
111 #[arg(long)]
113 nightly_branch: String,
114 #[arg(long)]
116 git_merge_commit_email: String,
117 #[arg(long)]
119 minicore_path: Utf8PathBuf,
120 #[arg(long)]
122 jobs: u32,
123 #[arg(long)]
125 host: String,
126 #[arg(long)]
128 target: String,
129
130 #[arg(long)]
133 cargo_path: Option<Utf8PathBuf>,
134 #[arg(long)]
136 stage0_rustc_path: Option<Utf8PathBuf>,
137 #[arg(long)]
139 query_rustc_path: Option<Utf8PathBuf>,
140 #[arg(long)]
142 rustdoc_path: Option<Utf8PathBuf>,
143 #[arg(long)]
145 coverage_dump_path: Option<Utf8PathBuf>,
146 #[arg(long)]
148 jsondocck_path: Option<Utf8PathBuf>,
149 #[arg(long)]
151 jsondoclint_path: Option<Utf8PathBuf>,
152 #[arg(long)]
154 run_clang_based_tests_with: Option<Utf8PathBuf>,
155 #[arg(long)]
157 llvm_filecheck: Option<Utf8PathBuf>,
158 #[arg(long)]
160 llvm_bin_dir: Option<Utf8PathBuf>,
161 #[arg(long)]
163 nodejs: Option<Utf8PathBuf>,
164 #[arg(long)]
166 npm: Option<Utf8PathBuf>,
167 #[arg(long)]
169 remote_test_client: Option<Utf8PathBuf>,
170 #[arg(long)]
172 cdb: Option<Utf8PathBuf>,
173 #[arg(long)]
175 gdb: Option<Utf8PathBuf>,
176 #[arg(long)]
178 lldb: Option<Utf8PathBuf>,
179 #[arg(long)]
181 lldb_version: Option<String>,
182 #[arg(long)]
184 llvm_version: Option<String>,
185 #[arg(long)]
187 android_cross_path: Option<Utf8PathBuf>,
188 #[arg(long)]
190 adb_path: Option<Utf8PathBuf>,
191 #[arg(long)]
193 adb_test_dir: Option<Utf8PathBuf>,
194 #[arg(long, default_value = "ar")]
196 ar: String,
197 #[arg(long)]
199 target_linker: Option<String>,
200 #[arg(long)]
202 host_linker: Option<String>,
203 #[arg(long)]
205 pass: Option<ForcePassMode>,
206 #[arg(long)]
208 run: Option<String>,
209 #[arg(long)]
211 runner: Option<String>,
212 #[arg(long)]
214 compare_mode: Option<CompareMode>,
215 #[arg(long)]
217 edition: Option<Edition>,
218 #[arg(long)]
220 debugger: Option<String>,
221 #[arg(long)]
223 default_codegen_backend: Option<CodegenBackend>,
224 #[arg(long)]
226 override_codegen_backend: Option<String>,
227 #[arg(long)]
229 compiletest_diff_tool: Option<String>,
230 #[arg(long)]
232 parallel_frontend_threads: Option<u32>,
233 #[arg(long)]
235 iteration_count: Option<u32>,
236
237 #[arg(long)]
240 bless: bool,
241 #[arg(long)]
243 fail_fast: bool,
244 #[arg(long)]
246 ignored: bool,
247 #[arg(long)]
249 has_enzyme: bool,
250 #[arg(long)]
252 has_offload: bool,
253 #[arg(long)]
255 with_rustc_debug_assertions: bool,
256 #[arg(long)]
258 with_std_debug_assertions: bool,
259 #[arg(long)]
261 with_std_remap_debuginfo: bool,
262 #[arg(long)]
264 exact: bool,
265 #[arg(long)]
267 rust_randomized_layout: bool,
268 #[arg(long)]
270 optimize_tests: bool,
271 #[arg(long)]
273 verbose: bool,
274 #[arg(long)]
276 verbose_run_make_subprocess_output: bool,
277 #[arg(long)]
279 system_llvm: bool,
280 #[arg(long)]
282 force_rerun: bool,
283 #[arg(long)]
285 only_modified: bool,
286 #[arg(long, hide = true)]
288 nocapture: bool,
289 #[arg(long)]
291 no_capture: bool,
292 #[arg(long)]
294 profiler_runtime: bool,
295 #[arg(long)]
297 git_hash: bool,
298 #[arg(long)]
300 rustfix_coverage: bool,
301 #[arg(long)]
303 bypass_ignore_backends: bool,
304
305 #[arg(long)]
309 skip: Vec<String>,
310 #[arg(long, allow_hyphen_values = true)]
312 host_rustcflags: Vec<String>,
313 #[arg(long, allow_hyphen_values = true)]
315 target_rustcflags: Vec<String>,
316
317 filters: Vec<String>,
321}
322
323fn parse_config(args: Vec<String>) -> Config {
324 let args = Args::parse_from(args);
325
326 fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
327 if path.is_relative() {
328 Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
329 } else {
330 path
331 }
332 }
333
334 if args.nocapture {
335 panic!("`--nocapture` is deprecated; please use `--no-capture`");
336 }
337
338 let adb_device_status = args.target.contains("android") && args.adb_test_dir.is_some();
339
340 let cdb_version = args.cdb.as_deref().and_then(debuggers::query_cdb_version);
342 let gdb_version = args.gdb.as_deref().and_then(debuggers::query_gdb_version);
344 let lldb_version = args.lldb_version.as_deref().and_then(debuggers::extract_lldb_version);
346 let llvm_version =
350 args.llvm_version.as_deref().map(directives::extract_llvm_version).or_else(|| {
351 directives::extract_llvm_version_from_binary(args.llvm_filecheck.as_ref()?.as_str())
352 });
353
354 let default_codegen_backend = args.default_codegen_backend.unwrap_or(CodegenBackend::Llvm);
355
356 let mode = args.mode;
357 let filters = if mode == TestMode::RunMake {
358 args.filters
359 .iter()
360 .map(|f| {
361 let path = Utf8Path::new(f);
367 let mut iter = path.iter().skip(1);
368
369 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
370 path.parent().unwrap().to_string()
373 } else {
374 f.to_string()
375 }
376 })
377 .collect::<Vec<_>>()
378 } else {
379 args.filters.clone()
386 };
387
388 let compare_mode = args.compare_mode;
389
390 let src_root = args.src_root;
391 let src_test_suite_root = args.src_test_suite_root;
392 assert!(
393 src_test_suite_root.starts_with(&src_root),
394 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
395 src_root,
396 src_test_suite_root
397 );
398
399 let build_root = args.build_root;
400 let build_test_suite_root = args.build_test_suite_root;
401 assert!(build_test_suite_root.starts_with(&build_root));
402
403 let parallel_frontend_threads =
404 args.parallel_frontend_threads.unwrap_or(Config::DEFAULT_PARALLEL_FRONTEND_THREADS);
405 let iteration_count = args.iteration_count.unwrap_or(Config::DEFAULT_ITERATION_COUNT);
406 assert!(iteration_count > 0, "`--iteration-count` must be a positive integer");
407
408 Config {
409 bless: args.bless,
410 fail_fast: args.fail_fast || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
411
412 host_compile_lib_path: make_absolute(args.compile_lib_path),
413 target_run_lib_path: make_absolute(args.run_lib_path),
414 rustc_path: args.rustc_path,
415 cargo_path: args.cargo_path,
416 stage0_rustc_path: args.stage0_rustc_path,
417 query_rustc_path: args.query_rustc_path,
418 rustdoc_path: args.rustdoc_path,
419 coverage_dump_path: args.coverage_dump_path,
420 python: args.python,
421 jsondocck_path: args.jsondocck_path,
422 jsondoclint_path: args.jsondoclint_path,
423 run_clang_based_tests_with: args.run_clang_based_tests_with,
424 llvm_filecheck: args.llvm_filecheck,
425 llvm_bin_dir: args.llvm_bin_dir,
426
427 src_root,
428 src_test_suite_root,
429
430 build_root,
431 build_test_suite_root,
432
433 sysroot_base: args.sysroot_base,
434
435 stage: args.stage,
436 stage_id: args.stage_id,
437
438 mode,
439 suite: args.suite,
440 debugger: args.debugger.map(|debugger| {
441 debugger
442 .parse::<Debugger>()
443 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
444 }),
445 run_ignored: args.ignored,
446 with_rustc_debug_assertions: args.with_rustc_debug_assertions,
447 with_std_debug_assertions: args.with_std_debug_assertions,
448 with_std_remap_debuginfo: args.with_std_remap_debuginfo,
449 filters,
450 skip: args.skip,
451 filter_exact: args.exact,
452 force_pass_mode: args.pass,
453 run: args.run.and_then(|mode| match mode.as_str() {
455 "auto" => None,
456 "always" => Some(true),
457 "never" => Some(false),
458 _ => panic!("unknown `--run` option `{}` given", mode),
459 }),
460 runner: args.runner,
461 host_rustcflags: args.host_rustcflags,
462 target_rustcflags: args.target_rustcflags,
463 optimize_tests: args.optimize_tests,
464 rust_randomized_layout: args.rust_randomized_layout,
465 target: args.target,
466 host: args.host,
467 cdb: args.cdb,
468 cdb_version,
469 gdb: args.gdb,
470 gdb_version,
471 lldb: args.lldb,
472 lldb_version,
473 llvm_version,
474 system_llvm: args.system_llvm,
475 android_cross_path: args.android_cross_path,
476 adb_path: args.adb_path,
477 adb_test_dir: args.adb_test_dir,
478 adb_device_status,
479 verbose: args.verbose,
480 verbose_run_make_subprocess_output: args.verbose_run_make_subprocess_output,
481 only_modified: args.only_modified,
482 remote_test_client: args.remote_test_client,
483 compare_mode,
484 rustfix_coverage: args.rustfix_coverage,
485 has_enzyme: args.has_enzyme,
486 has_offload: args.has_offload,
487 channel: args.channel,
488 git_hash: args.git_hash,
489 edition: args.edition,
490
491 cc: args.cc,
492 cxx: args.cxx,
493 cflags: args.cflags,
494 cxxflags: args.cxxflags,
495 ar: args.ar,
496 target_linker: args.target_linker,
497 host_linker: args.host_linker,
498 llvm_components: args.llvm_components,
499 nodejs: args.nodejs,
500
501 force_rerun: args.force_rerun,
502
503 target_cfgs: OnceLock::new(),
504 builtin_cfg_names: OnceLock::new(),
505 supported_crate_types: OnceLock::new(),
506
507 capture: !args.no_capture,
508
509 nightly_branch: args.nightly_branch,
510 git_merge_commit_email: args.git_merge_commit_email,
511
512 profiler_runtime: args.profiler_runtime,
513
514 diff_command: args.compiletest_diff_tool,
515
516 minicore_path: args.minicore_path,
517
518 default_codegen_backend,
519 override_codegen_backend: args.override_codegen_backend,
520 bypass_ignore_backends: args.bypass_ignore_backends,
521
522 jobs: args.jobs,
523
524 parallel_frontend_threads,
525 iteration_count,
526 }
527}
528
529fn run_tests(config: Arc<Config>) {
531 debug!(?config, "run_tests");
532
533 panic_hook::install_panic_hook();
534
535 if config.rustfix_coverage {
539 let mut coverage_file_path = config.build_test_suite_root.clone();
540 coverage_file_path.push("rustfix_missing_coverage.txt");
541 if coverage_file_path.exists() {
542 if let Err(e) = fs::remove_file(&coverage_file_path) {
543 panic!("Could not delete {} due to {}", coverage_file_path, e)
544 }
545 }
546 }
547
548 unsafe {
552 raise_fd_limit::raise_fd_limit();
553 }
554 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
559
560 let mut configs = Vec::new();
561 if let TestMode::DebugInfo = config.mode {
562 if !config.target.contains("emscripten") {
564 configs.extend([
568 Arc::new(Config { debugger: Some(Debugger::Cdb), ..config.as_ref().clone() }),
569 Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.as_ref().clone() }),
570 Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.as_ref().clone() }),
571 ]);
572
573 if config.target.contains("android") {
575 println!("{} debug-info test uses tcp 5039 port. please reserve it", config.target);
576
577 unsafe { env::set_var("RUST_TEST_THREADS", "1") };
586 }
587 }
588 } else {
589 configs.push(config.clone());
590 };
591
592 let mut tests = Vec::new();
595 for c in configs {
596 tests.extend(collect_and_make_tests(c));
597 }
598
599 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
600
601 let ok = executor::run_tests(&config, tests);
605
606 if !ok {
608 let mut msg = String::from("Some tests failed in compiletest");
616 write!(msg, " suite={}", config.suite).unwrap();
617
618 if let Some(compare_mode) = config.compare_mode.as_ref() {
619 write!(msg, " compare_mode={}", compare_mode).unwrap();
620 }
621
622 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
623 write!(msg, " pass_mode={}", pass_mode).unwrap();
624 }
625
626 write!(msg, " mode={}", config.mode).unwrap();
627 write!(msg, " host={}", config.host).unwrap();
628 write!(msg, " target={}", config.target).unwrap();
629
630 println!("{msg}");
631
632 std::process::exit(1);
633 }
634}
635
636struct TestCollectorCx {
638 config: Arc<Config>,
639 cache: DirectivesCache,
640 common_inputs_stamp: Stamp,
641 modified_tests: Vec<Utf8PathBuf>,
642}
643
644struct TestCollector {
646 tests: Vec<CollectedTest>,
647 found_path_stems: HashSet<Utf8PathBuf>,
648 poisoned: bool,
649}
650
651impl TestCollector {
652 fn new() -> Self {
653 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
654 }
655
656 fn merge(&mut self, mut other: Self) {
657 self.tests.append(&mut other.tests);
658 self.found_path_stems.extend(other.found_path_stems);
659 self.poisoned |= other.poisoned;
660 }
661}
662
663fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
673 debug!("making tests from {}", config.src_test_suite_root);
674 let common_inputs_stamp = common_inputs_stamp(&config);
675 let modified_tests =
676 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
677 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
678 });
679 let cache = DirectivesCache::load(&config);
680
681 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
682 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
683 .unwrap_or_else(|reason| {
684 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
685 });
686
687 let TestCollector { tests, found_path_stems, poisoned } = collector;
688
689 if poisoned {
690 eprintln!();
691 panic!("there are errors in tests");
692 }
693
694 check_for_overlapping_test_paths(&found_path_stems);
695
696 tests
697}
698
699fn common_inputs_stamp(config: &Config) -> Stamp {
707 let src_root = &config.src_root;
708
709 let mut stamp = Stamp::from_path(&config.rustc_path);
710
711 let pretty_printer_files = [
713 "src/etc/rust_types.py",
714 "src/etc/gdb_load_rust_pretty_printers.py",
715 "src/etc/gdb_lookup.py",
716 "src/etc/gdb_providers.py",
717 "src/etc/lldb_batchmode",
718 "src/etc/lldb_lookup.py",
719 "src/etc/lldb_providers.py",
720 ];
721 for file in &pretty_printer_files {
722 let path = src_root.join(file);
723 stamp.add_path(&path);
724 }
725
726 stamp.add_dir(&src_root.join("src/etc/natvis"));
727
728 stamp.add_dir(&config.target_run_lib_path);
729
730 if let Some(ref rustdoc_path) = config.rustdoc_path {
731 stamp.add_path(&rustdoc_path);
732 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
733 }
734
735 if let Some(coverage_dump_path) = &config.coverage_dump_path {
738 stamp.add_path(coverage_dump_path)
739 }
740
741 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
742
743 stamp.add_dir(&src_root.join("src/tools/compiletest"));
745
746 stamp
747}
748
749fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
754 if !config.only_modified {
757 return Ok(vec![]);
758 }
759
760 let files = get_git_modified_files(
761 &config.git_config(),
762 Some(dir.as_std_path()),
763 &vec!["rs", "stderr", "fixed"],
764 )?;
765 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
767
768 let all_paths = [&files[..], &untracked_files[..]].concat();
769 let full_paths = {
770 let mut full_paths: Vec<Utf8PathBuf> = all_paths
771 .into_iter()
772 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
773 .filter_map(
774 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
775 )
776 .collect();
777 full_paths.dedup();
778 full_paths.sort_unstable();
779 full_paths
780 };
781 Ok(full_paths)
782}
783
784fn collect_tests_from_dir(
787 cx: &TestCollectorCx,
788 dir: &Utf8Path,
789 relative_dir_path: &Utf8Path,
790) -> io::Result<TestCollector> {
791 if dir.join("compiletest-ignore-dir").exists() {
793 return Ok(TestCollector::new());
794 }
795
796 let mut components = dir.components().rev();
797 if let Some(Utf8Component::Normal(last)) = components.next()
798 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
799 && let Some(Utf8Component::Normal(parent)) = components.next()
800 && parent == "tests"
801 && let Ok(backend) = backend.parse::<CodegenBackend>()
802 && backend != cx.config.default_codegen_backend
803 {
804 warning!(
806 "Ignoring tests in `{dir}` because they don't match the configured codegen \
807 backend (`{}`)",
808 cx.config.default_codegen_backend.as_str(),
809 );
810 return Ok(TestCollector::new());
811 }
812
813 if cx.config.mode == TestMode::RunMake {
815 let mut collector = TestCollector::new();
816 if dir.join("rmake.rs").exists() {
817 let paths = TestPaths {
818 file: dir.to_path_buf(),
819 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
820 };
821 make_test(cx, &mut collector, &paths);
822 return Ok(collector);
824 }
825 }
826
827 let build_dir = output_relative_path(&cx.config, relative_dir_path);
834 fs::create_dir_all(&build_dir).unwrap();
835
836 fs::read_dir(dir.as_std_path())?
841 .par_bridge()
842 .map(|file| {
843 let mut collector = TestCollector::new();
844 let file = file?;
845 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
846 let file_name = file_path.file_name().unwrap();
847
848 if is_test(file_name)
849 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
850 {
851 debug!(%file_path, "found test file");
853
854 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
856 collector.found_path_stems.insert(rel_test_path);
857
858 let paths =
859 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
860 make_test(cx, &mut collector, &paths);
861 } else if file_path.is_dir() {
862 let relative_file_path = relative_dir_path.join(file_name);
864 if file_name != "auxiliary" {
865 debug!(%file_path, "found directory");
866 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
867 }
868 } else {
869 debug!(%file_path, "found other file/directory");
870 }
871 Ok(collector)
872 })
873 .reduce(
874 || Ok(TestCollector::new()),
875 |a, b| {
876 let mut a = a?;
877 a.merge(b?);
878 Ok(a)
879 },
880 )
881}
882
883fn is_test(file_name: &str) -> bool {
885 if !file_name.ends_with(".rs") {
886 return false;
887 }
888
889 let invalid_prefixes = &[".", "#", "~"];
891 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
892}
893
894fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
897 let test_path = if cx.config.mode == TestMode::RunMake {
901 testpaths.file.join("rmake.rs")
902 } else {
903 testpaths.file.clone()
904 };
905
906 let file_contents =
908 fs::read_to_string(&test_path).expect("reading test file for directives should succeed");
909 let file_directives = FileDirectives::from_file_contents(&test_path, &file_contents);
910
911 if let Err(message) = directives::do_early_directives_check(cx.config.mode, &file_directives) {
912 panic!("directives check failed:\n{message}");
915 }
916 let early_props = EarlyProps::from_file_directives(&cx.config, &file_directives);
917
918 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
925 vec![None]
926 } else {
927 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
928 };
929
930 collector.tests.extend(revisions.into_iter().map(|revision| {
933 let (test_name, filterable_path) =
935 make_test_name_and_filterable_path(&cx.config, testpaths, revision);
936
937 let mut aux_props = AuxProps::default();
940
941 let mut desc = make_test_description(
945 &cx.config,
946 &cx.cache,
947 test_name,
948 &test_path,
949 &filterable_path,
950 &file_directives,
951 revision,
952 &mut collector.poisoned,
953 &mut aux_props,
954 );
955
956 if !desc.is_ignored()
959 && !cx.config.force_rerun
960 && is_up_to_date(cx, testpaths, &aux_props, revision)
961 {
962 desc.ignore_message = Some("up-to-date".into());
966 }
967
968 let config = Arc::clone(&cx.config);
969 let testpaths = testpaths.clone();
970 let revision = revision.map(str::to_owned);
971
972 CollectedTest { desc, config, testpaths, revision }
973 }));
974}
975
976fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
979 output_base_dir(config, testpaths, revision).join("stamp")
980}
981
982fn files_related_to_test(
987 config: &Config,
988 testpaths: &TestPaths,
989 aux_props: &AuxProps,
990 revision: Option<&str>,
991) -> Vec<Utf8PathBuf> {
992 let mut related = vec![];
993
994 if testpaths.file.is_dir() {
995 for entry in WalkDir::new(&testpaths.file) {
997 let path = entry.unwrap().into_path();
998 if path.is_file() {
999 related.push(Utf8PathBuf::try_from(path).unwrap());
1000 }
1001 }
1002 } else {
1003 related.push(testpaths.file.clone());
1004 }
1005
1006 for aux in aux_props.all_aux_path_strings() {
1007 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
1012 related.push(path);
1013 }
1014
1015 for extension in UI_EXTENSIONS {
1017 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
1018 related.push(path);
1019 }
1020
1021 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
1023
1024 related
1025}
1026
1027fn is_up_to_date(
1033 cx: &TestCollectorCx,
1034 testpaths: &TestPaths,
1035 aux_props: &AuxProps,
1036 revision: Option<&str>,
1037) -> bool {
1038 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
1039 let contents = match fs::read_to_string(&stamp_file_path) {
1041 Ok(f) => f,
1042 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
1043 Err(_) => return false,
1045 };
1046 let expected_hash = runtest::compute_stamp_hash(&cx.config);
1047 if contents != expected_hash {
1048 return false;
1051 }
1052
1053 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1056 for path in files_related_to_test(&cx.config, testpaths, aux_props, revision) {
1057 inputs_stamp.add_path(&path);
1058 }
1059
1060 inputs_stamp < Stamp::from_path(&stamp_file_path)
1063}
1064
1065#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1067struct Stamp {
1068 time: SystemTime,
1069}
1070
1071impl Stamp {
1072 fn from_path(path: &Utf8Path) -> Self {
1074 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1075 stamp.add_path(path);
1076 stamp
1077 }
1078
1079 fn add_path(&mut self, path: &Utf8Path) {
1082 let modified = fs::metadata(path.as_std_path())
1083 .and_then(|metadata| metadata.modified())
1084 .unwrap_or(SystemTime::UNIX_EPOCH);
1085 self.time = self.time.max(modified);
1086 }
1087
1088 fn add_dir(&mut self, path: &Utf8Path) {
1092 let path = path.as_std_path();
1093 for entry in WalkDir::new(path) {
1094 let entry = entry.unwrap();
1095 if entry.file_type().is_file() {
1096 let modified = entry
1097 .metadata()
1098 .ok()
1099 .and_then(|metadata| metadata.modified().ok())
1100 .unwrap_or(SystemTime::UNIX_EPOCH);
1101 self.time = self.time.max(modified);
1102 }
1103 }
1104 }
1105}
1106
1107fn make_test_name_and_filterable_path(
1109 config: &Config,
1110 testpaths: &TestPaths,
1111 revision: Option<&str>,
1112) -> (String, Utf8PathBuf) {
1113 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1115 let debugger = match config.debugger {
1116 Some(d) => format!("-{}", d),
1117 None => String::new(),
1118 };
1119 let mode_suffix = match config.compare_mode {
1120 Some(ref mode) => format!(" ({})", mode.to_str()),
1121 None => String::new(),
1122 };
1123
1124 let name = format!(
1125 "[{}{}{}] {}{}",
1126 config.mode,
1127 debugger,
1128 mode_suffix,
1129 path,
1130 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1131 );
1132
1133 let mut filterable_path = path.strip_prefix("tests").unwrap().to_owned();
1137 filterable_path = filterable_path.components().skip(1).collect();
1139
1140 (name, filterable_path)
1141}
1142
1143fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1161 let mut collisions = Vec::new();
1162 for path in found_path_stems {
1163 for ancestor in path.ancestors().skip(1) {
1164 if found_path_stems.contains(ancestor) {
1165 collisions.push((path, ancestor));
1166 }
1167 }
1168 }
1169 if !collisions.is_empty() {
1170 collisions.sort();
1171 let collisions: String = collisions
1172 .into_iter()
1173 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1174 .collect();
1175 panic!(
1176 "{collisions}\n\
1177 Tests cannot have overlapping names. Make sure they use unique prefixes."
1178 );
1179 }
1180}
1181
1182fn early_config_check(config: &Config) {
1183 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1184 let actioned = if config.bless { "blessed" } else { "checked" };
1185 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1186 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1187 }
1188
1189 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1191 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1192 }
1193}