rust/src/bootstrap/test.rs

2103 lines
66 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Implementation of the test-related targets of the build system.
//!
//! This file implements the various regression test suites that we execute on
//! our CI.
use std::env;
use std::ffi::OsString;
use std::fmt;
use std::fs;
use std::iter;
use std::path::{Path, PathBuf};
use std::process::Command;
use build_helper::{self, output, t};
use crate::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step};
use crate::cache::{Interned, INTERNER};
use crate::compile;
use crate::dist;
use crate::flags::Subcommand;
use crate::native;
use crate::tool::{self, Tool, SourceType};
use crate::toolstate::ToolState;
use crate::util::{self, dylib_path, dylib_path_var};
use crate::Crate as CargoCrate;
use crate::{DocTests, Mode, GitRepo};
const ADB_TEST_DIR: &str = "/data/tmp/work";
/// The two modes of the test runner; tests or benchmarks.
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord)]
pub enum TestKind {
/// Run `cargo test`.
Test,
/// Run `cargo bench`.
Bench,
}
impl From<Kind> for TestKind {
fn from(kind: Kind) -> Self {
match kind {
Kind::Test => TestKind::Test,
Kind::Bench => TestKind::Bench,
_ => panic!("unexpected kind in crate: {:?}", kind),
}
}
}
impl TestKind {
// Return the cargo subcommand for this test kind
fn subcommand(self) -> &'static str {
match self {
TestKind::Test => "test",
TestKind::Bench => "bench",
}
}
}
impl fmt::Display for TestKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match *self {
TestKind::Test => "Testing",
TestKind::Bench => "Benchmarking",
})
}
}
fn try_run(builder: &Builder<'_>, cmd: &mut Command) -> bool {
if !builder.fail_fast {
if !builder.try_run(cmd) {
let mut failures = builder.delayed_failures.borrow_mut();
failures.push(format!("{:?}", cmd));
return false;
}
} else {
builder.run(cmd);
}
true
}
fn try_run_quiet(builder: &Builder<'_>, cmd: &mut Command) -> bool {
if !builder.fail_fast {
if !builder.try_run_quiet(cmd) {
let mut failures = builder.delayed_failures.borrow_mut();
failures.push(format!("{:?}", cmd));
return false;
}
} else {
builder.run_quiet(cmd);
}
true
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Linkcheck {
host: Interned<String>,
}
impl Step for Linkcheck {
type Output = ();
const ONLY_HOSTS: bool = true;
const DEFAULT: bool = true;
/// Runs the `linkchecker` tool as compiled in `stage` by the `host` compiler.
///
/// This tool in `src/tools` will verify the validity of all our links in the
/// documentation to ensure we don't have a bunch of dead ones.
fn run(self, builder: &Builder<'_>) {
let host = self.host;
builder.info(&format!("Linkcheck ({})", host));
builder.default_doc(None);
let _time = util::timeit(&builder);
try_run(
builder,
builder
.tool_cmd(Tool::Linkchecker)
.arg(builder.out.join(host).join("doc")),
);
}
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
let builder = run.builder;
run.path("src/tools/linkchecker")
.default_condition(builder.config.docs)
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Linkcheck { host: run.target });
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Cargotest {
stage: u32,
host: Interned<String>,
}
impl Step for Cargotest {
type Output = ();
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/cargotest")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Cargotest {
stage: run.builder.top_stage,
host: run.target,
});
}
/// Runs the `cargotest` tool as compiled in `stage` by the `host` compiler.
///
/// This tool in `src/tools` will check out a few Rust projects and run `cargo
/// test` to ensure that we don't regress the test suites there.
fn run(self, builder: &Builder<'_>) {
let compiler = builder.compiler(self.stage, self.host);
builder.ensure(compile::Rustc {
compiler,
target: compiler.host,
});
// Note that this is a short, cryptic, and not scoped directory name. This
// is currently to minimize the length of path on Windows where we otherwise
// quickly run into path name limit constraints.
let out_dir = builder.out.join("ct");
t!(fs::create_dir_all(&out_dir));
let _time = util::timeit(&builder);
let mut cmd = builder.tool_cmd(Tool::CargoTest);
try_run(
builder,
cmd.arg(&builder.initial_cargo)
.arg(&out_dir)
.env("RUSTC", builder.rustc(compiler))
.env("RUSTDOC", builder.rustdoc(compiler)),
);
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Cargo {
stage: u32,
host: Interned<String>,
}
impl Step for Cargo {
type Output = ();
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/cargo")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Cargo {
stage: run.builder.top_stage,
host: run.target,
});
}
/// Runs `cargo test` for `cargo` packaged with Rust.
fn run(self, builder: &Builder<'_>) {
let compiler = builder.compiler(self.stage, self.host);
builder.ensure(tool::Cargo {
compiler,
target: self.host,
});
let mut cargo = tool::prepare_tool_cargo(builder,
compiler,
Mode::ToolRustc,
self.host,
"test",
"src/tools/cargo",
SourceType::Submodule,
&[]);
if !builder.fail_fast {
cargo.arg("--no-fail-fast");
}
// Don't run cross-compile tests, we may not have cross-compiled libstd libs
// available.
cargo.env("CFG_DISABLE_CROSS_TESTS", "1");
// Disable a test that has issues with mingw.
cargo.env("CARGO_TEST_DISABLE_GIT_CLI", "1");
// Forcibly disable tests using nightly features since any changes to
// those features won't be able to land.
cargo.env("CARGO_TEST_DISABLE_NIGHTLY", "1");
try_run(
builder,
cargo.env("PATH", &path_for_cargo(builder, compiler)),
);
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Rls {
stage: u32,
host: Interned<String>,
}
impl Step for Rls {
type Output = ();
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/rls")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Rls {
stage: run.builder.top_stage,
host: run.target,
});
}
/// Runs `cargo test` for the rls.
fn run(self, builder: &Builder<'_>) {
let stage = self.stage;
let host = self.host;
let compiler = builder.compiler(stage, host);
let build_result = builder.ensure(tool::Rls {
compiler,
target: self.host,
extra_features: Vec::new(),
});
if build_result.is_none() {
eprintln!("failed to test rls: could not build");
return;
}
let mut cargo = tool::prepare_tool_cargo(builder,
compiler,
Mode::ToolRustc,
host,
"test",
"src/tools/rls",
SourceType::Submodule,
&[]);
builder.add_rustc_lib_path(compiler, &mut cargo);
cargo.arg("--")
.args(builder.config.cmd.test_args());
if try_run(builder, &mut cargo) {
builder.save_toolstate("rls", ToolState::TestPass);
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Rustfmt {
stage: u32,
host: Interned<String>,
}
impl Step for Rustfmt {
type Output = ();
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/rustfmt")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Rustfmt {
stage: run.builder.top_stage,
host: run.target,
});
}
/// Runs `cargo test` for rustfmt.
fn run(self, builder: &Builder<'_>) {
let stage = self.stage;
let host = self.host;
let compiler = builder.compiler(stage, host);
let build_result = builder.ensure(tool::Rustfmt {
compiler,
target: self.host,
extra_features: Vec::new(),
});
if build_result.is_none() {
eprintln!("failed to test rustfmt: could not build");
return;
}
let mut cargo = tool::prepare_tool_cargo(builder,
compiler,
Mode::ToolRustc,
host,
"test",
"src/tools/rustfmt",
SourceType::Submodule,
&[]);
let dir = testdir(builder, compiler.host);
t!(fs::create_dir_all(&dir));
cargo.env("RUSTFMT_TEST_DIR", dir);
builder.add_rustc_lib_path(compiler, &mut cargo);
if try_run(builder, &mut cargo) {
builder.save_toolstate("rustfmt", ToolState::TestPass);
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Miri {
stage: u32,
host: Interned<String>,
}
impl Step for Miri {
type Output = ();
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/miri")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Miri {
stage: run.builder.top_stage,
host: run.target,
});
}
/// Runs `cargo test` for miri.
fn run(self, builder: &Builder<'_>) {
let stage = self.stage;
let host = self.host;
let compiler = builder.compiler(stage, host);
let miri = builder.ensure(tool::Miri {
compiler,
target: self.host,
extra_features: Vec::new(),
});
if let Some(miri) = miri {
// # Run `cargo miri setup`.
// As a side-effect, this will install xargo.
let mut cargo = tool::prepare_tool_cargo(
builder,
compiler,
Mode::ToolRustc,
host,
"run",
"src/tools/miri",
SourceType::Submodule,
&[],
);
cargo
.arg("--bin")
.arg("cargo-miri")
.arg("--")
.arg("miri")
.arg("setup");
// Tell `cargo miri` not to worry about the sysroot mismatch (we built with
// stage1 but run with stage2).
cargo.env("MIRI_SKIP_SYSROOT_CHECK", "1");
// Tell `cargo miri setup` where to find the sources.
cargo.env("XARGO_RUST_SRC", builder.src.join("src"));
// Debug things.
cargo.env("RUST_BACKTRACE", "1");
// Configure `cargo install` path, and let cargo-miri know that that's where
// xargo ends up.
cargo.env("CARGO_INSTALL_ROOT", &builder.out); // cargo adds a `bin/`
cargo.env("XARGO", builder.out.join("bin").join("xargo"));
if !try_run(builder, &mut cargo) {
return;
}
// # Determine where Miri put its sysroot.
// To this end, we run `cargo miri setup --env` and capture the output.
// (We do this separately from the above so that when the setup actually
// happens we get some output.)
// We re-use the `cargo` from above.
cargo.arg("--env");
// FIXME: Is there a way in which we can re-use the usual `run` helpers?
let miri_sysroot = if builder.config.dry_run {
String::new()
} else {
builder.verbose(&format!("running: {:?}", cargo));
let out = cargo.output()
.expect("We already ran `cargo miri setup` before and that worked");
assert!(out.status.success(), "`cargo miri setup` returned with non-0 exit code");
// Output is "MIRI_SYSROOT=<str>\n".
let stdout = String::from_utf8(out.stdout)
.expect("`cargo miri setup` stdout is not valid UTF-8");
let stdout = stdout.trim();
builder.verbose(&format!("`cargo miri setup --env` returned: {:?}", stdout));
let sysroot = stdout.splitn(2, '=')
.nth(1).expect("`cargo miri setup` stdout did not contain '='");
sysroot.to_owned()
};
// # Run `cargo test`.
let mut cargo = tool::prepare_tool_cargo(
builder,
compiler,
Mode::ToolRustc,
host,
"test",
"src/tools/miri",
SourceType::Submodule,
&[],
);
// miri tests need to know about the stage sysroot
cargo.env("MIRI_SYSROOT", miri_sysroot);
cargo.env("RUSTC_TEST_SUITE", builder.rustc(compiler));
cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(compiler));
cargo.env("MIRI_PATH", miri);
builder.add_rustc_lib_path(compiler, &mut cargo);
if !try_run(builder, &mut cargo) {
return;
}
// # Done!
builder.save_toolstate("miri", ToolState::TestPass);
} else {
eprintln!("failed to test miri: could not build");
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct CompiletestTest {
host: Interned<String>,
}
impl Step for CompiletestTest {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/compiletest")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(CompiletestTest {
host: run.target,
});
}
/// Runs `cargo test` for compiletest.
fn run(self, builder: &Builder<'_>) {
let host = self.host;
let compiler = builder.compiler(0, host);
let mut cargo = tool::prepare_tool_cargo(builder,
compiler,
Mode::ToolBootstrap,
host,
"test",
"src/tools/compiletest",
SourceType::InTree,
&[]);
try_run(builder, &mut cargo);
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Clippy {
stage: u32,
host: Interned<String>,
}
impl Step for Clippy {
type Output = ();
const ONLY_HOSTS: bool = true;
const DEFAULT: bool = false;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/clippy")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Clippy {
stage: run.builder.top_stage,
host: run.target,
});
}
/// Runs `cargo test` for clippy.
fn run(self, builder: &Builder<'_>) {
let stage = self.stage;
let host = self.host;
let compiler = builder.compiler(stage, host);
let clippy = builder.ensure(tool::Clippy {
compiler,
target: self.host,
extra_features: Vec::new(),
});
if let Some(clippy) = clippy {
let mut cargo = tool::prepare_tool_cargo(builder,
compiler,
Mode::ToolRustc,
host,
"test",
"src/tools/clippy",
SourceType::Submodule,
&[]);
// clippy tests need to know about the stage sysroot
cargo.env("SYSROOT", builder.sysroot(compiler));
cargo.env("RUSTC_TEST_SUITE", builder.rustc(compiler));
cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(compiler));
let host_libs = builder
.stage_out(compiler, Mode::ToolRustc)
.join(builder.cargo_dir());
cargo.env("HOST_LIBS", host_libs);
// clippy tests need to find the driver
cargo.env("CLIPPY_DRIVER_PATH", clippy);
builder.add_rustc_lib_path(compiler, &mut cargo);
if try_run(builder, &mut cargo) {
builder.save_toolstate("clippy-driver", ToolState::TestPass);
}
} else {
eprintln!("failed to test clippy: could not build");
}
}
}
fn path_for_cargo(builder: &Builder<'_>, compiler: Compiler) -> OsString {
// Configure PATH to find the right rustc. NB. we have to use PATH
// and not RUSTC because the Cargo test suite has tests that will
// fail if rustc is not spelled `rustc`.
let path = builder.sysroot(compiler).join("bin");
let old_path = env::var_os("PATH").unwrap_or_default();
env::join_paths(iter::once(path).chain(env::split_paths(&old_path))).expect("")
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct RustdocTheme {
pub compiler: Compiler,
}
impl Step for RustdocTheme {
type Output = ();
const DEFAULT: bool = true;
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/rustdoc-themes")
}
fn make_run(run: RunConfig<'_>) {
let compiler = run.builder.compiler(run.builder.top_stage, run.host);
run.builder.ensure(RustdocTheme { compiler });
}
fn run(self, builder: &Builder<'_>) {
let rustdoc = builder.out.join("bootstrap/debug/rustdoc");
let mut cmd = builder.tool_cmd(Tool::RustdocTheme);
cmd.arg(rustdoc.to_str().unwrap())
.arg(
builder
.src
.join("src/librustdoc/html/static/themes")
.to_str()
.unwrap(),
)
.env("RUSTC_STAGE", self.compiler.stage.to_string())
.env("RUSTC_SYSROOT", builder.sysroot(self.compiler))
.env(
"RUSTDOC_LIBDIR",
builder.sysroot_libdir(self.compiler, self.compiler.host),
)
.env("CFG_RELEASE_CHANNEL", &builder.config.channel)
.env("RUSTDOC_REAL", builder.rustdoc(self.compiler))
.env("RUSTDOC_CRATE_VERSION", builder.rust_version())
.env("RUSTC_BOOTSTRAP", "1");
if let Some(linker) = builder.linker(self.compiler.host) {
cmd.env("RUSTC_TARGET_LINKER", linker);
}
try_run(builder, &mut cmd);
}
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct RustdocJSStd {
pub host: Interned<String>,
pub target: Interned<String>,
}
impl Step for RustdocJSStd {
type Output = ();
const DEFAULT: bool = true;
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/test/rustdoc-js-std")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(RustdocJSStd {
host: run.host,
target: run.target,
});
}
fn run(self, builder: &Builder<'_>) {
if let Some(ref nodejs) = builder.config.nodejs {
let mut command = Command::new(nodejs);
command.args(&["src/tools/rustdoc-js-std/tester.js", &*self.host]);
builder.ensure(crate::doc::Std {
target: self.target,
stage: builder.top_stage,
});
builder.run(&mut command);
} else {
builder.info(
"No nodejs found, skipping \"src/test/rustdoc-js-std\" tests"
);
}
}
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct RustdocJSNotStd {
pub host: Interned<String>,
pub target: Interned<String>,
pub compiler: Compiler,
}
impl Step for RustdocJSNotStd {
type Output = ();
const DEFAULT: bool = true;
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/test/rustdoc-js")
}
fn make_run(run: RunConfig<'_>) {
let compiler = run.builder.compiler(run.builder.top_stage, run.host);
run.builder.ensure(RustdocJSNotStd {
host: run.host,
target: run.target,
compiler,
});
}
fn run(self, builder: &Builder<'_>) {
if builder.config.nodejs.is_some() {
builder.ensure(Compiletest {
compiler: self.compiler,
target: self.target,
mode: "js-doc-test",
suite: "rustdoc-js",
path: None,
compare_mode: None,
});
} else {
builder.info(
"No nodejs found, skipping \"src/test/rustdoc-js\" tests"
);
}
}
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct RustdocUi {
pub host: Interned<String>,
pub target: Interned<String>,
pub compiler: Compiler,
}
impl Step for RustdocUi {
type Output = ();
const DEFAULT: bool = true;
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/test/rustdoc-ui")
}
fn make_run(run: RunConfig<'_>) {
let compiler = run.builder.compiler(run.builder.top_stage, run.host);
run.builder.ensure(RustdocUi {
host: run.host,
target: run.target,
compiler,
});
}
fn run(self, builder: &Builder<'_>) {
builder.ensure(Compiletest {
compiler: self.compiler,
target: self.target,
mode: "ui",
suite: "rustdoc-ui",
path: Some("src/test/rustdoc-ui"),
compare_mode: None,
})
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Tidy;
impl Step for Tidy {
type Output = ();
const DEFAULT: bool = true;
const ONLY_HOSTS: bool = true;
/// Runs the `tidy` tool.
///
/// This tool in `src/tools` checks up on various bits and pieces of style and
/// otherwise just implements a few lint-like checks that are specific to the
/// compiler itself.
fn run(self, builder: &Builder<'_>) {
let mut cmd = builder.tool_cmd(Tool::Tidy);
cmd.arg(builder.src.join("src"));
cmd.arg(&builder.initial_cargo);
if !builder.config.vendor {
cmd.arg("--no-vendor");
}
if builder.is_verbose() {
cmd.arg("--verbose");
}
builder.info("tidy check");
try_run(builder, &mut cmd);
}
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/tidy")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Tidy);
}
}
fn testdir(builder: &Builder<'_>, host: Interned<String>) -> PathBuf {
builder.out.join(host).join("test")
}
macro_rules! default_test {
($name:ident { path: $path:expr, mode: $mode:expr, suite: $suite:expr }) => {
test!($name { path: $path, mode: $mode, suite: $suite, default: true, host: false });
}
}
macro_rules! default_test_with_compare_mode {
($name:ident { path: $path:expr, mode: $mode:expr, suite: $suite:expr,
compare_mode: $compare_mode:expr }) => {
test_with_compare_mode!($name { path: $path, mode: $mode, suite: $suite, default: true,
host: false, compare_mode: $compare_mode });
}
}
macro_rules! host_test {
($name:ident { path: $path:expr, mode: $mode:expr, suite: $suite:expr }) => {
test!($name { path: $path, mode: $mode, suite: $suite, default: true, host: true });
}
}
macro_rules! test {
($name:ident { path: $path:expr, mode: $mode:expr, suite: $suite:expr, default: $default:expr,
host: $host:expr }) => {
test_definitions!($name { path: $path, mode: $mode, suite: $suite, default: $default,
host: $host, compare_mode: None });
}
}
macro_rules! test_with_compare_mode {
($name:ident { path: $path:expr, mode: $mode:expr, suite: $suite:expr, default: $default:expr,
host: $host:expr, compare_mode: $compare_mode:expr }) => {
test_definitions!($name { path: $path, mode: $mode, suite: $suite, default: $default,
host: $host, compare_mode: Some($compare_mode) });
}
}
macro_rules! test_definitions {
($name:ident {
path: $path:expr,
mode: $mode:expr,
suite: $suite:expr,
default: $default:expr,
host: $host:expr,
compare_mode: $compare_mode:expr
}) => {
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct $name {
pub compiler: Compiler,
pub target: Interned<String>,
}
impl Step for $name {
type Output = ();
const DEFAULT: bool = $default;
const ONLY_HOSTS: bool = $host;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.suite_path($path)
}
fn make_run(run: RunConfig<'_>) {
let compiler = run.builder.compiler(run.builder.top_stage, run.host);
run.builder.ensure($name {
compiler,
target: run.target,
});
}
fn run(self, builder: &Builder<'_>) {
builder.ensure(Compiletest {
compiler: self.compiler,
target: self.target,
mode: $mode,
suite: $suite,
path: Some($path),
compare_mode: $compare_mode,
})
}
}
}
}
default_test_with_compare_mode!(Ui {
path: "src/test/ui",
mode: "ui",
suite: "ui",
compare_mode: "nll"
});
default_test!(CompileFail {
path: "src/test/compile-fail",
mode: "compile-fail",
suite: "compile-fail"
});
default_test!(RunFail {
path: "src/test/run-fail",
mode: "run-fail",
suite: "run-fail"
});
default_test!(RunPassValgrind {
path: "src/test/run-pass-valgrind",
mode: "run-pass-valgrind",
suite: "run-pass-valgrind"
});
default_test!(MirOpt {
path: "src/test/mir-opt",
mode: "mir-opt",
suite: "mir-opt"
});
default_test!(Codegen {
path: "src/test/codegen",
mode: "codegen",
suite: "codegen"
});
default_test!(CodegenUnits {
path: "src/test/codegen-units",
mode: "codegen-units",
suite: "codegen-units"
});
default_test!(Incremental {
path: "src/test/incremental",
mode: "incremental",
suite: "incremental"
});
default_test!(Debuginfo {
path: "src/test/debuginfo",
mode: "debuginfo",
suite: "debuginfo"
});
host_test!(UiFullDeps {
path: "src/test/ui-fulldeps",
mode: "ui",
suite: "ui-fulldeps"
});
host_test!(Rustdoc {
path: "src/test/rustdoc",
mode: "rustdoc",
suite: "rustdoc"
});
host_test!(Pretty {
path: "src/test/pretty",
mode: "pretty",
suite: "pretty"
});
test!(RunFailPretty {
path: "src/test/run-fail/pretty",
mode: "pretty",
suite: "run-fail",
default: false,
host: true
});
test!(RunPassValgrindPretty {
path: "src/test/run-pass-valgrind/pretty",
mode: "pretty",
suite: "run-pass-valgrind",
default: false,
host: true
});
default_test!(RunMake {
path: "src/test/run-make",
mode: "run-make",
suite: "run-make"
});
host_test!(RunMakeFullDeps {
path: "src/test/run-make-fulldeps",
mode: "run-make",
suite: "run-make-fulldeps"
});
default_test!(Assembly {
path: "src/test/assembly",
mode: "assembly",
suite: "assembly"
});
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
struct Compiletest {
compiler: Compiler,
target: Interned<String>,
mode: &'static str,
suite: &'static str,
path: Option<&'static str>,
compare_mode: Option<&'static str>,
}
impl Step for Compiletest {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.never()
}
/// Executes the `compiletest` tool to run a suite of tests.
///
/// Compiles all tests with `compiler` for `target` with the specified
/// compiletest `mode` and `suite` arguments. For example `mode` can be
/// "run-pass" or `suite` can be something like `debuginfo`.
fn run(self, builder: &Builder<'_>) {
let compiler = self.compiler;
let target = self.target;
let mode = self.mode;
let suite = self.suite;
// Path for test suite
let suite_path = self.path.unwrap_or("");
// Skip codegen tests if they aren't enabled in configuration.
if !builder.config.codegen_tests && suite == "codegen" {
return;
}
if suite == "debuginfo" {
let msvc = builder.config.build.contains("msvc");
if mode == "debuginfo" {
return builder.ensure(Compiletest {
mode: if msvc { "debuginfo-cdb" } else { "debuginfo-gdb+lldb" },
..self
});
}
builder.ensure(dist::DebuggerScripts {
sysroot: builder.sysroot(compiler),
host: target,
});
}
if suite.ends_with("fulldeps") {
builder.ensure(compile::Rustc { compiler, target });
}
builder.ensure(compile::Std { compiler, target });
// ensure that `libproc_macro` is available on the host.
builder.ensure(compile::Std { compiler, target: compiler.host });
// Also provide `rust_test_helpers` for the host.
builder.ensure(native::TestHelpers { target: compiler.host });
// wasm32 can't build the test helpers
if !target.contains("wasm32") {
builder.ensure(native::TestHelpers { target });
}
builder.ensure(RemoteCopyLibs { compiler, target });
let mut cmd = builder.tool_cmd(Tool::Compiletest);
// compiletest currently has... a lot of arguments, so let's just pass all
// of them!
cmd.arg("--compile-lib-path")
.arg(builder.rustc_libdir(compiler));
cmd.arg("--run-lib-path")
.arg(builder.sysroot_libdir(compiler, target));
cmd.arg("--rustc-path").arg(builder.rustc(compiler));
let is_rustdoc = suite.ends_with("rustdoc-ui") || suite.ends_with("rustdoc-js");
// Avoid depending on rustdoc when we don't need it.
if mode == "rustdoc"
|| (mode == "run-make" && suite.ends_with("fulldeps"))
|| (mode == "ui" && is_rustdoc)
|| mode == "js-doc-test"
{
cmd.arg("--rustdoc-path")
.arg(builder.rustdoc(compiler));
}
cmd.arg("--src-base")
.arg(builder.src.join("src/test").join(suite));
cmd.arg("--build-base")
.arg(testdir(builder, compiler.host).join(suite));
cmd.arg("--stage-id")
.arg(format!("stage{}-{}", compiler.stage, target));
cmd.arg("--mode").arg(mode);
cmd.arg("--target").arg(target);
cmd.arg("--host").arg(&*compiler.host);
cmd.arg("--llvm-filecheck")
.arg(builder.llvm_filecheck(builder.config.build));
if builder.config.cmd.bless() {
cmd.arg("--bless");
}
let compare_mode = builder.config.cmd.compare_mode().or_else(|| {
if builder.config.test_compare_mode {
self.compare_mode
} else {
None
}
});
if let Some(ref pass) = builder.config.cmd.pass() {
cmd.arg("--pass");
cmd.arg(pass);
}
if let Some(ref nodejs) = builder.config.nodejs {
cmd.arg("--nodejs").arg(nodejs);
}
let mut flags = if is_rustdoc {
Vec::new()
} else {
vec!["-Crpath".to_string()]
};
if !is_rustdoc {
if builder.config.rust_optimize_tests {
flags.push("-O".to_string());
}
}
flags.push(format!("-Cdebuginfo={}", builder.config.rust_debuginfo_level_tests));
flags.push("-Zunstable-options".to_string());
flags.push(builder.config.cmd.rustc_args().join(" "));
if let Some(linker) = builder.linker(target) {
cmd.arg("--linker").arg(linker);
}
let mut hostflags = flags.clone();
hostflags.push(format!(
"-Lnative={}",
builder.test_helpers_out(compiler.host).display()
));
cmd.arg("--host-rustcflags").arg(hostflags.join(" "));
let mut targetflags = flags;
targetflags.push(format!(
"-Lnative={}",
builder.test_helpers_out(target).display()
));
cmd.arg("--target-rustcflags").arg(targetflags.join(" "));
cmd.arg("--docck-python").arg(builder.python());
if builder.config.build.ends_with("apple-darwin") {
// Force /usr/bin/python on macOS for LLDB tests because we're loading the
// LLDB plugin's compiled module which only works with the system python
// (namely not Homebrew-installed python)
cmd.arg("--lldb-python").arg("/usr/bin/python");
} else {
cmd.arg("--lldb-python").arg(builder.python());
}
if let Some(ref gdb) = builder.config.gdb {
cmd.arg("--gdb").arg(gdb);
}
let run = |cmd: &mut Command| {
cmd.output().map(|output| {
String::from_utf8_lossy(&output.stdout)
.lines().next().unwrap_or_else(|| {
panic!("{:?} failed {:?}", cmd, output)
}).to_string()
})
};
let lldb_exe = if builder.config.lldb_enabled && !target.contains("emscripten") {
// Test against the lldb that was just built.
builder.llvm_out(target).join("bin").join("lldb")
} else {
PathBuf::from("lldb")
};
let lldb_version = Command::new(&lldb_exe)
.arg("--version")
.output()
.map(|output| { String::from_utf8_lossy(&output.stdout).to_string() })
.ok();
if let Some(ref vers) = lldb_version {
cmd.arg("--lldb-version").arg(vers);
let lldb_python_dir = run(Command::new(&lldb_exe).arg("-P")).ok();
if let Some(ref dir) = lldb_python_dir {
cmd.arg("--lldb-python-dir").arg(dir);
}
}
if util::forcing_clang_based_tests() {
let clang_exe = builder.llvm_out(target).join("bin").join("clang");
cmd.arg("--run-clang-based-tests-with").arg(clang_exe);
}
// Get paths from cmd args
let paths = match &builder.config.cmd {
Subcommand::Test { ref paths, .. } => &paths[..],
_ => &[],
};
// Get test-args by striping suite path
let mut test_args: Vec<&str> = paths
.iter()
.map(|p| {
match p.strip_prefix(".") {
Ok(path) => path,
Err(_) => p,
}
})
.filter(|p| p.starts_with(suite_path) && (p.is_dir() || p.is_file()))
.filter_map(|p| {
// Since test suite paths are themselves directories, if we don't
// specify a directory or file, we'll get an empty string here
// (the result of the test suite directory without its suite prefix).
// Therefore, we need to filter these out, as only the first --test-args
// flag is respected, so providing an empty --test-args conflicts with
// any following it.
match p.strip_prefix(suite_path).ok().and_then(|p| p.to_str()) {
Some(s) if s != "" => Some(s),
_ => None,
}
})
.collect();
test_args.append(&mut builder.config.cmd.test_args());
cmd.args(&test_args);
if builder.is_verbose() {
cmd.arg("--verbose");
}
if !builder.config.verbose_tests {
cmd.arg("--quiet");
}
if builder.config.llvm_enabled() {
let llvm_config = builder.ensure(native::Llvm {
target: builder.config.build,
emscripten: false,
});
if !builder.config.dry_run {
let llvm_version = output(Command::new(&llvm_config).arg("--version"));
cmd.arg("--llvm-version").arg(llvm_version);
}
if !builder.is_rust_llvm(target) {
cmd.arg("--system-llvm");
}
// Only pass correct values for these flags for the `run-make` suite as it
// requires that a C++ compiler was configured which isn't always the case.
if !builder.config.dry_run && suite == "run-make-fulldeps" {
let llvm_components = output(Command::new(&llvm_config).arg("--components"));
let llvm_cxxflags = output(Command::new(&llvm_config).arg("--cxxflags"));
cmd.arg("--cc")
.arg(builder.cc(target))
.arg("--cxx")
.arg(builder.cxx(target).unwrap())
.arg("--cflags")
.arg(builder.cflags(target, GitRepo::Rustc).join(" "))
.arg("--llvm-components")
.arg(llvm_components.trim())
.arg("--llvm-cxxflags")
.arg(llvm_cxxflags.trim());
if let Some(ar) = builder.ar(target) {
cmd.arg("--ar").arg(ar);
}
// The llvm/bin directory contains many useful cross-platform
// tools. Pass the path to run-make tests so they can use them.
let llvm_bin_path = llvm_config.parent()
.expect("Expected llvm-config to be contained in directory");
assert!(llvm_bin_path.is_dir());
cmd.arg("--llvm-bin-dir").arg(llvm_bin_path);
// If LLD is available, add it to the PATH
if builder.config.lld_enabled {
let lld_install_root = builder.ensure(native::Lld {
target: builder.config.build,
});
let lld_bin_path = lld_install_root.join("bin");
let old_path = env::var_os("PATH").unwrap_or_default();
let new_path = env::join_paths(std::iter::once(lld_bin_path)
.chain(env::split_paths(&old_path)))
.expect("Could not add LLD bin path to PATH");
cmd.env("PATH", new_path);
}
}
}
if suite != "run-make-fulldeps" {
cmd.arg("--cc")
.arg("")
.arg("--cxx")
.arg("")
.arg("--cflags")
.arg("")
.arg("--llvm-components")
.arg("")
.arg("--llvm-cxxflags")
.arg("");
}
if builder.remote_tested(target) {
cmd.arg("--remote-test-client")
.arg(builder.tool_exe(Tool::RemoteTestClient));
}
// Running a C compiler on MSVC requires a few env vars to be set, to be
// sure to set them here.
//
// Note that if we encounter `PATH` we make sure to append to our own `PATH`
// rather than stomp over it.
if target.contains("msvc") {
for &(ref k, ref v) in builder.cc[&target].env() {
if k != "PATH" {
cmd.env(k, v);
}
}
}
cmd.env("RUSTC_BOOTSTRAP", "1");
builder.add_rust_test_threads(&mut cmd);
if builder.config.sanitizers {
cmd.env("RUSTC_SANITIZER_SUPPORT", "1");
}
if builder.config.profiler {
cmd.env("RUSTC_PROFILER_SUPPORT", "1");
}
cmd.env("RUST_TEST_TMPDIR", builder.out.join("tmp"));
cmd.arg("--adb-path").arg("adb");
cmd.arg("--adb-test-dir").arg(ADB_TEST_DIR);
if target.contains("android") {
// Assume that cc for this target comes from the android sysroot
cmd.arg("--android-cross-path")
.arg(builder.cc(target).parent().unwrap().parent().unwrap());
} else {
cmd.arg("--android-cross-path").arg("");
}
if builder.config.cmd.rustfix_coverage() {
cmd.arg("--rustfix-coverage");
}
builder.ci_env.force_coloring_in_ci(&mut cmd);
builder.info(&format!(
"Check compiletest suite={} mode={} ({} -> {})",
suite, mode, &compiler.host, target
));
let _time = util::timeit(&builder);
try_run(builder, &mut cmd);
if let Some(compare_mode) = compare_mode {
cmd.arg("--compare-mode").arg(compare_mode);
builder.info(&format!(
"Check compiletest suite={} mode={} compare_mode={} ({} -> {})",
suite, mode, compare_mode, &compiler.host, target
));
let _time = util::timeit(&builder);
try_run(builder, &mut cmd);
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
struct DocTest {
compiler: Compiler,
path: &'static str,
name: &'static str,
is_ext_doc: bool,
}
impl Step for DocTest {
type Output = ();
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.never()
}
/// Runs `rustdoc --test` for all documentation in `src/doc`.
///
/// This will run all tests in our markdown documentation (e.g., the book)
/// located in `src/doc`. The `rustdoc` that's run is the one that sits next to
/// `compiler`.
fn run(self, builder: &Builder<'_>) {
let compiler = self.compiler;
builder.ensure(compile::Std {
compiler,
target: compiler.host,
});
// Do a breadth-first traversal of the `src/doc` directory and just run
// tests for all files that end in `*.md`
let mut stack = vec![builder.src.join(self.path)];
let _time = util::timeit(&builder);
let mut files = Vec::new();
while let Some(p) = stack.pop() {
if p.is_dir() {
stack.extend(t!(p.read_dir()).map(|p| t!(p).path()));
continue;
}
if p.extension().and_then(|s| s.to_str()) != Some("md") {
continue;
}
// The nostarch directory in the book is for no starch, and so isn't
// guaranteed to builder. We don't care if it doesn't build, so skip it.
if p.to_str().map_or(false, |p| p.contains("nostarch")) {
continue;
}
files.push(p);
}
files.sort();
let mut toolstate = ToolState::TestPass;
for file in files {
if !markdown_test(builder, compiler, &file) {
toolstate = ToolState::TestFail;
}
}
if self.is_ext_doc {
builder.save_toolstate(self.name, toolstate);
}
}
}
macro_rules! test_book {
($($name:ident, $path:expr, $book_name:expr, default=$default:expr;)+) => {
$(
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct $name {
compiler: Compiler,
}
impl Step for $name {
type Output = ();
const DEFAULT: bool = $default;
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path($path)
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure($name {
compiler: run.builder.compiler(run.builder.top_stage, run.host),
});
}
fn run(self, builder: &Builder<'_>) {
builder.ensure(DocTest {
compiler: self.compiler,
path: $path,
name: $book_name,
is_ext_doc: !$default,
});
}
}
)+
}
}
test_book!(
Nomicon, "src/doc/nomicon", "nomicon", default=false;
Reference, "src/doc/reference", "reference", default=false;
RustdocBook, "src/doc/rustdoc", "rustdoc", default=true;
RustcBook, "src/doc/rustc", "rustc", default=true;
RustByExample, "src/doc/rust-by-example", "rust-by-example", default=false;
EmbeddedBook, "src/doc/embedded-book", "embedded-book", default=false;
TheBook, "src/doc/book", "book", default=false;
UnstableBook, "src/doc/unstable-book", "unstable-book", default=true;
EditionGuide, "src/doc/edition-guide", "edition-guide", default=false;
);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct ErrorIndex {
compiler: Compiler,
}
impl Step for ErrorIndex {
type Output = ();
const DEFAULT: bool = true;
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/error_index_generator")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(ErrorIndex {
compiler: run.builder.compiler(run.builder.top_stage, run.host),
});
}
/// Runs the error index generator tool to execute the tests located in the error
/// index.
///
/// The `error_index_generator` tool lives in `src/tools` and is used to
/// generate a markdown file from the error indexes of the code base which is
/// then passed to `rustdoc --test`.
fn run(self, builder: &Builder<'_>) {
let compiler = self.compiler;
builder.ensure(compile::Std {
compiler,
target: compiler.host,
});
let dir = testdir(builder, compiler.host);
t!(fs::create_dir_all(&dir));
let output = dir.join("error-index.md");
let mut tool = tool::ErrorIndex::command(
builder,
builder.compiler(compiler.stage, builder.config.build),
);
tool.arg("markdown")
.arg(&output)
.env("CFG_BUILD", &builder.config.build);
builder.info(&format!("Testing error-index stage{}", compiler.stage));
let _time = util::timeit(&builder);
builder.run_quiet(&mut tool);
markdown_test(builder, compiler, &output);
}
}
fn markdown_test(builder: &Builder<'_>, compiler: Compiler, markdown: &Path) -> bool {
match fs::read_to_string(markdown) {
Ok(contents) => {
if !contents.contains("```") {
return true;
}
}
Err(_) => {}
}
builder.info(&format!("doc tests for: {}", markdown.display()));
let mut cmd = builder.rustdoc_cmd(compiler);
builder.add_rust_test_threads(&mut cmd);
cmd.arg("--test");
cmd.arg(markdown);
cmd.env("RUSTC_BOOTSTRAP", "1");
let test_args = builder.config.cmd.test_args().join(" ");
cmd.arg("--test-args").arg(test_args);
if builder.config.verbose_tests {
try_run(builder, &mut cmd)
} else {
try_run_quiet(builder, &mut cmd)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct RustcGuide;
impl Step for RustcGuide {
type Output = ();
const DEFAULT: bool = false;
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/doc/rustc-guide")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(RustcGuide);
}
fn run(self, builder: &Builder<'_>) {
let src = builder.src.join("src/doc/rustc-guide");
let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook);
let toolstate = if try_run(builder, rustbook_cmd.arg("linkcheck").arg(&src)) {
ToolState::TestPass
} else {
ToolState::TestFail
};
builder.save_toolstate("rustc-guide", toolstate);
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct CrateLibrustc {
compiler: Compiler,
target: Interned<String>,
test_kind: TestKind,
krate: Interned<String>,
}
impl Step for CrateLibrustc {
type Output = ();
const DEFAULT: bool = true;
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.krate("rustc-main")
}
fn make_run(run: RunConfig<'_>) {
let builder = run.builder;
let compiler = builder.compiler(builder.top_stage, run.host);
for krate in builder.in_tree_crates("rustc-main") {
if run.path.ends_with(&krate.path) {
let test_kind = builder.kind.into();
builder.ensure(CrateLibrustc {
compiler,
target: run.target,
test_kind,
krate: krate.name,
});
}
}
}
fn run(self, builder: &Builder<'_>) {
builder.ensure(Crate {
compiler: self.compiler,
target: self.target,
mode: Mode::Rustc,
test_kind: self.test_kind,
krate: self.krate,
});
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct CrateNotDefault {
compiler: Compiler,
target: Interned<String>,
test_kind: TestKind,
krate: &'static str,
}
impl Step for CrateNotDefault {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/librustc_asan")
.path("src/librustc_lsan")
.path("src/librustc_msan")
.path("src/librustc_tsan")
}
fn make_run(run: RunConfig<'_>) {
let builder = run.builder;
let compiler = builder.compiler(builder.top_stage, run.host);
let test_kind = builder.kind.into();
builder.ensure(CrateNotDefault {
compiler,
target: run.target,
test_kind,
krate: match run.path {
_ if run.path.ends_with("src/librustc_asan") => "rustc_asan",
_ if run.path.ends_with("src/librustc_lsan") => "rustc_lsan",
_ if run.path.ends_with("src/librustc_msan") => "rustc_msan",
_ if run.path.ends_with("src/librustc_tsan") => "rustc_tsan",
_ => panic!("unexpected path {:?}", run.path),
},
});
}
fn run(self, builder: &Builder<'_>) {
builder.ensure(Crate {
compiler: self.compiler,
target: self.target,
mode: Mode::Std,
test_kind: self.test_kind,
krate: INTERNER.intern_str(self.krate),
});
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Crate {
pub compiler: Compiler,
pub target: Interned<String>,
pub mode: Mode,
pub test_kind: TestKind,
pub krate: Interned<String>,
}
impl Step for Crate {
type Output = ();
const DEFAULT: bool = true;
fn should_run(mut run: ShouldRun<'_>) -> ShouldRun<'_> {
let builder = run.builder;
for krate in run.builder.in_tree_crates("test") {
if !(krate.name.starts_with("rustc_") && krate.name.ends_with("san")) {
run = run.path(krate.local_path(&builder).to_str().unwrap());
}
}
run
}
fn make_run(run: RunConfig<'_>) {
let builder = run.builder;
let compiler = builder.compiler(builder.top_stage, run.host);
let make = |mode: Mode, krate: &CargoCrate| {
let test_kind = builder.kind.into();
builder.ensure(Crate {
compiler,
target: run.target,
mode,
test_kind,
krate: krate.name,
});
};
for krate in builder.in_tree_crates("test") {
if run.path.ends_with(&krate.local_path(&builder)) {
make(Mode::Std, krate);
}
}
}
/// Runs all unit tests plus documentation tests for a given crate defined
/// by a `Cargo.toml` (single manifest)
///
/// This is what runs tests for crates like the standard library, compiler, etc.
/// It essentially is the driver for running `cargo test`.
///
/// Currently this runs all tests for a DAG by passing a bunch of `-p foo`
/// arguments, and those arguments are discovered from `cargo metadata`.
fn run(self, builder: &Builder<'_>) {
let compiler = self.compiler;
let target = self.target;
let mode = self.mode;
let test_kind = self.test_kind;
let krate = self.krate;
builder.ensure(compile::Std { compiler, target });
builder.ensure(RemoteCopyLibs { compiler, target });
// If we're not doing a full bootstrap but we're testing a stage2
// version of libstd, then what we're actually testing is the libstd
// produced in stage1. Reflect that here by updating the compiler that
// we're working with automatically.
let compiler = builder.compiler_for(compiler.stage, compiler.host, target);
let mut cargo = builder.cargo(compiler, mode, target, test_kind.subcommand());
match mode {
Mode::Std => {
compile::std_cargo(builder, &compiler, target, &mut cargo);
}
Mode::Rustc => {
builder.ensure(compile::Rustc { compiler, target });
compile::rustc_cargo(builder, &mut cargo);
}
_ => panic!("can only test libraries"),
};
// Build up the base `cargo test` command.
//
// Pass in some standard flags then iterate over the graph we've discovered
// in `cargo metadata` with the maps above and figure out what `-p`
// arguments need to get passed.
if test_kind.subcommand() == "test" && !builder.fail_fast {
cargo.arg("--no-fail-fast");
}
match builder.doc_tests {
DocTests::Only => {
cargo.arg("--doc");
}
DocTests::No => {
cargo.args(&["--lib", "--bins", "--examples", "--tests", "--benches"]);
}
DocTests::Yes => {}
}
cargo.arg("-p").arg(krate);
// The tests are going to run with the *target* libraries, so we need to
// ensure that those libraries show up in the LD_LIBRARY_PATH equivalent.
//
// Note that to run the compiler we need to run with the *host* libraries,
// but our wrapper scripts arrange for that to be the case anyway.
let mut dylib_path = dylib_path();
dylib_path.insert(0, PathBuf::from(&*builder.sysroot_libdir(compiler, target)));
cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
cargo.arg("--");
cargo.args(&builder.config.cmd.test_args());
if !builder.config.verbose_tests {
cargo.arg("--quiet");
}
if target.contains("emscripten") {
cargo.env(
format!("CARGO_TARGET_{}_RUNNER", envify(&target)),
builder
.config
.nodejs
.as_ref()
.expect("nodejs not configured"),
);
} else if target.starts_with("wasm32") {
// Warn about running tests without the `wasm_syscall` feature enabled.
// The javascript shim implements the syscall interface so that test
// output can be correctly reported.
if !builder.config.wasm_syscall {
builder.info(
"Libstd was built without `wasm_syscall` feature enabled: \
test output may not be visible."
);
}
// On the wasm32-unknown-unknown target we're using LTO which is
// incompatible with `-C prefer-dynamic`, so disable that here
cargo.env("RUSTC_NO_PREFER_DYNAMIC", "1");
let node = builder
.config
.nodejs
.as_ref()
.expect("nodejs not configured");
let runner = format!(
"{} {}/src/etc/wasm32-shim.js",
node.display(),
builder.src.display()
);
cargo.env(format!("CARGO_TARGET_{}_RUNNER", envify(&target)), &runner);
} else if builder.remote_tested(target) {
cargo.env(
format!("CARGO_TARGET_{}_RUNNER", envify(&target)),
format!("{} run", builder.tool_exe(Tool::RemoteTestClient).display()),
);
}
builder.info(&format!(
"{} {} stage{} ({} -> {})",
test_kind, krate, compiler.stage, &compiler.host, target
));
let _time = util::timeit(&builder);
try_run(builder, &mut cargo);
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct CrateRustdoc {
host: Interned<String>,
test_kind: TestKind,
}
impl Step for CrateRustdoc {
type Output = ();
const DEFAULT: bool = true;
const ONLY_HOSTS: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.paths(&["src/librustdoc", "src/tools/rustdoc"])
}
fn make_run(run: RunConfig<'_>) {
let builder = run.builder;
let test_kind = builder.kind.into();
builder.ensure(CrateRustdoc {
host: run.host,
test_kind,
});
}
fn run(self, builder: &Builder<'_>) {
let test_kind = self.test_kind;
let compiler = builder.compiler(builder.top_stage, self.host);
let target = compiler.host;
builder.ensure(compile::Rustc { compiler, target });
let mut cargo = tool::prepare_tool_cargo(builder,
compiler,
Mode::ToolRustc,
target,
test_kind.subcommand(),
"src/tools/rustdoc",
SourceType::InTree,
&[]);
if test_kind.subcommand() == "test" && !builder.fail_fast {
cargo.arg("--no-fail-fast");
}
cargo.arg("-p").arg("rustdoc:0.0.0");
cargo.arg("--");
cargo.args(&builder.config.cmd.test_args());
if self.host.contains("musl") {
cargo.arg("'-Ctarget-feature=-crt-static'");
}
if !builder.config.verbose_tests {
cargo.arg("--quiet");
}
builder.info(&format!(
"{} rustdoc stage{} ({} -> {})",
test_kind, compiler.stage, &compiler.host, target
));
let _time = util::timeit(&builder);
try_run(builder, &mut cargo);
}
}
fn envify(s: &str) -> String {
s.chars()
.map(|c| match c {
'-' => '_',
c => c,
})
.flat_map(|c| c.to_uppercase())
.collect()
}
/// Some test suites are run inside emulators or on remote devices, and most
/// of our test binaries are linked dynamically which means we need to ship
/// the standard library and such to the emulator ahead of time. This step
/// represents this and is a dependency of all test suites.
///
/// Most of the time this is a no-op. For some steps such as shipping data to
/// QEMU we have to build our own tools so we've got conditional dependencies
/// on those programs as well. Note that the remote test client is built for
/// the build target (us) and the server is built for the target.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct RemoteCopyLibs {
compiler: Compiler,
target: Interned<String>,
}
impl Step for RemoteCopyLibs {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.never()
}
fn run(self, builder: &Builder<'_>) {
let compiler = self.compiler;
let target = self.target;
if !builder.remote_tested(target) {
return;
}
builder.ensure(compile::Std { compiler, target });
builder.info(&format!("REMOTE copy libs to emulator ({})", target));
t!(fs::create_dir_all(builder.out.join("tmp")));
let server = builder.ensure(tool::RemoteTestServer {
compiler: compiler.with_stage(0),
target,
});
// Spawn the emulator and wait for it to come online
let tool = builder.tool_exe(Tool::RemoteTestClient);
let mut cmd = Command::new(&tool);
cmd.arg("spawn-emulator")
.arg(target)
.arg(&server)
.arg(builder.out.join("tmp"));
if let Some(rootfs) = builder.qemu_rootfs(target) {
cmd.arg(rootfs);
}
builder.run(&mut cmd);
// Push all our dylibs to the emulator
for f in t!(builder.sysroot_libdir(compiler, target).read_dir()) {
let f = t!(f);
let name = f.file_name().into_string().unwrap();
if util::is_dylib(&name) {
builder.run(Command::new(&tool).arg("push").arg(f.path()));
}
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Distcheck;
impl Step for Distcheck {
type Output = ();
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("distcheck")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Distcheck);
}
/// Runs "distcheck", a 'make check' from a tarball
fn run(self, builder: &Builder<'_>) {
builder.info("Distcheck");
let dir = builder.out.join("tmp").join("distcheck");
let _ = fs::remove_dir_all(&dir);
t!(fs::create_dir_all(&dir));
// Guarantee that these are built before we begin running.
builder.ensure(dist::PlainSourceTarball);
builder.ensure(dist::Src);
let mut cmd = Command::new("tar");
cmd.arg("-xzf")
.arg(builder.ensure(dist::PlainSourceTarball))
.arg("--strip-components=1")
.current_dir(&dir);
builder.run(&mut cmd);
builder.run(
Command::new("./configure")
.args(&builder.config.configure_args)
.arg("--enable-vendor")
.current_dir(&dir),
);
builder.run(
Command::new(build_helper::make(&builder.config.build))
.arg("check")
.current_dir(&dir),
);
// Now make sure that rust-src has all of libstd's dependencies
builder.info("Distcheck rust-src");
let dir = builder.out.join("tmp").join("distcheck-src");
let _ = fs::remove_dir_all(&dir);
t!(fs::create_dir_all(&dir));
let mut cmd = Command::new("tar");
cmd.arg("-xzf")
.arg(builder.ensure(dist::Src))
.arg("--strip-components=1")
.current_dir(&dir);
builder.run(&mut cmd);
let toml = dir.join("rust-src/lib/rustlib/src/rust/src/libstd/Cargo.toml");
builder.run(
Command::new(&builder.initial_cargo)
.arg("generate-lockfile")
.arg("--manifest-path")
.arg(&toml)
.current_dir(&dir),
);
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Bootstrap;
impl Step for Bootstrap {
type Output = ();
const DEFAULT: bool = true;
const ONLY_HOSTS: bool = true;
/// Tests the build system itself.
fn run(self, builder: &Builder<'_>) {
let mut cmd = Command::new(&builder.initial_cargo);
cmd.arg("test")
.current_dir(builder.src.join("src/bootstrap"))
.env("RUSTFLAGS", "-Cdebuginfo=2")
.env("CARGO_TARGET_DIR", builder.out.join("bootstrap"))
.env("RUSTC_BOOTSTRAP", "1")
.env("RUSTC", &builder.initial_rustc);
if let Some(flags) = option_env!("RUSTFLAGS") {
// Use the same rustc flags for testing as for "normal" compilation,
// so that Cargo doesnt recompile the entire dependency graph every time:
// https://github.com/rust-lang/rust/issues/49215
cmd.env("RUSTFLAGS", flags);
}
if !builder.fail_fast {
cmd.arg("--no-fail-fast");
}
cmd.arg("--").args(&builder.config.cmd.test_args());
// rustbuild tests are racy on directory creation so just run them one at a time.
// Since there's not many this shouldn't be a problem.
cmd.arg("--test-threads=1");
try_run(builder, &mut cmd);
}
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/bootstrap")
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(Bootstrap);
}
}