From f40ef997ac99e6b202fbd6d25840672fc09bfbe7 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Thu, 31 Aug 2023 15:11:26 -0700 Subject: [PATCH 1/5] 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. --- .github/scripts/run_rustc_tests.sh | 35 ++++++++ Cargo.toml | 12 +++ rust-toolchain.toml | 3 + tools/compiletest/Cargo.toml | 14 +++ tools/compiletest/build.rs | 16 ++++ tools/compiletest/src/args.rs | 40 +++++++++ tools/compiletest/src/main.rs | 12 +++ tools/test-drive/Cargo.toml | 12 +++ tools/test-drive/build.rs | 15 ++++ tools/test-drive/src/main.rs | 88 +++++++++++++++++++ tools/test-drive/src/sanity_checks.rs | 121 ++++++++++++++++++++++++++ 11 files changed, 368 insertions(+) create mode 100755 .github/scripts/run_rustc_tests.sh create mode 100644 Cargo.toml create mode 100644 rust-toolchain.toml create mode 100644 tools/compiletest/Cargo.toml create mode 100644 tools/compiletest/build.rs create mode 100644 tools/compiletest/src/args.rs create mode 100644 tools/compiletest/src/main.rs create mode 100644 tools/test-drive/Cargo.toml create mode 100644 tools/test-drive/build.rs create mode 100644 tools/test-drive/src/main.rs create mode 100644 tools/test-drive/src/sanity_checks.rs diff --git a/.github/scripts/run_rustc_tests.sh b/.github/scripts/run_rustc_tests.sh new file mode 100755 index 0000000..a6938b7 --- /dev/null +++ b/.github/scripts/run_rustc_tests.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -e +set -u + +# Location of a rust repository. Clone one if path doesn't exist. +RUST_REPO="${RUST_REPO:?Missing path to rust repository. Set RUST_REPO}" +# Where we will store the SMIR tools (Optional). +TOOLS_BIN="${TOOLS_BIN:-"/tmp/smir/bin"}" +# Assume we are inside SMIR repository +SMIR_PATH=$(git rev-parse --show-toplevel) +export RUST_BACKTRACE=1 + +pushd "${SMIR_PATH}" +cargo +smir build -Z unstable-options --out-dir "${TOOLS_BIN}" +export PATH="${TOOLS_BIN}":"${PATH}" + +if [[ ! -e "${RUST_REPO}" ]]; then + mkdir -p "$(dirname ${RUST_REPO})" + git clone --depth 1 https://github.com/rust-lang/rust.git "${RUST_REPO}" +fi + +pushd "${RUST_REPO}" +SUITES=( + # Match https://github.com/rust-lang/rust/blob/master/src/bootstrap/test.rs for now + "tests/ui/cfg ui" +) +for suite_cfg in "${SUITES[@]}"; do + # Hack to work on older bash like the ones on MacOS. + suite_pair=($suite_cfg) + suite=${suite_pair[0]} + mode=${suite_pair[1]} + echo "${suite_cfg} pair: $suite_pair mode: $mode" + compiletest --driver-path="${TOOLS_BIN}/test-drive" --mode=${mode} --src-base="${suite}" --output-path "${RUST_REPO}/build" +done diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e90ec9c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +# Cargo workspace for utility tools used to check stable-mir in CI +[workspace] +resolver = "2" +members = [ + "tools/compiletest", + "tools/test-drive", +] + +exclude = [ + "build", + "target", +] diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..ee9dc17 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +components = ["llvm-tools-preview", "rustc-dev", "rust-src", "rustfmt"] diff --git a/tools/compiletest/Cargo.toml b/tools/compiletest/Cargo.toml new file mode 100644 index 0000000..94fea23 --- /dev/null +++ b/tools/compiletest/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "compiletest" +description = "Run tests using compiletest-rs" +version = "0.0.0" +edition = "2021" + +[dependencies] +compiletest_rs = { version = "0.10.0", features = [ "rustc" ] } +clap = { version = "4.1.3", features = ["derive"] } + +[package.metadata.rust-analyzer] +# This crate uses #[feature(rustc_private)]. +# See https://github.com/rust-analyzer/rust-analyzer/pull/7891 +rustc_private = true diff --git a/tools/compiletest/build.rs b/tools/compiletest/build.rs new file mode 100644 index 0000000..63fcfb5 --- /dev/null +++ b/tools/compiletest/build.rs @@ -0,0 +1,16 @@ +use std::env; +use std::path::PathBuf; + +pub fn main() { + // Add rustup to the rpath in order to properly link with the correct rustc version. + let rustup_home = env::var("RUSTUP_HOME").unwrap(); + let toolchain = env::var("RUSTUP_TOOLCHAIN").unwrap(); + let rustc_lib: PathBuf = [&rustup_home, "toolchains", &toolchain, "lib"] + .iter() + .collect(); + println!( + "cargo:rustc-link-arg-bin=compiletest=-Wl,-rpath,{}", + rustc_lib.display() + ); + println!("cargo:rustc-env=RUSTC_LIB_PATH={}", rustc_lib.display()); +} diff --git a/tools/compiletest/src/args.rs b/tools/compiletest/src/args.rs new file mode 100644 index 0000000..72a5f83 --- /dev/null +++ b/tools/compiletest/src/args.rs @@ -0,0 +1,40 @@ +use compiletest_rs::Config; +use std::fmt::Debug; +use std::path::PathBuf; + +#[derive(Debug, clap::Parser)] +#[command(version, name = "compiletest")] +pub struct Args { + /// The path where all tests are + #[arg(long)] + src_base: PathBuf, + + /// The mode according to compiletest modes. + #[arg(long)] + mode: String, + + /// Path for the stable-mir driver. + #[arg(long)] + driver_path: PathBuf, + + /// Path for where the output should be stored. + #[arg(long)] + output_path: PathBuf, + + #[arg(long)] + verbose: bool, +} + +impl From for Config { + fn from(args: Args) -> Config { + let mut config = Config::default(); + config.mode = args.mode.parse().expect("Invalid mode"); + config.src_base = args.src_base; + config.rustc_path = args.driver_path; + config.build_base = args.output_path; + config.verbose = args.verbose; + config.run_lib_path = PathBuf::from(env!("RUSTC_LIB_PATH")); + config.link_deps(); + config + } +} diff --git a/tools/compiletest/src/main.rs b/tools/compiletest/src/main.rs new file mode 100644 index 0000000..b27f8ee --- /dev/null +++ b/tools/compiletest/src/main.rs @@ -0,0 +1,12 @@ +//! Run compiletest on a given folder. + +mod args; +use clap::Parser; +use compiletest_rs::Config; + +fn main() { + let args = args::Args::parse(); + println!("args: ${args:?}"); + let cfg = Config::from(args); + compiletest_rs::run_tests(&cfg); +} diff --git a/tools/test-drive/Cargo.toml b/tools/test-drive/Cargo.toml new file mode 100644 index 0000000..12b88aa --- /dev/null +++ b/tools/test-drive/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "test-drive" +description = "A rustc wrapper that can be used to test stable-mir on a crate" +version = "0.0.0" +edition = "2021" + +[dependencies] + +[package.metadata.rust-analyzer] +# This crate uses #[feature(rustc_private)]. +# See https://github.com/rust-analyzer/rust-analyzer/pull/7891 +rustc_private = true diff --git a/tools/test-drive/build.rs b/tools/test-drive/build.rs new file mode 100644 index 0000000..44c9940 --- /dev/null +++ b/tools/test-drive/build.rs @@ -0,0 +1,15 @@ +use std::env; +use std::path::PathBuf; + +pub fn main() { + // Add rustup to the rpath in order to properly link with the correct rustc version. + let rustup_home = env::var("RUSTUP_HOME").unwrap(); + let toolchain = env::var("RUSTUP_TOOLCHAIN").unwrap(); + let rustc_lib: PathBuf = [&rustup_home, "toolchains", &toolchain, "lib"] + .iter() + .collect(); + println!( + "cargo:rustc-link-arg-bin=test-drive=-Wl,-rpath,{}", + rustc_lib.display() + ); +} diff --git a/tools/test-drive/src/main.rs b/tools/test-drive/src/main.rs new file mode 100644 index 0000000..d8f20d7 --- /dev/null +++ b/tools/test-drive/src/main.rs @@ -0,0 +1,88 @@ +//! Test that users are able to inspec the MIR body of functions and types + +#![feature(rustc_private)] +#![feature(assert_matches)] +#![feature(result_option_inspect)] + +mod sanity_checks; + +extern crate rustc_middle; +extern crate rustc_smir; + +use rustc_middle::ty::TyCtxt; +use rustc_smir::{rustc_internal, stable_mir}; +use std::panic::{catch_unwind, AssertUnwindSafe}; +use std::process::ExitCode; + +const CHECK_ARG: &str = "--check-smir"; + +type TestResult = Result<(), String>; + +/// This is a wrapper that can be used to replace rustc. +/// +/// Besides all supported rustc arguments, use `--check-smir` to run all the stable-mir checks. +/// This allows us to use this tool in cargo projects to analyze the target crate only by running +/// `cargo rustc --check-smir`. +fn main() -> ExitCode { + let mut check_smir = false; + let args: Vec<_> = std::env::args() + .filter(|arg| { + let is_check_arg = arg == CHECK_ARG; + check_smir |= is_check_arg; + !is_check_arg + }) + .collect(); + + + let callback = if check_smir { test_stable_mir } else { |_: TyCtxt| ExitCode::SUCCESS }; + let result = rustc_internal::StableMir::new(args, callback).continue_compilation().run(); + if let Ok(test_result) = result { + test_result + } else { + ExitCode::FAILURE + } +} + +macro_rules! run_tests { + ($( $test:path ),+) => { + [$({ + run_test(stringify!($test), || { $test() }) + },)+] + }; +} + +/// This function invoke other tests and process their results. +/// Tests should avoid panic, +fn test_stable_mir(tcx: TyCtxt<'_>) -> ExitCode { + let results = run_tests![ + sanity_checks::test_entry_fn, + sanity_checks::test_all_fns, + sanity_checks::test_traits, + sanity_checks::test_crates + ]; + let (success, failure): (Vec<_>, Vec<_>) = results.iter().partition(|r| r.is_ok()); + println!( + "Ran {} tests. {} succeeded. {} failed", + results.len(), + success.len(), + failure.len() + ); + if failure.is_empty() { + ExitCode::SUCCESS + } else { + ExitCode::FAILURE + } +} + +fn run_test TestResult>(name: &str, f: F) -> TestResult { + let result = match catch_unwind(AssertUnwindSafe(f)) { + Err(_) => Err("Panic: {}".to_string()), + Ok(result) => result, + }; + println!( + "Test {}: {}", + name, + result.as_ref().err().unwrap_or(&"Ok".to_string()) + ); + result +} diff --git a/tools/test-drive/src/sanity_checks.rs b/tools/test-drive/src/sanity_checks.rs new file mode 100644 index 0000000..8267db7 --- /dev/null +++ b/tools/test-drive/src/sanity_checks.rs @@ -0,0 +1,121 @@ +//! Module that contains sanity checks that Stable MIR APIs don't crash and that +//! their result is coherent. +//! +//! These checks should only depend on StableMIR APIs. See other modules for tests that compare +//! the result between StableMIR and internal APIs. +use crate::TestResult; +use rustc_middle::ty::TyCtxt; +use rustc_smir::stable_mir; +use std::collections::HashSet; +use std::fmt::Debug; +use std::hint::black_box; + +fn check_equal(val: T, expected: T, msg: &str) -> TestResult +where + T: Debug + PartialEq, +{ + if val != expected { + Err(format!( + "{}: \n Expected: {:?}\n Found: {:?}", + msg, expected, val + )) + } else { + Ok(()) + } +} + +pub fn check(val: bool, msg: String) -> TestResult { + if !val { + Err(msg) + } else { + Ok(()) + } +} + +// Test that if there is an entry point, the function is part of `all_local_items`. +pub fn test_entry_fn() -> TestResult { + let entry_fn = stable_mir::entry_fn(); + entry_fn.map_or(Ok(()), |entry_fn| { + let all_items = stable_mir::all_local_items(); + check( + all_items.contains(&entry_fn), + format!("Failed to find entry_fn `{:?}`", entry_fn), + ) + }) +} + +/// Check that the crate isn't empty and iterate over function bodies. +pub fn test_all_fns() -> TestResult { + let all_items = stable_mir::all_local_items(); + check( + !all_items.is_empty(), + "Failed to find any local item".to_string(), + )?; + + for item in all_items { + // Get body and iterate over items + let body = item.body(); + check_body(body); + } + Ok(()) +} + +/// FIXME: Create to track improvements to TraitDecls / ImplTraitDecls. +/// Using these structures will always follow calls to get more details about those structures. +/// Unless user is trying to find a specific type, this will get repetitive. +pub fn test_traits() -> TestResult { + let all_traits = stable_mir::all_trait_decls(); + for trait_decl in all_traits.iter().map(stable_mir::trait_decl) { + // Can't compare trait_decl, so just compare a field for now. + check_equal( + stable_mir::trait_decl(&trait_decl.def_id).specialization_kind, + trait_decl.specialization_kind, + "external crate mismatch", + )?; + } + + for trait_impl in stable_mir::all_trait_impls() + .iter() + .map(stable_mir::trait_impl) + { + check( + all_traits.contains(&trait_impl.value.def_id), + format!("Failed to find trait definition {trait_impl:?}"), + )?; + } + Ok(()) +} + +pub fn test_crates() -> TestResult { + for krate in stable_mir::external_crates() { + check_equal( + stable_mir::find_crate(&krate.name.as_str()), + Some(krate), + "external crate mismatch", + )?; + } + + let local = stable_mir::local_crate(); + check_equal( + stable_mir::find_crate(&local.name.as_str()), + Some(local), + "local crate mismatch", + ) +} + +/// Visit all local types, statements and terminator to ensure nothing crashes. +fn check_body(body: stable_mir::mir::Body) { + for bb in body.blocks { + for stmt in bb.statements { + black_box(matches!(stmt, stable_mir::mir::Statement::Assign(..))); + } + black_box(matches!( + bb.terminator, + stable_mir::mir::Terminator::Goto { .. } + )); + } + + for local in body.locals { + black_box(matches!(local.kind(), stable_mir::ty::TyKind::Alias(..))); + } +} From acf8fecda0d3e13eb7072fc7c237a091f10fe45b Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Thu, 31 Aug 2023 20:30:55 -0700 Subject: [PATCH 2/5] Add github file --- .github/scripts/run_rustc_tests.sh | 2 +- .github/workflows/nightly.yml | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/nightly.yml diff --git a/.github/scripts/run_rustc_tests.sh b/.github/scripts/run_rustc_tests.sh index a6938b7..e23bec4 100755 --- a/.github/scripts/run_rustc_tests.sh +++ b/.github/scripts/run_rustc_tests.sh @@ -12,7 +12,7 @@ SMIR_PATH=$(git rev-parse --show-toplevel) export RUST_BACKTRACE=1 pushd "${SMIR_PATH}" -cargo +smir build -Z unstable-options --out-dir "${TOOLS_BIN}" +cargo build -Z unstable-options --out-dir "${TOOLS_BIN}" export PATH="${TOOLS_BIN}":"${PATH}" if [[ ! -e "${RUST_REPO}" ]]; then diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..0fa20bb --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,21 @@ +name: Run compiler tests + +on: + schedule: + - cron: "0 6 * * *" # Run daily at 06:00 UTC + workflow_dispatch: # Allow manual dispatching + pull_request: + +jobs: + compile-test: + name: Rust Compiler Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Test + run: ./.github/scripts/run_rustc_tests.sh + env: + RUST_REPO: "/tmp/rustc" + TOOLS_BIN: "/tmp/smir/bin" From 96ac2b41cb4fae0c4c427c777563e6f80dce9aae Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Wed, 6 Sep 2023 20:47:36 -0700 Subject: [PATCH 3/5] Use ui_tests --- .github/scripts/run_rustc_tests.sh | 4 +- tools/compiletest/Cargo.toml | 2 +- tools/compiletest/src/args.rs | 58 ++++++++++++++++++++------- tools/compiletest/src/main.rs | 33 ++++++++++++--- tools/test-drive/src/main.rs | 43 +++++++++++++------- tools/test-drive/src/sanity_checks.rs | 5 ++- 6 files changed, 107 insertions(+), 38 deletions(-) diff --git a/.github/scripts/run_rustc_tests.sh b/.github/scripts/run_rustc_tests.sh index e23bec4..992650e 100755 --- a/.github/scripts/run_rustc_tests.sh +++ b/.github/scripts/run_rustc_tests.sh @@ -23,7 +23,7 @@ fi pushd "${RUST_REPO}" SUITES=( # Match https://github.com/rust-lang/rust/blob/master/src/bootstrap/test.rs for now - "tests/ui/cfg ui" + "tests/ui/cfg yolo" ) for suite_cfg in "${SUITES[@]}"; do # Hack to work on older bash like the ones on MacOS. @@ -31,5 +31,5 @@ for suite_cfg in "${SUITES[@]}"; do suite=${suite_pair[0]} mode=${suite_pair[1]} echo "${suite_cfg} pair: $suite_pair mode: $mode" - compiletest --driver-path="${TOOLS_BIN}/test-drive" --mode=${mode} --src-base="${suite}" --output-path "${RUST_REPO}/build" + compiletest --driver-path="${TOOLS_BIN}/test-drive" --mode=${mode} --src-base="${suite}" --output-dir="${RUST_REPO}/build" done diff --git a/tools/compiletest/Cargo.toml b/tools/compiletest/Cargo.toml index 94fea23..dedf166 100644 --- a/tools/compiletest/Cargo.toml +++ b/tools/compiletest/Cargo.toml @@ -5,7 +5,7 @@ version = "0.0.0" edition = "2021" [dependencies] -compiletest_rs = { version = "0.10.0", features = [ "rustc" ] } +ui_test = "0.20.0" clap = { version = "4.1.3", features = ["derive"] } [package.metadata.rust-analyzer] diff --git a/tools/compiletest/src/args.rs b/tools/compiletest/src/args.rs index 72a5f83..747db63 100644 --- a/tools/compiletest/src/args.rs +++ b/tools/compiletest/src/args.rs @@ -1,6 +1,22 @@ -use compiletest_rs::Config; +/// Create our own parser and build the Config from it. use std::fmt::Debug; use std::path::PathBuf; +use ui_test::{CommandBuilder, Config, OutputConflictHandling, RustfixMode}; + +#[derive(Copy, Clone, Debug, clap::ValueEnum)] +/// Decides what is expected of each test's exit status. +pub enum Mode { + /// The test passes a full execution of the rustc driver + Pass, + /// The test produces an executable binary that can get executed on the host + Run, + /// The rustc driver panicked + Panic, + /// The rustc driver emitted an error + Fail, + /// Run the tests, but always pass them as long as all annotations are satisfied and stderr files match. + Yolo, +} #[derive(Debug, clap::Parser)] #[command(version, name = "compiletest")] @@ -9,9 +25,9 @@ pub struct Args { #[arg(long)] src_base: PathBuf, - /// The mode according to compiletest modes. - #[arg(long)] - mode: String, + /// The mode according to ui_test modes. + #[arg(long, default_value="yolo")] + mode: Mode, /// Path for the stable-mir driver. #[arg(long)] @@ -19,22 +35,36 @@ pub struct Args { /// Path for where the output should be stored. #[arg(long)] - output_path: PathBuf, + output_dir: PathBuf, #[arg(long)] - verbose: bool, + pub verbose: bool, +} + +impl From for ui_test::Mode { + /// Use rustc configuration as default but override arguments to fit our use case. + fn from(mode: Mode) -> ui_test::Mode { + match mode { + Mode::Pass => { ui_test::Mode::Pass } + Mode::Run => { ui_test::Mode::Run { exit_code: 0 }} + Mode::Panic => { ui_test::Mode::Panic } + Mode::Fail => { ui_test::Mode::Fail { require_patterns: false, rustfix: RustfixMode::Disabled }} + Mode::Yolo => { ui_test::Mode::Yolo { rustfix: RustfixMode::Disabled }} + } + } } impl From for Config { + /// Use rustc configuration as default but override arguments to fit our use case. fn from(args: Args) -> Config { - let mut config = Config::default(); - config.mode = args.mode.parse().expect("Invalid mode"); - config.src_base = args.src_base; - config.rustc_path = args.driver_path; - config.build_base = args.output_path; - config.verbose = args.verbose; - config.run_lib_path = PathBuf::from(env!("RUSTC_LIB_PATH")); - config.link_deps(); + let mut config = Config::rustc(args.src_base); + config.program = CommandBuilder::rustc(); + config.program.program = args.driver_path; + config.program.args.push("--check-smir".into()); + config.mode = ui_test::Mode::from(args.mode); + config.output_conflict_handling = OutputConflictHandling::Error("Should Fail".to_string()); + config.out_dir = args.output_dir; + //config.run_lib_path = PathBuf::from(env!("RUSTC_LIB_PATH")); config } } diff --git a/tools/compiletest/src/main.rs b/tools/compiletest/src/main.rs index b27f8ee..370565b 100644 --- a/tools/compiletest/src/main.rs +++ b/tools/compiletest/src/main.rs @@ -1,12 +1,35 @@ //! Run compiletest on a given folder. mod args; + +use std::process::ExitCode; use clap::Parser; -use compiletest_rs::Config; +use ui_test::{status_emitter, Config}; -fn main() { +fn main() -> ExitCode { let args = args::Args::parse(); - println!("args: ${args:?}"); - let cfg = Config::from(args); - compiletest_rs::run_tests(&cfg); + let verbose = args.verbose; + if verbose { + println!("args: ${args:?}"); + } + let config = Config::from(args); + if verbose { + println!("Compiler: {}", config.program.display()); + } + + let name = config.root_dir.display().to_string(); + + let text = if verbose { + status_emitter::Text::verbose() + } else { + status_emitter::Text::quiet() + }; + + let result = ui_test::run_tests_generic( + vec![config], + ui_test::default_file_filter, + ui_test::default_per_file_config, + (text, status_emitter::Gha:: { name }), + ); + if result.is_ok() { ExitCode::SUCCESS } else { ExitCode::FAILURE } } diff --git a/tools/test-drive/src/main.rs b/tools/test-drive/src/main.rs index d8f20d7..ea5fa61 100644 --- a/tools/test-drive/src/main.rs +++ b/tools/test-drive/src/main.rs @@ -10,11 +10,17 @@ extern crate rustc_middle; extern crate rustc_smir; use rustc_middle::ty::TyCtxt; -use rustc_smir::{rustc_internal, stable_mir}; +use rustc_smir::rustc_internal; +use rustc_smir::stable_mir::CompilerError; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::ops::ControlFlow; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::process::ExitCode; const CHECK_ARG: &str = "--check-smir"; +const VERBOSE_ARG: &str = "--verbose"; +// Use a static variable for simplicity. +static VERBOSE: AtomicBool = AtomicBool::new(false); type TestResult = Result<(), String>; @@ -32,12 +38,15 @@ fn main() -> ExitCode { !is_check_arg }) .collect(); - - - let callback = if check_smir { test_stable_mir } else { |_: TyCtxt| ExitCode::SUCCESS }; - let result = rustc_internal::StableMir::new(args, callback).continue_compilation().run(); - if let Ok(test_result) = result { - test_result + VERBOSE.store(args.iter().any(|arg| &*arg == VERBOSE_ARG), Ordering::Relaxed); + let callback = if check_smir { + test_stable_mir + } else { + |_: TyCtxt| ControlFlow::<()>::Continue(()) + }; + let result = rustc_internal::StableMir::new(args, callback).run(); + if result.is_ok() || matches!(result, Err(CompilerError::Skipped)) { + ExitCode::SUCCESS } else { ExitCode::FAILURE } @@ -51,9 +60,15 @@ macro_rules! run_tests { }; } +fn info(msg: String) { + if VERBOSE.load(Ordering::Relaxed) { + println!("{}", msg); + } +} + /// This function invoke other tests and process their results. /// Tests should avoid panic, -fn test_stable_mir(tcx: TyCtxt<'_>) -> ExitCode { +fn test_stable_mir(_tcx: TyCtxt<'_>) -> ControlFlow<()> { let results = run_tests![ sanity_checks::test_entry_fn, sanity_checks::test_all_fns, @@ -61,16 +76,16 @@ fn test_stable_mir(tcx: TyCtxt<'_>) -> ExitCode { sanity_checks::test_crates ]; let (success, failure): (Vec<_>, Vec<_>) = results.iter().partition(|r| r.is_ok()); - println!( + info(format!( "Ran {} tests. {} succeeded. {} failed", results.len(), success.len(), failure.len() - ); + )); if failure.is_empty() { - ExitCode::SUCCESS + ControlFlow::<()>::Continue(()) } else { - ExitCode::FAILURE + ControlFlow::<()>::Break(()) } } @@ -79,10 +94,10 @@ fn run_test TestResult>(name: &str, f: F) -> TestResult { Err(_) => Err("Panic: {}".to_string()), Ok(result) => result, }; - println!( + info(format!( "Test {}: {}", name, result.as_ref().err().unwrap_or(&"Ok".to_string()) - ); + )); result } diff --git a/tools/test-drive/src/sanity_checks.rs b/tools/test-drive/src/sanity_checks.rs index 8267db7..fa1e275 100644 --- a/tools/test-drive/src/sanity_checks.rs +++ b/tools/test-drive/src/sanity_checks.rs @@ -4,9 +4,7 @@ //! These checks should only depend on StableMIR APIs. See other modules for tests that compare //! the result between StableMIR and internal APIs. use crate::TestResult; -use rustc_middle::ty::TyCtxt; use rustc_smir::stable_mir; -use std::collections::HashSet; use std::fmt::Debug; use std::hint::black_box; @@ -36,6 +34,7 @@ pub fn check(val: bool, msg: String) -> TestResult { pub fn test_entry_fn() -> TestResult { let entry_fn = stable_mir::entry_fn(); entry_fn.map_or(Ok(()), |entry_fn| { + check_body(entry_fn.body()); let all_items = stable_mir::all_local_items(); check( all_items.contains(&entry_fn), @@ -52,6 +51,8 @@ pub fn test_all_fns() -> TestResult { "Failed to find any local item".to_string(), )?; + // Not sure which API to use to make sure this is a function that has a body. + #[cfg(skip)] for item in all_items { // Get body and iterate over items let body = item.body(); From 825e1bce8727832759a5d1fe6de67cb87f8ec497 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Mon, 25 Sep 2023 17:28:36 -0700 Subject: [PATCH 4/5] Split compiler tests and local tests --- .github/scripts/run_own_tests.sh | 41 +++++++++ .github/scripts/run_rustc_tests.sh | 109 ++++++++++++++++++----- .github/workflows/nightly.yml | 12 ++- tests/fixme/associated-items/methods.rs | 83 +++++++++++++++++ tests/sanity-checks/simple/simple_lib.rs | 23 +++++ tools/compiletest/src/args.rs | 65 +++++++++++--- tools/compiletest/src/main.rs | 8 +- tools/test-drive/src/main.rs | 10 ++- tools/test-drive/src/sanity_checks.rs | 1 - 9 files changed, 311 insertions(+), 41 deletions(-) create mode 100755 .github/scripts/run_own_tests.sh create mode 100644 tests/fixme/associated-items/methods.rs create mode 100644 tests/sanity-checks/simple/simple_lib.rs diff --git a/.github/scripts/run_own_tests.sh b/.github/scripts/run_own_tests.sh new file mode 100755 index 0000000..894d8cc --- /dev/null +++ b/.github/scripts/run_own_tests.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Execute our own set of tests using a local `compiletest` tool based on `ui_test`. +set -e +set -u + +# Where we will store the SMIR tools (Optional). +TOOLS_BIN="${TOOLS_BIN:-"/tmp/smir/bin"}" +# Assume we are inside SMIR repository +SMIR_PATH=$(git rev-parse --show-toplevel) +export RUST_BACKTRACE=1 + +# Build stable_mir tools +function build_smir_tools() { + pushd "${SMIR_PATH}" + cargo +nightly build -Z unstable-options --out-dir "${TOOLS_BIN}" + export PATH="${TOOLS_BIN}":"${PATH}" +} + +# Run tests +function run_tests() { + SUITES=( + "sanity-checks pass" + "fixme fail" + ) + for suite_cfg in "${SUITES[@]}"; do + # Hack to work on older bash like the ones on MacOS. + suite_pair=($suite_cfg) + suite=${suite_pair[0]} + mode=${suite_pair[1]} + echo "#### Running suite: ${suite} mode: ${mode}" + compiletest \ + --driver-path="${TOOLS_BIN}/test-drive" \ + --mode=${mode} \ + --src-base="tests/${suite}" \ + --output-dir="target/tests/" \ + --no-capture + done +} + +build_smir_tools +run_tests diff --git a/.github/scripts/run_rustc_tests.sh b/.github/scripts/run_rustc_tests.sh index 992650e..2dc9538 100755 --- a/.github/scripts/run_rustc_tests.sh +++ b/.github/scripts/run_rustc_tests.sh @@ -1,35 +1,96 @@ #!/usr/bin/env bash +# Run rustc test suites using our test driver using nightly. +# This script leverages the rustc's repo compiletest crate. +# +# The suites configuration should match: +# https://github.com/rust-lang/rust/blob/master/src/bootstrap/test.rs + set -e set -u +export RUST_BACKTRACE=1 # Location of a rust repository. Clone one if path doesn't exist. -RUST_REPO="${RUST_REPO:?Missing path to rust repository. Set RUST_REPO}" +RUST_REPO="${RUST_REPO:-"/tmp/rustc"}" + # Where we will store the SMIR tools (Optional). TOOLS_BIN="${TOOLS_BIN:-"/tmp/smir/bin"}" + # Assume we are inside SMIR repository SMIR_PATH=$(git rev-parse --show-toplevel) -export RUST_BACKTRACE=1 -pushd "${SMIR_PATH}" -cargo build -Z unstable-options --out-dir "${TOOLS_BIN}" -export PATH="${TOOLS_BIN}":"${PATH}" - -if [[ ! -e "${RUST_REPO}" ]]; then - mkdir -p "$(dirname ${RUST_REPO})" - git clone --depth 1 https://github.com/rust-lang/rust.git "${RUST_REPO}" -fi - -pushd "${RUST_REPO}" -SUITES=( - # Match https://github.com/rust-lang/rust/blob/master/src/bootstrap/test.rs for now - "tests/ui/cfg yolo" -) -for suite_cfg in "${SUITES[@]}"; do - # Hack to work on older bash like the ones on MacOS. - suite_pair=($suite_cfg) - suite=${suite_pair[0]} - mode=${suite_pair[1]} - echo "${suite_cfg} pair: $suite_pair mode: $mode" - compiletest --driver-path="${TOOLS_BIN}/test-drive" --mode=${mode} --src-base="${suite}" --output-dir="${RUST_REPO}/build" -done +# Build stable_mir tools +function build_smir_tools() { + pushd "${SMIR_PATH}" + cargo +nightly build -Z unstable-options --out-dir "${TOOLS_BIN}" + export PATH="${TOOLS_BIN}":"${PATH}" +} + +# Set up rustc repository +function setup_rustc_repo() { + if [[ ! -e "${RUST_REPO}" ]]; then + mkdir -p "$(dirname ${RUST_REPO})" + git clone -b master https://github.com/rust-lang/rust.git "${RUST_REPO}" + pushd "${RUST_REPO}" + commit="$(rustc +nightly -vV | awk '/^commit-hash/ { print $2 }')" + git checkout ${commit} + git submodule init -- "${RUST_REPO}/library/stdarch" + git submodule update + else + pushd "${RUST_REPO}" + fi +} + +function run_tests() { + # Run the following suite configuration for now (test suite + mode) + SUITES=( + "codegen codegen" + "codegen-units codegen-units" + # -- The suites below are failing because of fully qualified paths for standard library + # E.g.: + # - _10 = _eprint(move _11) -> [return: bb6, unwind unreachable]; + # + _10 = std::io::_eprint(move _11) -> [return: bb6, unwind unreachable]; + # + #"ui ui" + #"mir-opt mir-opt" + #"pretty pretty" -- 2 failing tests + ) + + SYSROOT=$(rustc +nightly --print sysroot) + PY_PATH=$(type -P python3) + HOST=$(rustc +nightly -vV | awk '/^host/ { print $2 }') + FILE_CHECK="$(which FileCheck-12 || which FileCheck-13 || which FileCheck-14)" + + for suite_cfg in "${SUITES[@]}"; do + # Hack to work on older bash like the ones on MacOS. + suite_pair=($suite_cfg) + suite=${suite_pair[0]} + mode=${suite_pair[1]} + + echo "#### Running suite: ${suite} mode: ${mode}" + cargo +nightly run -p compiletest -- \ + --compile-lib-path="${SYSROOT}/lib" \ + --run-lib-path="${SYSROOT}/lib"\ + --python="${PY_PATH}" \ + --rustc-path="${TOOLS_BIN}/test-drive" \ + --mode=${mode} \ + --suite="${suite}" \ + --src-base="tests/${suite}" \ + --build-base="$(pwd)/build/${HOST}/stage1/tests/${suite}" \ + --sysroot-base="$SYSROOT" \ + --stage-id=stage1-${HOST} \ + --cc= \ + --cxx= \ + --cflags= \ + --cxxflags= \ + --llvm-components= \ + --android-cross-path= \ + --target=${HOST} \ + --llvm-filecheck="${FILE_CHECK}" \ + --channel=nightly + done +} + +build_smir_tools +setup_rustc_repo +run_tests diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 0fa20bb..216eda1 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -14,7 +14,17 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Test + - name: Install latest nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + + - name: Test local suites + run: ./.github/scripts/run_own_tests.sh + env: + TOOLS_BIN: "/tmp/smir/bin" + + - name: Test rustc suites run: ./.github/scripts/run_rustc_tests.sh env: RUST_REPO: "/tmp/rustc" diff --git a/tests/fixme/associated-items/methods.rs b/tests/fixme/associated-items/methods.rs new file mode 100644 index 0000000..4d1ac0d --- /dev/null +++ b/tests/fixme/associated-items/methods.rs @@ -0,0 +1,83 @@ +//! Example derived from +#![feature(box_into_inner)] + +use std::pin::Pin; +use std::rc::Rc; +use std::sync::Arc; + +#[derive(Default, Debug)] +struct Example { + inner: String, +} + +type Alias = Example; +trait Trait { + type Output; +} +impl Trait for Example { + type Output = Example; +} + +#[allow(unused)] +impl Example { + pub fn by_value(self: Self) { + self.by_ref("by_val"); + } + + pub fn by_ref(self: &Self, source: &str) { + println!("{source}: {}", self.inner); + } + + pub fn by_ref_mut(self: &mut Self) { + self.inner = "by_ref_mut".to_string(); + self.by_ref("mut"); + } + + pub fn by_box(self: Box) { + self.by_ref("by_box"); + Box::into_inner(self).by_value(); + } + + pub fn by_rc(self: Rc) { + self.by_ref("by_rc"); + } + + pub fn by_arc(self: Arc) { + self.by_ref("by_arc"); + } + + pub fn by_pin(self: Pin<&Self>) { + self.by_ref("by_pin"); + } + + pub fn explicit_type(self: Arc) { + self.by_ref("explicit"); + } + + pub fn with_lifetime<'a>(self: &'a Self) { + self.by_ref("lifetime"); + } + + pub fn nested<'a>(self: &mut &'a Arc>>) { + self.by_ref("nested"); + } + + pub fn via_projection(self: ::Output) { + self.by_ref("via_projection"); + } + + pub fn from(name: String) -> Self { + Example { inner: name } + } +} + +fn main() { + let example = Example::from("Hello".to_string()); + example.by_value(); + + let boxed = Box::new(Example::default()); + boxed.by_box(); + + Example::default().by_ref_mut(); + Example::default().with_lifetime(); +} diff --git a/tests/sanity-checks/simple/simple_lib.rs b/tests/sanity-checks/simple/simple_lib.rs new file mode 100644 index 0000000..b86f9fe --- /dev/null +++ b/tests/sanity-checks/simple/simple_lib.rs @@ -0,0 +1,23 @@ +//! Just a simple library + +pub struct Point { + pub x: i64, + pub y: i64, +} + +impl Point { + pub fn distance(&self, other: &Point) -> u64 { + let (x_dist, x_over) = (self.x - other.x).overflowing_pow(2); + let (y_dist, y_over) = (self.y - other.y).overflowing_pow(2); + if y_over | x_over { + panic!("overflow"); + } + + let dist = (x_dist as u64 + y_dist as u64) >> 1; + dist + } + + pub fn distance_root(&self) -> u64 { + self.distance(&Point { x: 0, y: 0 }) + } +} diff --git a/tools/compiletest/src/args.rs b/tools/compiletest/src/args.rs index 747db63..e783954 100644 --- a/tools/compiletest/src/args.rs +++ b/tools/compiletest/src/args.rs @@ -1,3 +1,4 @@ +use std::ffi::OsString; /// Create our own parser and build the Config from it. use std::fmt::Debug; use std::path::PathBuf; @@ -16,6 +17,8 @@ pub enum Mode { Fail, /// Run the tests, but always pass them as long as all annotations are satisfied and stderr files match. Yolo, + /// The test passes a full execution of `cargo build` + CargoPass, } #[derive(Debug, clap::Parser)] @@ -26,7 +29,7 @@ pub struct Args { src_base: PathBuf, /// The mode according to ui_test modes. - #[arg(long, default_value="yolo")] + #[arg(long, default_value = "yolo")] mode: Mode, /// Path for the stable-mir driver. @@ -39,17 +42,26 @@ pub struct Args { #[arg(long)] pub verbose: bool, + + /// Run test-driver on verbose mode to print test outputs. + #[arg(long)] + pub no_capture: bool, } impl From for ui_test::Mode { /// Use rustc configuration as default but override arguments to fit our use case. fn from(mode: Mode) -> ui_test::Mode { match mode { - Mode::Pass => { ui_test::Mode::Pass } - Mode::Run => { ui_test::Mode::Run { exit_code: 0 }} - Mode::Panic => { ui_test::Mode::Panic } - Mode::Fail => { ui_test::Mode::Fail { require_patterns: false, rustfix: RustfixMode::Disabled }} - Mode::Yolo => { ui_test::Mode::Yolo { rustfix: RustfixMode::Disabled }} + Mode::Pass | Mode::CargoPass => ui_test::Mode::Pass, + Mode::Run => ui_test::Mode::Run { exit_code: 0 }, + Mode::Panic => ui_test::Mode::Panic, + Mode::Fail => ui_test::Mode::Fail { + require_patterns: false, + rustfix: RustfixMode::Disabled, + }, + Mode::Yolo => ui_test::Mode::Yolo { + rustfix: RustfixMode::Disabled, + }, } } } @@ -57,10 +69,12 @@ impl From for ui_test::Mode { impl From for Config { /// Use rustc configuration as default but override arguments to fit our use case. fn from(args: Args) -> Config { - let mut config = Config::rustc(args.src_base); - config.program = CommandBuilder::rustc(); - config.program.program = args.driver_path; - config.program.args.push("--check-smir".into()); + let mut config = if matches!(args.mode, Mode::CargoPass) { + cargo_config(&args) + } else { + driver_config(&args) + }; + config.filter(r"\[T-DRIVE\].*\n", ""); config.mode = ui_test::Mode::from(args.mode); config.output_conflict_handling = OutputConflictHandling::Error("Should Fail".to_string()); config.out_dir = args.output_dir; @@ -68,3 +82,34 @@ impl From for Config { config } } + +fn rustc_flags(args: &Args) -> Vec { + let mut flags = vec!["--check-smir".into()]; + if args.verbose || args.no_capture { + flags.push("--verbose".into()); + } + flags +} + +/// Configure cargo tests that will run the test-driver instead of rustc. +fn cargo_config(args: &Args) -> Config { + let mut config = Config::cargo(args.src_base.clone()); + config.program.envs.push(( + "RUST".into(), + Some(args.driver_path.clone().into_os_string()), + )); + config.program.envs.push(( + "CARGO_ENCODED_RUSTFLAGS".into(), + Some(rustc_flags(args).join(&OsString::from("\x1f")).into()), + )); + config +} + +/// Configure tests that will invoke the test-driver directly as rustc. +fn driver_config(args: &Args) -> Config { + let mut config = Config::rustc(args.src_base.clone()); + config.program = CommandBuilder::rustc(); + config.program.program = args.driver_path.clone(); + config.program.args = rustc_flags(args); + config +} diff --git a/tools/compiletest/src/main.rs b/tools/compiletest/src/main.rs index 370565b..1e96c79 100644 --- a/tools/compiletest/src/main.rs +++ b/tools/compiletest/src/main.rs @@ -2,8 +2,8 @@ mod args; -use std::process::ExitCode; use clap::Parser; +use std::process::ExitCode; use ui_test::{status_emitter, Config}; fn main() -> ExitCode { @@ -31,5 +31,9 @@ fn main() -> ExitCode { ui_test::default_per_file_config, (text, status_emitter::Gha:: { name }), ); - if result.is_ok() { ExitCode::SUCCESS } else { ExitCode::FAILURE } + if result.is_ok() { + ExitCode::SUCCESS + } else { + ExitCode::FAILURE + } } diff --git a/tools/test-drive/src/main.rs b/tools/test-drive/src/main.rs index ea5fa61..5b88b1f 100644 --- a/tools/test-drive/src/main.rs +++ b/tools/test-drive/src/main.rs @@ -12,10 +12,10 @@ extern crate rustc_smir; use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; use rustc_smir::stable_mir::CompilerError; -use std::sync::atomic::{AtomicBool, Ordering}; use std::ops::ControlFlow; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::process::ExitCode; +use std::sync::atomic::{AtomicBool, Ordering}; const CHECK_ARG: &str = "--check-smir"; const VERBOSE_ARG: &str = "--verbose"; @@ -38,7 +38,10 @@ fn main() -> ExitCode { !is_check_arg }) .collect(); - VERBOSE.store(args.iter().any(|arg| &*arg == VERBOSE_ARG), Ordering::Relaxed); + VERBOSE.store( + args.iter().any(|arg| &*arg == VERBOSE_ARG), + Ordering::Relaxed, + ); let callback = if check_smir { test_stable_mir } else { @@ -62,7 +65,8 @@ macro_rules! run_tests { fn info(msg: String) { if VERBOSE.load(Ordering::Relaxed) { - println!("{}", msg); + // We filter output based on [T-DRIVE] prefix. + eprintln!("[T-DRIVE] {}", msg); } } diff --git a/tools/test-drive/src/sanity_checks.rs b/tools/test-drive/src/sanity_checks.rs index fa1e275..dbfdd64 100644 --- a/tools/test-drive/src/sanity_checks.rs +++ b/tools/test-drive/src/sanity_checks.rs @@ -52,7 +52,6 @@ pub fn test_all_fns() -> TestResult { )?; // Not sure which API to use to make sure this is a function that has a body. - #[cfg(skip)] for item in all_items { // Get body and iterate over items let body = item.body(); From 9434648ba82a0519222677bcc3fdf8b4f1ac5ced Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Tue, 26 Sep 2023 12:44:21 -0700 Subject: [PATCH 5/5] Enable smir checks on rustc suite I had forgotten to enable the smir checks before. Unfortunately, enabling them triggered a few issues. For now, I set the rustc suite to not fail the CI and created issue https://github.com/rust-lang/project-stable-mir/issues/39 to track the work needed to enable it. --- .github/scripts/run_own_tests.sh | 2 +- .github/scripts/run_rustc_tests.sh | 4 ++- .github/workflows/nightly.yml | 2 ++ tools/compiletest/src/args.rs | 22 ++++++++------ tools/test-drive/src/main.rs | 41 ++++++++++++++------------- tools/test-drive/src/sanity_checks.rs | 11 ++----- 6 files changed, 45 insertions(+), 37 deletions(-) diff --git a/.github/scripts/run_own_tests.sh b/.github/scripts/run_own_tests.sh index 894d8cc..bed6df7 100755 --- a/.github/scripts/run_own_tests.sh +++ b/.github/scripts/run_own_tests.sh @@ -20,7 +20,7 @@ function build_smir_tools() { function run_tests() { SUITES=( "sanity-checks pass" - "fixme fail" + "fixme fix-me" ) for suite_cfg in "${SUITES[@]}"; do # Hack to work on older bash like the ones on MacOS. diff --git a/.github/scripts/run_rustc_tests.sh b/.github/scripts/run_rustc_tests.sh index 2dc9538..455d1d8 100755 --- a/.github/scripts/run_rustc_tests.sh +++ b/.github/scripts/run_rustc_tests.sh @@ -87,7 +87,9 @@ function run_tests() { --android-cross-path= \ --target=${HOST} \ --llvm-filecheck="${FILE_CHECK}" \ - --channel=nightly + --channel=nightly \ + --target-rustcflags="--smir-check" \ + --host-rustcflags="--smir-check" done } diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 216eda1..8f7bf0a 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -29,3 +29,5 @@ jobs: env: RUST_REPO: "/tmp/rustc" TOOLS_BIN: "/tmp/smir/bin" + # Don't fail CI for now. See: https://github.com/rust-lang/project-stable-mir/issues/39 + continue-on-error: true diff --git a/tools/compiletest/src/args.rs b/tools/compiletest/src/args.rs index e783954..29a044c 100644 --- a/tools/compiletest/src/args.rs +++ b/tools/compiletest/src/args.rs @@ -7,14 +7,16 @@ use ui_test::{CommandBuilder, Config, OutputConflictHandling, RustfixMode}; #[derive(Copy, Clone, Debug, clap::ValueEnum)] /// Decides what is expected of each test's exit status. pub enum Mode { - /// The test passes a full execution of the rustc driver + /// The test passes a full execution of the rustc driver. Pass, - /// The test produces an executable binary that can get executed on the host + /// The test produces an executable binary that can get executed on the host. Run, - /// The rustc driver panicked - Panic, - /// The rustc driver emitted an error + /// The rustc driver should emit an error. Fail, + /// The test is currently failing but is expected to succeed. + /// This is used to add test cases that reproduce an existing bug. This help us identify issues + /// that may be "accidentally" fixed. + FixMe, /// Run the tests, but always pass them as long as all annotations are satisfied and stderr files match. Yolo, /// The test passes a full execution of `cargo build` @@ -54,7 +56,7 @@ impl From for ui_test::Mode { match mode { Mode::Pass | Mode::CargoPass => ui_test::Mode::Pass, Mode::Run => ui_test::Mode::Run { exit_code: 0 }, - Mode::Panic => ui_test::Mode::Panic, + Mode::FixMe | Mode::Fail => ui_test::Mode::Fail { require_patterns: false, rustfix: RustfixMode::Disabled, @@ -84,9 +86,13 @@ impl From for Config { } fn rustc_flags(args: &Args) -> Vec { - let mut flags = vec!["--check-smir".into()]; + let mut flags = vec!["--smir-check".into()]; if args.verbose || args.no_capture { - flags.push("--verbose".into()); + flags.push("--smir-verbose".into()); + } + if matches!(args.mode, Mode::FixMe) { + // Enable checks that should pass but may trigger an existing issue. + flags.push("--smir-fixme".into()); } flags } diff --git a/tools/test-drive/src/main.rs b/tools/test-drive/src/main.rs index 5b88b1f..8aaf936 100644 --- a/tools/test-drive/src/main.rs +++ b/tools/test-drive/src/main.rs @@ -17,10 +17,16 @@ use std::panic::{catch_unwind, AssertUnwindSafe}; use std::process::ExitCode; use std::sync::atomic::{AtomicBool, Ordering}; -const CHECK_ARG: &str = "--check-smir"; -const VERBOSE_ARG: &str = "--verbose"; +// ---- Arguments that should be parsed by the test-driver (w/ "smir" prefix) +const CHECK_ARG: &str = "--smir-check"; +/// Enable verbose mode. +const VERBOSE_ARG: &str = "--smir-verbose"; +/// Argument used to enable checks that may be failing due to an existing issue. +const FIXME_ARG: &str = "--smir-fixme"; + // Use a static variable for simplicity. static VERBOSE: AtomicBool = AtomicBool::new(false); +static FIXME_CHECKS: AtomicBool = AtomicBool::new(false); type TestResult = Result<(), String>; @@ -30,24 +36,19 @@ type TestResult = Result<(), String>; /// This allows us to use this tool in cargo projects to analyze the target crate only by running /// `cargo rustc --check-smir`. fn main() -> ExitCode { - let mut check_smir = false; - let args: Vec<_> = std::env::args() - .filter(|arg| { - let is_check_arg = arg == CHECK_ARG; - check_smir |= is_check_arg; - !is_check_arg - }) - .collect(); - VERBOSE.store( - args.iter().any(|arg| &*arg == VERBOSE_ARG), - Ordering::Relaxed, - ); - let callback = if check_smir { + let args = std::env::args(); + let (smir_args, rustc_args) : (Vec, _) = args.partition + (|arg| arg + .starts_with + ("--smir")); + let callback = if smir_args.contains(&CHECK_ARG.to_string()) { + VERBOSE.store(smir_args.contains(&VERBOSE_ARG.to_string()), Ordering::Relaxed); + FIXME_CHECKS.store(smir_args.contains(&FIXME_ARG.to_string()), Ordering::Relaxed); test_stable_mir } else { |_: TyCtxt| ControlFlow::<()>::Continue(()) }; - let result = rustc_internal::StableMir::new(args, callback).run(); + let result = rustc_internal::StableMir::new(rustc_args, callback).run(); if result.is_ok() || matches!(result, Err(CompilerError::Skipped)) { ExitCode::SUCCESS } else { @@ -73,12 +74,14 @@ fn info(msg: String) { /// This function invoke other tests and process their results. /// Tests should avoid panic, fn test_stable_mir(_tcx: TyCtxt<'_>) -> ControlFlow<()> { - let results = run_tests![ + let mut results = Vec::from(run_tests![ sanity_checks::test_entry_fn, sanity_checks::test_all_fns, - sanity_checks::test_traits, sanity_checks::test_crates - ]; + ]); + if FIXME_CHECKS.load(Ordering::Relaxed) { + results.extend_from_slice(&run_tests!(sanity_checks::test_traits)) + } let (success, failure): (Vec<_>, Vec<_>) = results.iter().partition(|r| r.is_ok()); info(format!( "Ran {} tests. {} succeeded. {} failed", diff --git a/tools/test-drive/src/sanity_checks.rs b/tools/test-drive/src/sanity_checks.rs index dbfdd64..610613d 100644 --- a/tools/test-drive/src/sanity_checks.rs +++ b/tools/test-drive/src/sanity_checks.rs @@ -43,15 +43,9 @@ pub fn test_entry_fn() -> TestResult { }) } -/// Check that the crate isn't empty and iterate over function bodies. +/// Iterate over local function bodies. pub fn test_all_fns() -> TestResult { let all_items = stable_mir::all_local_items(); - check( - !all_items.is_empty(), - "Failed to find any local item".to_string(), - )?; - - // Not sure which API to use to make sure this is a function that has a body. for item in all_items { // Get body and iterate over items let body = item.body(); @@ -60,10 +54,11 @@ pub fn test_all_fns() -> TestResult { Ok(()) } -/// FIXME: Create to track improvements to TraitDecls / ImplTraitDecls. /// Using these structures will always follow calls to get more details about those structures. /// Unless user is trying to find a specific type, this will get repetitive. pub fn test_traits() -> TestResult { + // FIXME: All trait declarations only return local traits. + // See https://github.com/rust-lang/project-stable-mir/issues/37 let all_traits = stable_mir::all_trait_decls(); for trait_decl in all_traits.iter().map(stable_mir::trait_decl) { // Can't compare trait_decl, so just compare a field for now.