hkalbasi_rustc_ap_compiletest/
lib.rs

1// The `test` crate is the only unstable feature
2// allowed here, just to share similar code.
3#![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        // Avoid spawning an external command when we know tidy won't be used.
211        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 we want to collect rustfix coverage information,
363    // we first make sure that the coverage file does not exist.
364    // It will be created later on.
365    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    // sadly osx needs some file descriptor limits raised for running tests in
376    // parallel (especially when we have lots and lots of child processes).
377    // For context, see #8904
378    unsafe {
379        raise_fd_limit::raise_fd_limit();
380    }
381    // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
382    // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
383    env::set_var("__COMPAT_LAYER", "RunAsInvoker");
384
385    // Let tests know which target they're running as
386    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        // Debugging emscripten code doesn't make sense today
393        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            // We want to report that the tests failed, but we also want to give
416            // some indication of just what tests we were running. Especially on
417            // CI, where there can be cross-compiled tests for a lot of
418            // architectures, without this critical information it can be quite
419            // easy to miss which tests failed, and as such fail to reproduce
420            // the failure locally.
421
422            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            // We don't know if tests passed or not, but if there was an error
439            // during testing we don't want to just succeed (we may not have
440            // tested something), so fail.
441            //
442            // This should realistically "never" happen, so don't try to make
443            // this a pretty error message.
444            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        // android debug-info test uses remote debugger so, we test 1 thread
478        // at once as they're all sharing the same TCP port to communicate
479        // over.
480        //
481        // we should figure out how to lift this restriction! (run them all
482        // on different ports allocated dynamically).
483        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
568/// Returns a stamp constructed from input files common to all test cases.
569fn 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    // Relevant pretty printer files
575    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    // Compiletest itself.
599    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    // Add new test cases to the list, it will be convenient in daily development.
611    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    // Ignore directories that contain a file named `compiletest-ignore-dir`.
639    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    // If we find a test foo/bar.rs, we have to build the
653    // output directory `$build/foo` so we can write
654    // `$build/foo/bar` into it. We do this *now* in this
655    // sequential loop because otherwise, if we do it in the
656    // tests themselves, they race for the privilege of
657    // creating the directories and sometimes fail randomly.
658    let build_dir = output_relative_path(&config, relative_dir_path);
659    fs::create_dir_all(&build_dir).unwrap();
660
661    // Add each `.rs` file as a test, and recurse further on any
662    // subdirectories we find, except for `aux` directories.
663    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
698/// Returns true if `file_name` looks like a proper test file name.
699pub 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    // `.`, `#`, and `~` are common temp-file prefixes.
707    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        // Parse directives in the Makefile
720        testpaths.file.join("Makefile")
721    } else {
722        PathBuf::from(&testpaths.file)
723    };
724    let early_props = EarlyProps::from_file(&config, &test_path);
725
726    // Incremental tests are special, they inherently cannot be run in parallel.
727    // `runtest::run` will be responsible for iterating over revisions.
728    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            // Ignore tests that already run and are up to date with respect to inputs.
745            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        // run-make tests use their individual directory
776        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    // UI test files.
792    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    // Check hash.
809    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    // Check timestamps.
820    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    // Print the name of the file, relative to the repository root.
868    // `src_base` looks like `/path/to/rust/tests/ui`
869    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
904/// Returns `true` if the given target is an Android target for the
905/// purposes of GDB testing.
906fn is_android_gdb_target(target: &str) -> bool {
907    matches!(
908        &target[..],
909        "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
910    )
911}
912
913/// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
914fn 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; // No compatible CDB.exe in the Windows 10 SDK
934    };
935
936    let mut path = PathBuf::new();
937    path.push(pf86);
938    path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
939    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
949/// Returns Path to CDB
950fn 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    // Example full_version_line: "cdb version 10.0.18362.1"
967    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
976/// Returns (Path to GDB, GDB Version, GDB has Rust Support)
977fn 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(), // may be empty if configure found no gdb
1005        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    // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
1029    // of the ? sections being optional
1030
1031    // We will parse up to 3 digits for each component, ignoring the date
1032
1033    // We skip text in parentheses.  This avoids accidentally parsing
1034    // the openSUSE version, which looks like:
1035    //  GNU gdb (GDB; openSUSE Leap 15.0) 8.1
1036    // This particular form is documented in the GNU coding standards:
1037    // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
1038
1039    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        // There is no patch version after minor-date (e.g. "4-2012").
1063        Some(idx) => {
1064            let minor = minor[..idx].parse().unwrap();
1065            (minor, 0)
1066        }
1067    };
1068
1069    Some(((major * 1000) + minor) * 1000 + patch)
1070}
1071
1072/// Returns (LLDB version, LLDB is rust-enabled)
1073fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
1074    // Extract the major LLDB version from the given version string.
1075    // LLDB version strings are different for Apple and non-Apple platforms.
1076    // The Apple variant looks like this:
1077    //
1078    // LLDB-179.5 (older versions)
1079    // lldb-300.2.51 (new versions)
1080    //
1081    // We are only interested in the major version number, so this function
1082    // will return `Some(179)` and `Some(300)` respectively.
1083    //
1084    // Upstream versions look like:
1085    // lldb version 6.0.1
1086    //
1087    // There doesn't seem to be a way to correlate the Apple version
1088    // with the upstream version, and since the tests were originally
1089    // written against Apple versions, we make a fake Apple version by
1090    // multiplying the first number by 100.  This is a hack, but
1091    // normally fine because the only non-Apple version we test is
1092    // rust-enabled.
1093
1094    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}