1#![feature(test)]
4
5extern crate test;
6
7#[cfg(test)]
8mod tests;
9
10pub mod common;
11pub mod compute_diff;
12pub mod errors;
13pub mod header;
14mod json;
15mod raise_fd_limit;
16mod read2;
17pub mod runtest;
18pub mod util;
19
20use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS};
21use crate::common::{Config, Debugger, Mode, PassMode, TestPaths};
22use crate::util::logv;
23use build_helper::git::{get_git_modified_files, get_git_untracked_files};
24use core::panic;
25use getopts::Options;
26use lazycell::AtomicLazyCell;
27use std::collections::BTreeSet;
28use std::ffi::OsString;
29use std::fs;
30use std::io::{self, ErrorKind};
31use std::path::{Path, PathBuf};
32use std::process::{Command, Stdio};
33use std::time::SystemTime;
34use std::{env, vec};
35use test::ColorConfig;
36use tracing::*;
37use walkdir::WalkDir;
38
39use self::header::{make_test_description, EarlyProps};
40use crate::header::HeadersCache;
41use std::sync::Arc;
42
43pub fn parse_config(args: Vec<String>) -> Config {
44 let mut opts = Options::new();
45 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
46 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
47 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
48 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
49 .optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH")
50 .reqopt("", "python", "path to python to use for doc tests", "PATH")
51 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
52 .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
53 .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
54 .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind")
55 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
56 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
57 .reqopt("", "src-base", "directory to scan for test files", "PATH")
58 .reqopt("", "build-base", "directory to deposit test outputs", "PATH")
59 .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
60 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
61 .reqopt(
62 "",
63 "mode",
64 "which sort of compile tests to run",
65 "run-pass-valgrind | pretty | debug-info | codegen | rustdoc \
66 | rustdoc-json | codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly",
67 )
68 .reqopt(
69 "",
70 "suite",
71 "which suite of compile tests to run. used for nicer error reporting.",
72 "SUITE",
73 )
74 .optopt(
75 "",
76 "pass",
77 "force {check,build,run}-pass tests to this mode.",
78 "check | build | run",
79 )
80 .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
81 .optflag("", "ignored", "run tests marked as ignored")
82 .optmulti("", "skip", "skip tests matching SUBSTRING. Can be passed multiple times", "SUBSTRING")
83 .optflag("", "exact", "filters match exactly")
84 .optopt(
85 "",
86 "runtool",
87 "supervisor program to run tests under \
88 (eg. emulator, valgrind)",
89 "PROGRAM",
90 )
91 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
92 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
93 .optflag("", "optimize-tests", "run tests with optimizations enabled")
94 .optflag("", "verbose", "run tests verbosely, showing all output")
95 .optflag(
96 "",
97 "bless",
98 "overwrite stderr/stdout files instead of complaining about a mismatch",
99 )
100 .optflag("", "quiet", "print one character per test instead of one line")
101 .optopt("", "color", "coloring: auto, always, never", "WHEN")
102 .optflag("", "json", "emit json output instead of plaintext output")
103 .optopt("", "logfile", "file to log test execution to", "FILE")
104 .optopt("", "target", "the target to build for", "TARGET")
105 .optopt("", "host", "the host to build for", "HOST")
106 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
107 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
108 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
109 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
110 .optflag("", "system-llvm", "is LLVM the system LLVM")
111 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
112 .optopt("", "adb-path", "path to the android debugger", "PATH")
113 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
114 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
115 .reqopt("", "cc", "path to a C compiler", "PATH")
116 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
117 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
118 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
119 .optopt("", "ar", "path to an archiver", "PATH")
120 .optopt("", "target-linker", "path to a linker for the target", "PATH")
121 .optopt("", "host-linker", "path to a linker for the host", "PATH")
122 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
123 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
124 .optopt("", "nodejs", "the name of nodejs", "PATH")
125 .optopt("", "npm", "the name of npm", "PATH")
126 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
127 .optopt(
128 "",
129 "compare-mode",
130 "mode describing what file the actual ui output will be compared to",
131 "COMPARE MODE",
132 )
133 .optflag(
134 "",
135 "rustfix-coverage",
136 "enable this to generate a Rustfix coverage file, which is saved in \
137 `./<build_base>/rustfix_missing_coverage.txt`",
138 )
139 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
140 .optflag("", "only-modified", "only run tests that result been modified")
141 .optflag("", "nocapture", "")
142 .optflag("h", "help", "show this message")
143 .reqopt("", "channel", "current Rust channel", "CHANNEL")
144 .optflag("", "git-hash", "run tests which rely on commit version being compiled into the binaries")
145 .optopt("", "edition", "default Rust edition", "EDITION");
146
147 let (argv0, args_) = args.split_first().unwrap();
148 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
149 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
150 println!("{}", opts.usage(&message));
151 println!();
152 panic!()
153 }
154
155 let matches = &match opts.parse(args_) {
156 Ok(m) => m,
157 Err(f) => panic!("{:?}", f),
158 };
159
160 if matches.opt_present("h") || matches.opt_present("help") {
161 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
162 println!("{}", opts.usage(&message));
163 println!();
164 panic!()
165 }
166
167 fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
168 match m.opt_str(nm) {
169 Some(s) => PathBuf::from(&s),
170 None => panic!("no option (=path) found for {}", nm),
171 }
172 }
173
174 fn make_absolute(path: PathBuf) -> PathBuf {
175 if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
176 }
177
178 let target = opt_str2(matches.opt_str("target"));
179 let android_cross_path = opt_path(matches, "android-cross-path");
180 let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target);
181 let (gdb, gdb_version, gdb_native_rust) =
182 analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
183 let (lldb_version, lldb_native_rust) = matches
184 .opt_str("lldb-version")
185 .as_deref()
186 .and_then(extract_lldb_version)
187 .map(|(v, b)| (Some(v), b))
188 .unwrap_or((None, false));
189 let color = match matches.opt_str("color").as_deref() {
190 Some("auto") | None => ColorConfig::AutoColor,
191 Some("always") => ColorConfig::AlwaysColor,
192 Some("never") => ColorConfig::NeverColor,
193 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
194 };
195 let llvm_version =
196 matches.opt_str("llvm-version").as_deref().and_then(header::extract_llvm_version).or_else(
197 || header::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
198 );
199
200 let src_base = opt_path(matches, "src-base");
201 let run_ignored = matches.opt_present("ignored");
202 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
203 let has_tidy = if mode == Mode::Rustdoc {
204 Command::new("tidy")
205 .arg("--version")
206 .stdout(Stdio::null())
207 .status()
208 .map_or(false, |status| status.success())
209 } else {
210 false
212 };
213 Config {
214 bless: matches.opt_present("bless"),
215 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
216 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
217 rustc_path: opt_path(matches, "rustc-path"),
218 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
219 rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
220 python: matches.opt_str("python").unwrap(),
221 jsondocck_path: matches.opt_str("jsondocck-path"),
222 jsondoclint_path: matches.opt_str("jsondoclint-path"),
223 valgrind_path: matches.opt_str("valgrind-path"),
224 force_valgrind: matches.opt_present("force-valgrind"),
225 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
226 llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
227 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
228 src_base,
229 build_base: opt_path(matches, "build-base"),
230 sysroot_base: opt_path(matches, "sysroot-base"),
231 stage_id: matches.opt_str("stage-id").unwrap(),
232 mode,
233 suite: matches.opt_str("suite").unwrap(),
234 debugger: None,
235 run_ignored,
236 filters: matches.free.clone(),
237 skip: matches.opt_strs("skip"),
238 filter_exact: matches.opt_present("exact"),
239 force_pass_mode: matches.opt_str("pass").map(|mode| {
240 mode.parse::<PassMode>()
241 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
242 }),
243 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
244 "auto" => None,
245 "always" => Some(true),
246 "never" => Some(false),
247 _ => panic!("unknown `--run` option `{}` given", mode),
248 }),
249 logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
250 runtool: matches.opt_str("runtool"),
251 host_rustcflags: matches.opt_strs("host-rustcflags"),
252 target_rustcflags: matches.opt_strs("target-rustcflags"),
253 optimize_tests: matches.opt_present("optimize-tests"),
254 target,
255 host: opt_str2(matches.opt_str("host")),
256 cdb,
257 cdb_version,
258 gdb,
259 gdb_version,
260 gdb_native_rust,
261 lldb_version,
262 lldb_native_rust,
263 llvm_version,
264 system_llvm: matches.opt_present("system-llvm"),
265 android_cross_path,
266 adb_path: opt_str2(matches.opt_str("adb-path")),
267 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
268 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
269 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
270 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
271 lldb_python_dir: matches.opt_str("lldb-python-dir"),
272 verbose: matches.opt_present("verbose"),
273 format: match (matches.opt_present("quiet"), matches.opt_present("json")) {
274 (true, true) => panic!("--quiet and --json are incompatible"),
275 (true, false) => test::OutputFormat::Terse,
276 (false, true) => test::OutputFormat::Json,
277 (false, false) => test::OutputFormat::Pretty,
278 },
279 only_modified: matches.opt_present("only-modified"),
280 color,
281 remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
282 compare_mode: matches
283 .opt_str("compare-mode")
284 .map(|s| s.parse().expect("invalid --compare-mode provided")),
285 rustfix_coverage: matches.opt_present("rustfix-coverage"),
286 has_tidy,
287 channel: matches.opt_str("channel").unwrap(),
288 git_hash: matches.opt_present("git-hash"),
289 edition: matches.opt_str("edition"),
290
291 cc: matches.opt_str("cc").unwrap(),
292 cxx: matches.opt_str("cxx").unwrap(),
293 cflags: matches.opt_str("cflags").unwrap(),
294 cxxflags: matches.opt_str("cxxflags").unwrap(),
295 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
296 target_linker: matches.opt_str("target-linker"),
297 host_linker: matches.opt_str("host-linker"),
298 llvm_components: matches.opt_str("llvm-components").unwrap(),
299 nodejs: matches.opt_str("nodejs"),
300 npm: matches.opt_str("npm"),
301
302 force_rerun: matches.opt_present("force-rerun"),
303
304 target_cfgs: AtomicLazyCell::new(),
305
306 nocapture: matches.opt_present("nocapture"),
307 }
308}
309
310pub fn log_config(config: &Config) {
311 let c = config;
312 logv(c, "configuration:".to_string());
313 logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
314 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
315 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
316 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
317 logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
318 logv(c, format!("src_base: {:?}", config.src_base.display()));
319 logv(c, format!("build_base: {:?}", config.build_base.display()));
320 logv(c, format!("stage_id: {}", config.stage_id));
321 logv(c, format!("mode: {}", config.mode));
322 logv(c, format!("run_ignored: {}", config.run_ignored));
323 logv(c, format!("filters: {:?}", config.filters));
324 logv(c, format!("skip: {:?}", config.skip));
325 logv(c, format!("filter_exact: {}", config.filter_exact));
326 logv(
327 c,
328 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
329 );
330 logv(c, format!("runtool: {}", opt_str(&config.runtool)));
331 logv(c, format!("host-rustcflags: {:?}", config.host_rustcflags));
332 logv(c, format!("target-rustcflags: {:?}", config.target_rustcflags));
333 logv(c, format!("target: {}", config.target));
334 logv(c, format!("host: {}", config.host));
335 logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
336 logv(c, format!("adb_path: {:?}", config.adb_path));
337 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
338 logv(c, format!("adb_device_status: {}", config.adb_device_status));
339 logv(c, format!("ar: {}", config.ar));
340 logv(c, format!("target-linker: {:?}", config.target_linker));
341 logv(c, format!("host-linker: {:?}", config.host_linker));
342 logv(c, format!("verbose: {}", config.verbose));
343 logv(c, format!("format: {:?}", config.format));
344 logv(c, "\n".to_string());
345}
346
347pub fn opt_str(maybestr: &Option<String>) -> &str {
348 match *maybestr {
349 None => "(none)",
350 Some(ref s) => s,
351 }
352}
353
354pub fn opt_str2(maybestr: Option<String>) -> String {
355 match maybestr {
356 None => "(none)".to_owned(),
357 Some(s) => s,
358 }
359}
360
361pub fn run_tests(config: Arc<Config>) {
362 if config.rustfix_coverage {
366 let mut coverage_file_path = config.build_base.clone();
367 coverage_file_path.push("rustfix_missing_coverage.txt");
368 if coverage_file_path.exists() {
369 if let Err(e) = fs::remove_file(&coverage_file_path) {
370 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
371 }
372 }
373 }
374
375 unsafe {
379 raise_fd_limit::raise_fd_limit();
380 }
381 env::set_var("__COMPAT_LAYER", "RunAsInvoker");
384
385 env::set_var("TARGET", &config.target);
387
388 let opts = test_opts(&config);
389
390 let mut configs = Vec::new();
391 if let Mode::DebugInfo = config.mode {
392 if !config.target.contains("emscripten") {
394 configs.extend(configure_cdb(&config));
395 configs.extend(configure_gdb(&config));
396 configs.extend(configure_lldb(&config));
397 }
398 } else {
399 configs.push(config.clone());
400 };
401
402 let mut tests = Vec::new();
403 for c in configs {
404 let mut found_paths = BTreeSet::new();
405 make_tests(c, &mut tests, &mut found_paths);
406 check_overlapping_tests(&found_paths);
407 }
408
409 tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice()));
410
411 let res = test::run_tests_console(&opts, tests);
412 match res {
413 Ok(true) => {}
414 Ok(false) => {
415 println!(
423 "Some tests failed in compiletest suite={}{} mode={} host={} target={}",
424 config.suite,
425 config
426 .compare_mode
427 .as_ref()
428 .map(|c| format!(" compare_mode={:?}", c))
429 .unwrap_or_default(),
430 config.mode,
431 config.host,
432 config.target
433 );
434
435 std::process::exit(1);
436 }
437 Err(e) => {
438 panic!("I/O failure during tests: {:?}", e);
445 }
446 }
447}
448
449fn configure_cdb(config: &Config) -> Option<Arc<Config>> {
450 config.cdb.as_ref()?;
451
452 Some(Arc::new(Config { debugger: Some(Debugger::Cdb), ..config.clone() }))
453}
454
455fn configure_gdb(config: &Config) -> Option<Arc<Config>> {
456 config.gdb_version?;
457
458 if config.matches_env("msvc") {
459 return None;
460 }
461
462 if config.remote_test_client.is_some() && !config.target.contains("android") {
463 println!(
464 "WARNING: debuginfo tests are not available when \
465 testing with remote"
466 );
467 return None;
468 }
469
470 if config.target.contains("android") {
471 println!(
472 "{} debug-info test uses tcp 5039 port.\
473 please reserve it",
474 config.target
475 );
476
477 env::set_var("RUST_TEST_THREADS", "1");
484 }
485
486 Some(Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.clone() }))
487}
488
489fn configure_lldb(config: &Config) -> Option<Arc<Config>> {
490 config.lldb_python_dir.as_ref()?;
491
492 if let Some(350) = config.lldb_version {
493 println!(
494 "WARNING: The used version of LLDB (350) has a \
495 known issue that breaks debuginfo tests. See \
496 issue #32520 for more information. Skipping all \
497 LLDB-based tests!",
498 );
499 return None;
500 }
501
502 Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() }))
503}
504
505pub fn test_opts(config: &Config) -> test::TestOpts {
506 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
507 eprintln!(
508 "WARNING: RUST_TEST_NOCAPTURE is no longer used. \
509 Use the `--nocapture` flag instead."
510 );
511 }
512
513 test::TestOpts {
514 exclude_should_panic: false,
515 filters: config.filters.clone(),
516 filter_exact: config.filter_exact,
517 run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
518 format: config.format,
519 logfile: config.logfile.clone(),
520 run_tests: true,
521 bench_benchmarks: true,
522 nocapture: config.nocapture,
523 color: config.color,
524 shuffle: false,
525 shuffle_seed: None,
526 test_threads: None,
527 skip: config.skip.clone(),
528 list: false,
529 options: test::Options::new(),
530 time_options: None,
531 force_run_in_process: false,
532 fail_fast: std::env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
533 }
534}
535
536pub fn make_tests(
537 config: Arc<Config>,
538 tests: &mut Vec<test::TestDescAndFn>,
539 found_paths: &mut BTreeSet<PathBuf>,
540) {
541 debug!("making tests from {:?}", config.src_base.display());
542 let inputs = common_inputs_stamp(&config);
543 let modified_tests = modified_tests(&config, &config.src_base).unwrap_or_else(|err| {
544 panic!("modified_tests got error from dir: {}, error: {}", config.src_base.display(), err)
545 });
546
547 let cache = HeadersCache::load(&config);
548 let mut poisoned = false;
549 collect_tests_from_dir(
550 config.clone(),
551 &cache,
552 &config.src_base,
553 &PathBuf::new(),
554 &inputs,
555 tests,
556 found_paths,
557 &modified_tests,
558 &mut poisoned,
559 )
560 .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
561
562 if poisoned {
563 eprintln!();
564 panic!("there are errors in tests");
565 }
566}
567
568fn common_inputs_stamp(config: &Config) -> Stamp {
570 let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
571
572 let mut stamp = Stamp::from_path(&config.rustc_path);
573
574 let pretty_printer_files = [
576 "src/etc/rust_types.py",
577 "src/etc/gdb_load_rust_pretty_printers.py",
578 "src/etc/gdb_lookup.py",
579 "src/etc/gdb_providers.py",
580 "src/etc/lldb_batchmode.py",
581 "src/etc/lldb_lookup.py",
582 "src/etc/lldb_providers.py",
583 ];
584 for file in &pretty_printer_files {
585 let path = rust_src_dir.join(file);
586 stamp.add_path(&path);
587 }
588
589 stamp.add_dir(&rust_src_dir.join("src/etc/natvis"));
590
591 stamp.add_dir(&config.run_lib_path);
592
593 if let Some(ref rustdoc_path) = config.rustdoc_path {
594 stamp.add_path(&rustdoc_path);
595 stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
596 }
597
598 stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
600
601 stamp
602}
603
604fn modified_tests(config: &Config, dir: &Path) -> Result<Vec<PathBuf>, String> {
605 if !config.only_modified {
606 return Ok(vec![]);
607 }
608 let files =
609 get_git_modified_files(Some(dir), &vec!["rs", "stderr", "fixed"])?.unwrap_or(vec![]);
610 let untracked_files = get_git_untracked_files(None)?.unwrap_or(vec![]);
612
613 let all_paths = [&files[..], &untracked_files[..]].concat();
614 let full_paths = {
615 let mut full_paths: Vec<PathBuf> = all_paths
616 .into_iter()
617 .map(|f| PathBuf::from(f).with_extension("").with_extension("rs"))
618 .filter_map(|f| if Path::new(&f).exists() { f.canonicalize().ok() } else { None })
619 .collect();
620 full_paths.dedup();
621 full_paths.sort_unstable();
622 full_paths
623 };
624 Ok(full_paths)
625}
626
627fn collect_tests_from_dir(
628 config: Arc<Config>,
629 cache: &HeadersCache,
630 dir: &Path,
631 relative_dir_path: &Path,
632 inputs: &Stamp,
633 tests: &mut Vec<test::TestDescAndFn>,
634 found_paths: &mut BTreeSet<PathBuf>,
635 modified_tests: &Vec<PathBuf>,
636 poisoned: &mut bool,
637) -> io::Result<()> {
638 if dir.join("compiletest-ignore-dir").exists() {
640 return Ok(());
641 }
642
643 if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
644 let paths = TestPaths {
645 file: dir.to_path_buf(),
646 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
647 };
648 tests.extend(make_test(config, cache, &paths, inputs, poisoned));
649 return Ok(());
650 }
651
652 let build_dir = output_relative_path(&config, relative_dir_path);
659 fs::create_dir_all(&build_dir).unwrap();
660
661 for file in fs::read_dir(dir)? {
664 let file = file?;
665 let file_path = file.path();
666 let file_name = file.file_name();
667 if is_test(&file_name) && (!config.only_modified || modified_tests.contains(&file_path)) {
668 debug!("found test file: {:?}", file_path.display());
669 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
670 found_paths.insert(rel_test_path);
671 let paths =
672 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
673
674 tests.extend(make_test(config.clone(), cache, &paths, inputs, poisoned))
675 } else if file_path.is_dir() {
676 let relative_file_path = relative_dir_path.join(file.file_name());
677 if &file_name != "auxiliary" {
678 debug!("found directory: {:?}", file_path.display());
679 collect_tests_from_dir(
680 config.clone(),
681 cache,
682 &file_path,
683 &relative_file_path,
684 inputs,
685 tests,
686 found_paths,
687 modified_tests,
688 poisoned,
689 )?;
690 }
691 } else {
692 debug!("found other file/directory: {:?}", file_path.display());
693 }
694 }
695 Ok(())
696}
697
698pub fn is_test(file_name: &OsString) -> bool {
700 let file_name = file_name.to_str().unwrap();
701
702 if !file_name.ends_with(".rs") {
703 return false;
704 }
705
706 let invalid_prefixes = &[".", "#", "~"];
708 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
709}
710
711fn make_test(
712 config: Arc<Config>,
713 cache: &HeadersCache,
714 testpaths: &TestPaths,
715 inputs: &Stamp,
716 poisoned: &mut bool,
717) -> Vec<test::TestDescAndFn> {
718 let test_path = if config.mode == Mode::RunMake {
719 testpaths.file.join("Makefile")
721 } else {
722 PathBuf::from(&testpaths.file)
723 };
724 let early_props = EarlyProps::from_file(&config, &test_path);
725
726 let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
729 vec![None]
730 } else {
731 early_props.revisions.iter().map(Some).collect()
732 };
733
734 revisions
735 .into_iter()
736 .map(|revision| {
737 let src_file =
738 std::fs::File::open(&test_path).expect("open test file to parse ignores");
739 let cfg = revision.map(|v| &**v);
740 let test_name = crate::make_test_name(&config, testpaths, revision);
741 let mut desc = make_test_description(
742 &config, cache, test_name, &test_path, src_file, cfg, poisoned,
743 );
744 if !config.force_rerun {
746 desc.ignore |= is_up_to_date(
747 &config,
748 testpaths,
749 &early_props,
750 revision.map(|s| s.as_str()),
751 inputs,
752 );
753 }
754 test::TestDescAndFn {
755 desc,
756 testfn: make_test_closure(config.clone(), testpaths, revision),
757 }
758 })
759 .collect()
760}
761
762fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
763 output_base_dir(config, testpaths, revision).join("stamp")
764}
765
766fn files_related_to_test(
767 config: &Config,
768 testpaths: &TestPaths,
769 props: &EarlyProps,
770 revision: Option<&str>,
771) -> Vec<PathBuf> {
772 let mut related = vec![];
773
774 if testpaths.file.is_dir() {
775 for entry in WalkDir::new(&testpaths.file) {
777 let path = entry.unwrap().into_path();
778 if path.is_file() {
779 related.push(path);
780 }
781 }
782 } else {
783 related.push(testpaths.file.clone());
784 }
785
786 for aux in &props.aux {
787 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
788 related.push(path);
789 }
790
791 for extension in UI_EXTENSIONS {
793 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
794 related.push(path);
795 }
796
797 related
798}
799
800fn is_up_to_date(
801 config: &Config,
802 testpaths: &TestPaths,
803 props: &EarlyProps,
804 revision: Option<&str>,
805 inputs: &Stamp,
806) -> bool {
807 let stamp_name = stamp(config, testpaths, revision);
808 let contents = match fs::read_to_string(&stamp_name) {
810 Ok(f) => f,
811 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
812 Err(_) => return false,
813 };
814 let expected_hash = runtest::compute_stamp_hash(config);
815 if contents != expected_hash {
816 return false;
817 }
818
819 let mut inputs = inputs.clone();
821 for path in files_related_to_test(config, testpaths, props, revision) {
822 inputs.add_path(&path);
823 }
824
825 inputs < Stamp::from_path(&stamp_name)
826}
827
828#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
829struct Stamp {
830 time: SystemTime,
831}
832
833impl Stamp {
834 fn from_path(path: &Path) -> Self {
835 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
836 stamp.add_path(path);
837 stamp
838 }
839
840 fn add_path(&mut self, path: &Path) {
841 let modified = fs::metadata(path)
842 .and_then(|metadata| metadata.modified())
843 .unwrap_or(SystemTime::UNIX_EPOCH);
844 self.time = self.time.max(modified);
845 }
846
847 fn add_dir(&mut self, path: &Path) {
848 for entry in WalkDir::new(path) {
849 let entry = entry.unwrap();
850 if entry.file_type().is_file() {
851 let modified = entry
852 .metadata()
853 .ok()
854 .and_then(|metadata| metadata.modified().ok())
855 .unwrap_or(SystemTime::UNIX_EPOCH);
856 self.time = self.time.max(modified);
857 }
858 }
859 }
860}
861
862fn make_test_name(
863 config: &Config,
864 testpaths: &TestPaths,
865 revision: Option<&String>,
866) -> test::TestName {
867 let root_directory = config.src_base.parent().unwrap().parent().unwrap();
870 let path = testpaths.file.strip_prefix(root_directory).unwrap();
871 let debugger = match config.debugger {
872 Some(d) => format!("-{}", d),
873 None => String::new(),
874 };
875 let mode_suffix = match config.compare_mode {
876 Some(ref mode) => format!(" ({})", mode.to_str()),
877 None => String::new(),
878 };
879
880 test::DynTestName(format!(
881 "[{}{}{}] {}{}",
882 config.mode,
883 debugger,
884 mode_suffix,
885 path.display(),
886 revision.map_or("".to_string(), |rev| format!("#{}", rev))
887 ))
888}
889
890fn make_test_closure(
891 config: Arc<Config>,
892 testpaths: &TestPaths,
893 revision: Option<&String>,
894) -> test::TestFn {
895 let config = config.clone();
896 let testpaths = testpaths.clone();
897 let revision = revision.cloned();
898 test::DynTestFn(Box::new(move || {
899 runtest::run(config, &testpaths, revision.as_deref());
900 Ok(())
901 }))
902}
903
904fn is_android_gdb_target(target: &str) -> bool {
907 matches!(
908 &target[..],
909 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
910 )
911}
912
913fn is_pc_windows_msvc_target(target: &str) -> bool {
915 target.ends_with("-pc-windows-msvc")
916}
917
918fn find_cdb(target: &str) -> Option<OsString> {
919 if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
920 return None;
921 }
922
923 let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
924 let cdb_arch = if cfg!(target_arch = "x86") {
925 "x86"
926 } else if cfg!(target_arch = "x86_64") {
927 "x64"
928 } else if cfg!(target_arch = "aarch64") {
929 "arm64"
930 } else if cfg!(target_arch = "arm") {
931 "arm"
932 } else {
933 return None; };
935
936 let mut path = PathBuf::new();
937 path.push(pf86);
938 path.push(r"Windows Kits\10\Debuggers"); path.push(cdb_arch);
940 path.push(r"cdb.exe");
941
942 if !path.exists() {
943 return None;
944 }
945
946 Some(path.into_os_string())
947}
948
949fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
951 let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
952
953 let mut version = None;
954 if let Some(cdb) = cdb.as_ref() {
955 if let Ok(output) = Command::new(cdb).arg("/version").output() {
956 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
957 version = extract_cdb_version(&first_line);
958 }
959 }
960 }
961
962 (cdb, version)
963}
964
965fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
966 let version = full_version_line.rsplit(' ').next()?;
968 let mut components = version.split('.');
969 let major: u16 = components.next().unwrap().parse().unwrap();
970 let minor: u16 = components.next().unwrap().parse().unwrap();
971 let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
972 let build: u16 = components.next().unwrap_or("0").parse().unwrap();
973 Some([major, minor, patch, build])
974}
975
976fn analyze_gdb(
978 gdb: Option<String>,
979 target: &str,
980 android_cross_path: &PathBuf,
981) -> (Option<String>, Option<u32>, bool) {
982 #[cfg(not(windows))]
983 const GDB_FALLBACK: &str = "gdb";
984 #[cfg(windows)]
985 const GDB_FALLBACK: &str = "gdb.exe";
986
987 const MIN_GDB_WITH_RUST: u32 = 7011010;
988
989 let fallback_gdb = || {
990 if is_android_gdb_target(target) {
991 let mut gdb_path = match android_cross_path.to_str() {
992 Some(x) => x.to_owned(),
993 None => panic!("cannot find android cross path"),
994 };
995 gdb_path.push_str("/bin/gdb");
996 gdb_path
997 } else {
998 GDB_FALLBACK.to_owned()
999 }
1000 };
1001
1002 let gdb = match gdb {
1003 None => fallback_gdb(),
1004 Some(ref s) if s.is_empty() => fallback_gdb(), Some(ref s) => s.to_owned(),
1006 };
1007
1008 let mut version_line = None;
1009 if let Ok(output) = Command::new(&gdb).arg("--version").output() {
1010 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
1011 version_line = Some(first_line.to_string());
1012 }
1013 }
1014
1015 let version = match version_line {
1016 Some(line) => extract_gdb_version(&line),
1017 None => return (None, None, false),
1018 };
1019
1020 let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
1021
1022 (Some(gdb), version, gdb_native_rust)
1023}
1024
1025fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
1026 let full_version_line = full_version_line.trim();
1027
1028 let unbracketed_part = full_version_line.split('[').next().unwrap();
1040 let mut splits = unbracketed_part.trim_end().rsplit(' ');
1041 let version_string = splits.next().unwrap();
1042
1043 let mut splits = version_string.split('.');
1044 let major = splits.next().unwrap();
1045 let minor = splits.next().unwrap();
1046 let patch = splits.next();
1047
1048 let major: u32 = major.parse().unwrap();
1049 let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
1050 None => {
1051 let minor = minor.parse().unwrap();
1052 let patch: u32 = match patch {
1053 Some(patch) => match patch.find(not_a_digit) {
1054 None => patch.parse().unwrap(),
1055 Some(idx) if idx > 3 => 0,
1056 Some(idx) => patch[..idx].parse().unwrap(),
1057 },
1058 None => 0,
1059 };
1060 (minor, patch)
1061 }
1062 Some(idx) => {
1064 let minor = minor[..idx].parse().unwrap();
1065 (minor, 0)
1066 }
1067 };
1068
1069 Some(((major * 1000) + minor) * 1000 + patch)
1070}
1071
1072fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
1074 let full_version_line = full_version_line.trim();
1095
1096 if let Some(apple_ver) =
1097 full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
1098 {
1099 if let Some(idx) = apple_ver.find(not_a_digit) {
1100 let version: u32 = apple_ver[..idx].parse().unwrap();
1101 return Some((version, full_version_line.contains("rust-enabled")));
1102 }
1103 } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
1104 if let Some(idx) = lldb_ver.find(not_a_digit) {
1105 let version: u32 = lldb_ver[..idx].parse().ok()?;
1106 return Some((version * 100, full_version_line.contains("rust-enabled")));
1107 }
1108 }
1109 None
1110}
1111
1112fn not_a_digit(c: char) -> bool {
1113 !c.is_digit(10)
1114}
1115
1116fn check_overlapping_tests(found_paths: &BTreeSet<PathBuf>) {
1117 let mut collisions = Vec::new();
1118 for path in found_paths {
1119 for ancestor in path.ancestors().skip(1) {
1120 if found_paths.contains(ancestor) {
1121 collisions.push((path, ancestor));
1122 }
1123 }
1124 }
1125 if !collisions.is_empty() {
1126 let collisions: String = collisions
1127 .into_iter()
1128 .map(|(path, check_parent)| format!("test {path:?} clashes with {check_parent:?}\n"))
1129 .collect();
1130 panic!(
1131 "{collisions}\n\
1132 Tests cannot have overlapping names. Make sure they use unique prefixes."
1133 );
1134 }
1135}