Skip to content

Commit f40ef99

Browse files
committed
Create tooling for end-to-end testing
Create two different tools: - `test-drive`: A rustc_driver that compiles a crate and run a few sanity checks on StableMIR. - `compiletest`: A wrapper to run compiler tests using the `test-drive` tool. I am also adding a script to run a few rustc tests and a nightly workflow. The files diff is not quite working yet so most tests that fail compilation don't succeed yet.
1 parent 9d7f594 commit f40ef99

File tree

11 files changed

+368
-0
lines changed

11 files changed

+368
-0
lines changed

.github/scripts/run_rustc_tests.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
set -u
5+
6+
# Location of a rust repository. Clone one if path doesn't exist.
7+
RUST_REPO="${RUST_REPO:?Missing path to rust repository. Set RUST_REPO}"
8+
# Where we will store the SMIR tools (Optional).
9+
TOOLS_BIN="${TOOLS_BIN:-"/tmp/smir/bin"}"
10+
# Assume we are inside SMIR repository
11+
SMIR_PATH=$(git rev-parse --show-toplevel)
12+
export RUST_BACKTRACE=1
13+
14+
pushd "${SMIR_PATH}"
15+
cargo +smir build -Z unstable-options --out-dir "${TOOLS_BIN}"
16+
export PATH="${TOOLS_BIN}":"${PATH}"
17+
18+
if [[ ! -e "${RUST_REPO}" ]]; then
19+
mkdir -p "$(dirname ${RUST_REPO})"
20+
git clone --depth 1 https://github.com/rust-lang/rust.git "${RUST_REPO}"
21+
fi
22+
23+
pushd "${RUST_REPO}"
24+
SUITES=(
25+
# Match https://github.com/rust-lang/rust/blob/master/src/bootstrap/test.rs for now
26+
"tests/ui/cfg ui"
27+
)
28+
for suite_cfg in "${SUITES[@]}"; do
29+
# Hack to work on older bash like the ones on MacOS.
30+
suite_pair=($suite_cfg)
31+
suite=${suite_pair[0]}
32+
mode=${suite_pair[1]}
33+
echo "${suite_cfg} pair: $suite_pair mode: $mode"
34+
compiletest --driver-path="${TOOLS_BIN}/test-drive" --mode=${mode} --src-base="${suite}" --output-path "${RUST_REPO}/build"
35+
done

Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Cargo workspace for utility tools used to check stable-mir in CI
2+
[workspace]
3+
resolver = "2"
4+
members = [
5+
"tools/compiletest",
6+
"tools/test-drive",
7+
]
8+
9+
exclude = [
10+
"build",
11+
"target",
12+
]

rust-toolchain.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[toolchain]
2+
channel = "nightly"
3+
components = ["llvm-tools-preview", "rustc-dev", "rust-src", "rustfmt"]

tools/compiletest/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "compiletest"
3+
description = "Run tests using compiletest-rs"
4+
version = "0.0.0"
5+
edition = "2021"
6+
7+
[dependencies]
8+
compiletest_rs = { version = "0.10.0", features = [ "rustc" ] }
9+
clap = { version = "4.1.3", features = ["derive"] }
10+
11+
[package.metadata.rust-analyzer]
12+
# This crate uses #[feature(rustc_private)].
13+
# See https://github.com/rust-analyzer/rust-analyzer/pull/7891
14+
rustc_private = true

tools/compiletest/build.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use std::env;
2+
use std::path::PathBuf;
3+
4+
pub fn main() {
5+
// Add rustup to the rpath in order to properly link with the correct rustc version.
6+
let rustup_home = env::var("RUSTUP_HOME").unwrap();
7+
let toolchain = env::var("RUSTUP_TOOLCHAIN").unwrap();
8+
let rustc_lib: PathBuf = [&rustup_home, "toolchains", &toolchain, "lib"]
9+
.iter()
10+
.collect();
11+
println!(
12+
"cargo:rustc-link-arg-bin=compiletest=-Wl,-rpath,{}",
13+
rustc_lib.display()
14+
);
15+
println!("cargo:rustc-env=RUSTC_LIB_PATH={}", rustc_lib.display());
16+
}

tools/compiletest/src/args.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use compiletest_rs::Config;
2+
use std::fmt::Debug;
3+
use std::path::PathBuf;
4+
5+
#[derive(Debug, clap::Parser)]
6+
#[command(version, name = "compiletest")]
7+
pub struct Args {
8+
/// The path where all tests are
9+
#[arg(long)]
10+
src_base: PathBuf,
11+
12+
/// The mode according to compiletest modes.
13+
#[arg(long)]
14+
mode: String,
15+
16+
/// Path for the stable-mir driver.
17+
#[arg(long)]
18+
driver_path: PathBuf,
19+
20+
/// Path for where the output should be stored.
21+
#[arg(long)]
22+
output_path: PathBuf,
23+
24+
#[arg(long)]
25+
verbose: bool,
26+
}
27+
28+
impl From<Args> for Config {
29+
fn from(args: Args) -> Config {
30+
let mut config = Config::default();
31+
config.mode = args.mode.parse().expect("Invalid mode");
32+
config.src_base = args.src_base;
33+
config.rustc_path = args.driver_path;
34+
config.build_base = args.output_path;
35+
config.verbose = args.verbose;
36+
config.run_lib_path = PathBuf::from(env!("RUSTC_LIB_PATH"));
37+
config.link_deps();
38+
config
39+
}
40+
}

tools/compiletest/src/main.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//! Run compiletest on a given folder.
2+
3+
mod args;
4+
use clap::Parser;
5+
use compiletest_rs::Config;
6+
7+
fn main() {
8+
let args = args::Args::parse();
9+
println!("args: ${args:?}");
10+
let cfg = Config::from(args);
11+
compiletest_rs::run_tests(&cfg);
12+
}

tools/test-drive/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "test-drive"
3+
description = "A rustc wrapper that can be used to test stable-mir on a crate"
4+
version = "0.0.0"
5+
edition = "2021"
6+
7+
[dependencies]
8+
9+
[package.metadata.rust-analyzer]
10+
# This crate uses #[feature(rustc_private)].
11+
# See https://github.com/rust-analyzer/rust-analyzer/pull/7891
12+
rustc_private = true

tools/test-drive/build.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use std::env;
2+
use std::path::PathBuf;
3+
4+
pub fn main() {
5+
// Add rustup to the rpath in order to properly link with the correct rustc version.
6+
let rustup_home = env::var("RUSTUP_HOME").unwrap();
7+
let toolchain = env::var("RUSTUP_TOOLCHAIN").unwrap();
8+
let rustc_lib: PathBuf = [&rustup_home, "toolchains", &toolchain, "lib"]
9+
.iter()
10+
.collect();
11+
println!(
12+
"cargo:rustc-link-arg-bin=test-drive=-Wl,-rpath,{}",
13+
rustc_lib.display()
14+
);
15+
}

tools/test-drive/src/main.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//! Test that users are able to inspec the MIR body of functions and types
2+
3+
#![feature(rustc_private)]
4+
#![feature(assert_matches)]
5+
#![feature(result_option_inspect)]
6+
7+
mod sanity_checks;
8+
9+
extern crate rustc_middle;
10+
extern crate rustc_smir;
11+
12+
use rustc_middle::ty::TyCtxt;
13+
use rustc_smir::{rustc_internal, stable_mir};
14+
use std::panic::{catch_unwind, AssertUnwindSafe};
15+
use std::process::ExitCode;
16+
17+
const CHECK_ARG: &str = "--check-smir";
18+
19+
type TestResult = Result<(), String>;
20+
21+
/// This is a wrapper that can be used to replace rustc.
22+
///
23+
/// Besides all supported rustc arguments, use `--check-smir` to run all the stable-mir checks.
24+
/// This allows us to use this tool in cargo projects to analyze the target crate only by running
25+
/// `cargo rustc --check-smir`.
26+
fn main() -> ExitCode {
27+
let mut check_smir = false;
28+
let args: Vec<_> = std::env::args()
29+
.filter(|arg| {
30+
let is_check_arg = arg == CHECK_ARG;
31+
check_smir |= is_check_arg;
32+
!is_check_arg
33+
})
34+
.collect();
35+
36+
37+
let callback = if check_smir { test_stable_mir } else { |_: TyCtxt| ExitCode::SUCCESS };
38+
let result = rustc_internal::StableMir::new(args, callback).continue_compilation().run();
39+
if let Ok(test_result) = result {
40+
test_result
41+
} else {
42+
ExitCode::FAILURE
43+
}
44+
}
45+
46+
macro_rules! run_tests {
47+
($( $test:path ),+) => {
48+
[$({
49+
run_test(stringify!($test), || { $test() })
50+
},)+]
51+
};
52+
}
53+
54+
/// This function invoke other tests and process their results.
55+
/// Tests should avoid panic,
56+
fn test_stable_mir(tcx: TyCtxt<'_>) -> ExitCode {
57+
let results = run_tests![
58+
sanity_checks::test_entry_fn,
59+
sanity_checks::test_all_fns,
60+
sanity_checks::test_traits,
61+
sanity_checks::test_crates
62+
];
63+
let (success, failure): (Vec<_>, Vec<_>) = results.iter().partition(|r| r.is_ok());
64+
println!(
65+
"Ran {} tests. {} succeeded. {} failed",
66+
results.len(),
67+
success.len(),
68+
failure.len()
69+
);
70+
if failure.is_empty() {
71+
ExitCode::SUCCESS
72+
} else {
73+
ExitCode::FAILURE
74+
}
75+
}
76+
77+
fn run_test<F: FnOnce() -> TestResult>(name: &str, f: F) -> TestResult {
78+
let result = match catch_unwind(AssertUnwindSafe(f)) {
79+
Err(_) => Err("Panic: {}".to_string()),
80+
Ok(result) => result,
81+
};
82+
println!(
83+
"Test {}: {}",
84+
name,
85+
result.as_ref().err().unwrap_or(&"Ok".to_string())
86+
);
87+
result
88+
}

0 commit comments

Comments
 (0)