Add supporting infrastructure for run-make V2 tests

This commit is contained in:
许杰友 Jieyou Xu (Joe) 2024-01-20 16:51:59 +00:00
parent d3d145ea1c
commit 48e9f92ce2
No known key found for this signature in database
GPG Key ID: 95DDEBD74A1DC2C0
11 changed files with 583 additions and 32 deletions

View File

@ -3274,6 +3274,10 @@ dependencies = [
"serde_json",
]
[[package]]
name = "run_make_support"
version = "0.0.0"
[[package]]
name = "rust-demangler"
version = "0.0.1"

View File

@ -10,6 +10,7 @@ members = [
"src/tools/clippy",
"src/tools/clippy/clippy_dev",
"src/tools/compiletest",
"src/tools/run-make-support",
"src/tools/error_index_generator",
"src/tools/linkchecker",
"src/tools/lint-docs",

View File

@ -1327,6 +1327,52 @@ fn run(self, builder: &Builder<'_>) {
};
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
pub struct RunMakeSupport {
pub compiler: Compiler,
pub target: TargetSelection,
}
impl Step for RunMakeSupport {
type Output = PathBuf;
const DEFAULT: bool = true;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.never()
}
fn make_run(run: RunConfig<'_>) {
let compiler = run.builder.compiler(run.builder.top_stage, run.build_triple());
run.builder.ensure(RunMakeSupport { compiler, target: run.build_triple() });
}
fn run(self, builder: &Builder<'_>) -> PathBuf {
builder.ensure(compile::Std::new(self.compiler, self.target));
let cargo = tool::prepare_tool_cargo(
builder,
self.compiler,
Mode::ToolStd,
self.target,
"build",
"src/tools/run-make-support",
SourceType::InTree,
&[],
);
let mut cargo = Command::from(cargo);
builder.run(&mut cargo);
let lib_name = "librun_make_support.rlib";
let lib = builder.tools_dir(self.compiler).join(&lib_name);
let cargo_out =
builder.cargo_out(self.compiler, Mode::ToolStd, self.target).join(&lib_name);
builder.copy(&cargo_out, &lib);
lib
}
}
default_test!(Ui { path: "tests/ui", mode: "ui", suite: "ui" });
default_test!(RunPassValgrind {
@ -1361,7 +1407,40 @@ fn run(self, builder: &Builder<'_>) {
host_test!(Pretty { path: "tests/pretty", mode: "pretty", suite: "pretty" });
default_test!(RunMake { path: "tests/run-make", mode: "run-make", suite: "run-make" });
// Special-handling is needed for `run-make`, so don't use `default_test` for defining `RunMake`
// tests.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct RunMake {
pub compiler: Compiler,
pub target: TargetSelection,
}
impl Step for RunMake {
type Output = ();
const DEFAULT: bool = true;
const ONLY_HOSTS: bool = false;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.suite_path("tests/run-make")
}
fn make_run(run: RunConfig<'_>) {
let compiler = run.builder.compiler(run.builder.top_stage, run.build_triple());
run.builder.ensure(RunMakeSupport { compiler, target: run.build_triple() });
run.builder.ensure(RunMake { compiler, target: run.target });
}
fn run(self, builder: &Builder<'_>) {
builder.ensure(Compiletest {
compiler: self.compiler,
target: self.target,
mode: "run-make",
suite: "run-make",
path: "tests/run-make",
compare_mode: None,
});
}
}
host_test!(RunMakeFullDeps {
path: "tests/run-make-fulldeps",

View File

@ -655,7 +655,14 @@ fn collect_tests_from_dir(
return Ok(());
}
if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
if config.mode == Mode::RunMake {
if dir.join("Makefile").exists() && dir.join("rmake.rs").exists() {
return Err(io::Error::other(
"run-make tests cannot have both `Makefile` and `rmake.rs`",
));
}
if dir.join("Makefile").exists() || dir.join("rmake.rs").exists() {
let paths = TestPaths {
file: dir.to_path_buf(),
relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
@ -663,6 +670,7 @@ fn collect_tests_from_dir(
tests.extend(make_test(config, cache, &paths, inputs, poisoned));
return Ok(());
}
}
// If we find a test foo/bar.rs, we have to build the
// output directory `$build/foo` so we can write
@ -733,8 +741,17 @@ fn make_test(
poisoned: &mut bool,
) -> Vec<test::TestDescAndFn> {
let test_path = if config.mode == Mode::RunMake {
// Parse directives in the Makefile
if testpaths.file.join("rmake.rs").exists() && testpaths.file.join("Makefile").exists() {
panic!("run-make tests cannot have both `rmake.rs` and `Makefile`");
}
if testpaths.file.join("rmake.rs").exists() {
// Parse directives in rmake.rs.
testpaths.file.join("rmake.rs")
} else {
// Parse directives in the Makefile.
testpaths.file.join("Makefile")
}
} else {
PathBuf::from(&testpaths.file)
};

View File

@ -3570,6 +3570,17 @@ fn run_incremental_test(&self) {
}
fn run_rmake_test(&self) {
let test_dir = &self.testpaths.file;
if test_dir.join("rmake.rs").exists() {
self.run_rmake_v2_test();
} else if test_dir.join("Makefile").exists() {
self.run_rmake_legacy_test();
} else {
self.fatal("failed to find either `rmake.rs` or `Makefile`")
}
}
fn run_rmake_legacy_test(&self) {
let cwd = env::current_dir().unwrap();
let src_root = self.config.src_base.parent().unwrap().parent().unwrap();
let src_root = cwd.join(&src_root);
@ -3737,6 +3748,238 @@ fn aggressive_rm_rf(&self, path: &Path) -> io::Result<()> {
fs::remove_dir(path)
}
fn run_rmake_v2_test(&self) {
// For `run-make` V2, we need to perform 2 steps to build and run a `run-make` V2 recipe
// (`rmake.rs`) to run the actual tests. The support library is already built as a tool
// dylib and is available under `build/$TARGET/stageN-tools-bin/librun_make_support.rlib`.
//
// 1. We need to build the recipe `rmake.rs` and link in the support library.
// 2. We need to run the recipe to build and run the tests.
let cwd = env::current_dir().unwrap();
let src_root = self.config.src_base.parent().unwrap().parent().unwrap();
let src_root = cwd.join(&src_root);
let build_root = self.config.build_base.parent().unwrap().parent().unwrap();
let build_root = cwd.join(&build_root);
let tmpdir = cwd.join(self.output_base_name());
if tmpdir.exists() {
self.aggressive_rm_rf(&tmpdir).unwrap();
}
create_dir_all(&tmpdir).unwrap();
// HACK: assume stageN-target, we only want stageN.
let stage = self.config.stage_id.split('-').next().unwrap();
// First, we construct the path to the built support library.
let mut support_lib_path = PathBuf::new();
support_lib_path.push(&build_root);
support_lib_path.push(format!("{}-tools-bin", stage));
support_lib_path.push("librun_make_support.rlib");
let mut stage_std_path = PathBuf::new();
stage_std_path.push(&build_root);
stage_std_path.push(&stage);
stage_std_path.push("lib");
// Then, we need to build the recipe `rmake.rs` and link in the support library.
let recipe_bin =
tmpdir.join(if self.config.target.contains("windows") { "rmake.exe" } else { "rmake" });
let mut support_lib_deps = PathBuf::new();
support_lib_deps.push(&build_root);
support_lib_deps.push(format!("{}-tools", stage));
support_lib_deps.push(&self.config.host);
support_lib_deps.push("release");
support_lib_deps.push("deps");
let mut support_lib_deps_deps = PathBuf::new();
support_lib_deps_deps.push(&build_root);
support_lib_deps_deps.push(format!("{}-tools", stage));
support_lib_deps_deps.push("release");
support_lib_deps_deps.push("deps");
debug!(?support_lib_deps);
debug!(?support_lib_deps_deps);
let res = self.cmd2procres(
Command::new(&self.config.rustc_path)
.arg("-o")
.arg(&recipe_bin)
.arg(format!(
"-Ldependency={}",
&support_lib_path.parent().unwrap().to_string_lossy()
))
.arg(format!("-Ldependency={}", &support_lib_deps.to_string_lossy()))
.arg(format!("-Ldependency={}", &support_lib_deps_deps.to_string_lossy()))
.arg("--extern")
.arg(format!("run_make_support={}", &support_lib_path.to_string_lossy()))
.arg(&self.testpaths.file.join("rmake.rs"))
.env("TARGET", &self.config.target)
.env("PYTHON", &self.config.python)
.env("S", &src_root)
.env("RUST_BUILD_STAGE", &self.config.stage_id)
.env("RUSTC", cwd.join(&self.config.rustc_path))
.env("TMPDIR", &tmpdir)
.env("LD_LIB_PATH_ENVVAR", dylib_env_var())
.env("HOST_RPATH_DIR", cwd.join(&self.config.compile_lib_path))
.env("TARGET_RPATH_DIR", cwd.join(&self.config.run_lib_path))
.env("LLVM_COMPONENTS", &self.config.llvm_components)
// We for sure don't want these tests to run in parallel, so make
// sure they don't have access to these vars if we run via `make`
// at the top level
.env_remove("MAKEFLAGS")
.env_remove("MFLAGS")
.env_remove("CARGO_MAKEFLAGS"),
);
if !res.status.success() {
self.fatal_proc_rec("run-make test failed: could not build `rmake.rs` recipe", &res);
}
// Finally, we need to run the recipe binary to build and run the actual tests.
debug!(?recipe_bin);
let mut dylib_env_paths = String::new();
dylib_env_paths.push_str(&env::var(dylib_env_var()).unwrap());
dylib_env_paths.push(':');
dylib_env_paths.push_str(&support_lib_path.parent().unwrap().to_string_lossy());
dylib_env_paths.push(':');
dylib_env_paths.push_str(
&stage_std_path.join("rustlib").join(&self.config.host).join("lib").to_string_lossy(),
);
let mut target_rpath_env_path = String::new();
target_rpath_env_path.push_str(&tmpdir.to_string_lossy());
target_rpath_env_path.push(':');
target_rpath_env_path.push_str(&dylib_env_paths);
let mut cmd = Command::new(&recipe_bin);
cmd.current_dir(&self.testpaths.file)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.env("LD_LIB_PATH_ENVVAR", dylib_env_var())
.env("TARGET_RPATH_ENV", &target_rpath_env_path)
.env(dylib_env_var(), &dylib_env_paths)
.env("TARGET", &self.config.target)
.env("PYTHON", &self.config.python)
.env("S", &src_root)
.env("RUST_BUILD_STAGE", &self.config.stage_id)
.env("RUSTC", cwd.join(&self.config.rustc_path))
.env("TMPDIR", &tmpdir)
.env("HOST_RPATH_DIR", cwd.join(&self.config.compile_lib_path))
.env("TARGET_RPATH_DIR", cwd.join(&self.config.run_lib_path))
.env("LLVM_COMPONENTS", &self.config.llvm_components)
// We for sure don't want these tests to run in parallel, so make
// sure they don't have access to these vars if we run via `make`
// at the top level
.env_remove("MAKEFLAGS")
.env_remove("MFLAGS")
.env_remove("CARGO_MAKEFLAGS");
if let Some(ref rustdoc) = self.config.rustdoc_path {
cmd.env("RUSTDOC", cwd.join(rustdoc));
}
if let Some(ref rust_demangler) = self.config.rust_demangler_path {
cmd.env("RUST_DEMANGLER", cwd.join(rust_demangler));
}
if let Some(ref node) = self.config.nodejs {
cmd.env("NODE", node);
}
if let Some(ref linker) = self.config.target_linker {
cmd.env("RUSTC_LINKER", linker);
}
if let Some(ref clang) = self.config.run_clang_based_tests_with {
cmd.env("CLANG", clang);
}
if let Some(ref filecheck) = self.config.llvm_filecheck {
cmd.env("LLVM_FILECHECK", filecheck);
}
if let Some(ref llvm_bin_dir) = self.config.llvm_bin_dir {
cmd.env("LLVM_BIN_DIR", llvm_bin_dir);
}
if let Some(ref remote_test_client) = self.config.remote_test_client {
cmd.env("REMOTE_TEST_CLIENT", remote_test_client);
}
// We don't want RUSTFLAGS set from the outside to interfere with
// compiler flags set in the test cases:
cmd.env_remove("RUSTFLAGS");
// Use dynamic musl for tests because static doesn't allow creating dylibs
if self.config.host.contains("musl") {
cmd.env("RUSTFLAGS", "-Ctarget-feature=-crt-static").env("IS_MUSL_HOST", "1");
}
if self.config.bless {
cmd.env("RUSTC_BLESS_TEST", "--bless");
// Assume this option is active if the environment variable is "defined", with _any_ value.
// As an example, a `Makefile` can use this option by:
//
// ifdef RUSTC_BLESS_TEST
// cp "$(TMPDIR)"/actual_something.ext expected_something.ext
// else
// $(DIFF) expected_something.ext "$(TMPDIR)"/actual_something.ext
// endif
}
if self.config.target.contains("msvc") && self.config.cc != "" {
// We need to pass a path to `lib.exe`, so assume that `cc` is `cl.exe`
// and that `lib.exe` lives next to it.
let lib = Path::new(&self.config.cc).parent().unwrap().join("lib.exe");
// MSYS doesn't like passing flags of the form `/foo` as it thinks it's
// a path and instead passes `C:\msys64\foo`, so convert all
// `/`-arguments to MSVC here to `-` arguments.
let cflags = self
.config
.cflags
.split(' ')
.map(|s| s.replace("/", "-"))
.collect::<Vec<_>>()
.join(" ");
let cxxflags = self
.config
.cxxflags
.split(' ')
.map(|s| s.replace("/", "-"))
.collect::<Vec<_>>()
.join(" ");
cmd.env("IS_MSVC", "1")
.env("IS_WINDOWS", "1")
.env("MSVC_LIB", format!("'{}' -nologo", lib.display()))
.env("CC", format!("'{}' {}", self.config.cc, cflags))
.env("CXX", format!("'{}' {}", &self.config.cxx, cxxflags));
} else {
cmd.env("CC", format!("{} {}", self.config.cc, self.config.cflags))
.env("CXX", format!("{} {}", self.config.cxx, self.config.cxxflags))
.env("AR", &self.config.ar);
if self.config.target.contains("windows") {
cmd.env("IS_WINDOWS", "1");
}
}
let (Output { stdout, stderr, status }, truncated) =
self.read2_abbreviated(cmd.spawn().expect("failed to spawn `rmake`"));
if !status.success() {
let res = ProcRes {
status,
stdout: String::from_utf8_lossy(&stdout).into_owned(),
stderr: String::from_utf8_lossy(&stderr).into_owned(),
truncated,
cmdline: format!("{:?}", cmd),
};
self.fatal_proc_rec("rmake recipe failed to complete", &res);
}
}
fn run_js_doc_test(&self) {
if let Some(nodejs) = &self.config.nodejs {
let out_dir = self.output_base_dir();

View File

@ -0,0 +1,6 @@
[package]
name = "run_make_support"
version = "0.0.0"
edition = "2021"
[dependencies]

View File

@ -0,0 +1,151 @@
use std::env;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
fn setup_common_build_cmd() -> Command {
let rustc = env::var("RUSTC").unwrap();
let mut cmd = Command::new(rustc);
cmd.arg("--out-dir")
.arg(env::var("TMPDIR").unwrap())
.arg("-L")
.arg(env::var("TMPDIR").unwrap());
cmd
}
fn handle_failed_output(cmd: &str, output: Output, caller_line_number: u32) -> ! {
eprintln!("command failed at line {caller_line_number}");
eprintln!("{cmd}");
eprintln!("output status: `{}`", output.status);
eprintln!("=== STDOUT ===\n{}\n\n", String::from_utf8(output.stdout).unwrap());
eprintln!("=== STDERR ===\n{}\n\n", String::from_utf8(output.stderr).unwrap());
std::process::exit(1)
}
pub fn rustc() -> RustcInvocationBuilder {
RustcInvocationBuilder::new()
}
pub fn aux_build() -> AuxBuildInvocationBuilder {
AuxBuildInvocationBuilder::new()
}
#[derive(Debug)]
pub struct RustcInvocationBuilder {
cmd: Command,
}
impl RustcInvocationBuilder {
fn new() -> Self {
let cmd = setup_common_build_cmd();
Self { cmd }
}
pub fn arg(&mut self, arg: &str) -> &mut RustcInvocationBuilder {
self.cmd.arg(arg);
self
}
#[track_caller]
pub fn run(&mut self) -> Output {
let caller_location = std::panic::Location::caller();
let caller_line_number = caller_location.line();
let output = self.cmd.output().unwrap();
if !output.status.success() {
handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
}
output
}
}
#[derive(Debug)]
pub struct AuxBuildInvocationBuilder {
cmd: Command,
}
impl AuxBuildInvocationBuilder {
fn new() -> Self {
let mut cmd = setup_common_build_cmd();
cmd.arg("--crate-type=lib");
Self { cmd }
}
pub fn arg(&mut self, arg: &str) -> &mut AuxBuildInvocationBuilder {
self.cmd.arg(arg);
self
}
#[track_caller]
pub fn run(&mut self) -> Output {
let caller_location = std::panic::Location::caller();
let caller_line_number = caller_location.line();
let output = self.cmd.output().unwrap();
if !output.status.success() {
handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
}
output
}
}
fn run_common(bin_name: &str) -> (Command, Output) {
let target = env::var("TARGET").unwrap();
let bin_name =
if target.contains("windows") { format!("{}.exe", bin_name) } else { bin_name.to_owned() };
let mut bin_path = PathBuf::new();
bin_path.push(env::var("TMPDIR").unwrap());
bin_path.push(&bin_name);
let ld_lib_path_envvar = env::var("LD_LIB_PATH_ENVVAR").unwrap();
let mut cmd = Command::new(bin_path);
cmd.env(&ld_lib_path_envvar, {
let mut paths = vec![];
paths.push(PathBuf::from(env::var("TMPDIR").unwrap()));
for p in env::split_paths(&env::var("TARGET_RPATH_ENV").unwrap()) {
paths.push(p.to_path_buf());
}
for p in env::split_paths(&env::var(&ld_lib_path_envvar).unwrap()) {
paths.push(p.to_path_buf());
}
env::join_paths(paths.iter()).unwrap()
});
if target.contains("windows") {
let mut paths = vec![];
for p in env::split_paths(&std::env::var("PATH").unwrap_or(String::new())) {
paths.push(p.to_path_buf());
}
paths.push(Path::new(&std::env::var("TARGET_RPATH_DIR").unwrap()).to_path_buf());
cmd.env("PATH", env::join_paths(paths.iter()).unwrap());
}
let output = cmd.output().unwrap();
(cmd, output)
}
/// Run a built binary and make sure it succeeds.
#[track_caller]
pub fn run(bin_name: &str) -> Output {
let caller_location = std::panic::Location::caller();
let caller_line_number = caller_location.line();
let (cmd, output) = run_common(bin_name);
if !output.status.success() {
handle_failed_output(&format!("{:#?}", cmd), output, caller_line_number);
}
output
}
/// Run a built binary and make sure it fails.
#[track_caller]
pub fn run_fail(bin_name: &str) -> Output {
let caller_location = std::panic::Location::caller();
let caller_line_number = caller_location.line();
let (cmd, output) = run_common(bin_name);
if output.status.success() {
handle_failed_output(&format!("{:#?}", cmd), output, caller_line_number);
}
output
}

View File

@ -1,6 +0,0 @@
include ../tools.mk
all:
$(RUSTC) --emit=metadata --crate-type lib stable.rs
$(RUSTC) --emit=metadata --extern stable=$(TMPDIR)/libstable.rmeta main.rs 2>&1 >/dev/null \
| $(CGREP) -e "stable since $$(cat $(S)/src/version)(-[a-zA-Z]+)?"

View File

@ -0,0 +1,27 @@
// ignore-tidy-linelength
extern crate run_make_support;
use std::path::PathBuf;
use run_make_support::{aux_build, rustc};
fn main() {
aux_build()
.arg("--emit=metadata")
.arg("stable.rs")
.run();
let mut stable_path = PathBuf::from(env!("TMPDIR"));
stable_path.push("libstable.rmeta");
let output = rustc()
.arg("--emit=metadata")
.arg("--extern")
.arg(&format!("stable={}", &stable_path.to_string_lossy()))
.arg("main.rs")
.run();
let stderr = String::from_utf8_lossy(&output.stderr);
let version = include_str!(concat!(env!("S"), "/src/version"));
let expected_string = format!("stable since {}", version.trim());
assert!(stderr.contains(&expected_string));
}

View File

@ -1,16 +0,0 @@
# ignore-cross-compile
include ../tools.mk
# Test that if we build `b` against a version of `a` that has one set
# of types, it will not run with a dylib that has a different set of
# types.
# NOTE(eddyb) this test only works with the `legacy` mangling,
# and will probably get removed once `legacy` is gone.
all:
$(RUSTC) a.rs --cfg x -C prefer-dynamic -Z unstable-options -C symbol-mangling-version=legacy
$(RUSTC) b.rs -C prefer-dynamic -Z unstable-options -C symbol-mangling-version=legacy
$(call RUN,b)
$(RUSTC) a.rs --cfg y -C prefer-dynamic -Z unstable-options -C symbol-mangling-version=legacy
$(call FAIL,b)

View File

@ -0,0 +1,45 @@
// ignore-tidy-linelength
extern crate run_make_support;
use run_make_support::{run, run_fail, rustc};
fn main() {
rustc()
.arg("a.rs")
.arg("--cfg")
.arg("x")
.arg("-C")
.arg("prefer-dynamic")
.arg("-Z")
.arg("unstable-options")
.arg("-C")
.arg("symbol-mangling-version=legacy")
.run();
rustc()
.arg("b.rs")
.arg("-C")
.arg("prefer-dynamic")
.arg("-Z")
.arg("unstable-options")
.arg("-C")
.arg("symbol-mangling-version=legacy")
.run();
run("b");
rustc()
.arg("a.rs")
.arg("--cfg")
.arg("y")
.arg("-C")
.arg("prefer-dynamic")
.arg("-Z")
.arg("unstable-options")
.arg("-C")
.arg("symbol-mangling-version=legacy")
.run();
run_fail("b");
}