diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 568e307366641..ab268f944816f 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -319,20 +319,6 @@ impl<'test> TestCx<'test> { TestMode::RustdocJs => true, TestMode::Ui => pm.is_some() || self.props.fail_mode > Some(FailMode::Build), TestMode::Crashes => false, - TestMode::Incremental => { - let revision = - self.revision.expect("incremental tests require a list of revisions"); - if revision.starts_with("cpass") - || revision.starts_with("bpass") - || revision.starts_with("rpass") - { - true - } else if revision.starts_with("bfail") { - false - } else { - panic!("revision name must begin with `cpass`, `bfail`, `bpass`, or `rpass`"); - } - } mode => panic!("unimplemented for mode {:?}", mode), } } diff --git a/src/tools/compiletest/src/runtest/incremental.rs b/src/tools/compiletest/src/runtest/incremental.rs index 85e0c89a8d018..6529882715a90 100644 --- a/src/tools/compiletest/src/runtest/incremental.rs +++ b/src/tools/compiletest/src/runtest/incremental.rs @@ -1,26 +1,43 @@ -use super::{Emit, FailMode, PassMode, ProcRes, TestCx, WillExecute}; +use std::sync::LazyLock; + +use crate::runtest::{Emit, TestCx, WillExecute}; +use crate::util::string_enum; + +string_enum!( + /// How far an incremental test revision should proceed through the compile/run + /// sequence, and whether the last step should succeed or fail, as determined + /// from the start of the revision name. + #[derive(Clone, Copy, PartialEq, Eq)] + enum IncrRevKind { + CheckPass => "cpass", + BuildFail => "bfail", + BuildPass => "bpass", + RunPass => "rpass", + } +); + +impl IncrRevKind { + fn for_revision_name(rev_name: &str) -> Result { + static MESSAGE: LazyLock = LazyLock::new(|| { + let values = IncrRevKind::STR_VARIANTS + .iter() + .map(|s| format!("`{s}`")) + .collect::>() + .join(", "); + format!("incremental revision name must begin with one of: {values}") + }); + + IncrRevKind::VARIANTS + .iter() + .copied() + .find(|kind| rev_name.starts_with(kind.to_str())) + .ok_or_else(|| MESSAGE.as_str()) + } +} impl TestCx<'_> { + /// Runs a single revision of an incremental test. pub(super) fn run_incremental_test(&self) { - // Basic plan for a test incremental/foo/bar.rs: - // - load list of revisions rpass1, bfail2, rpass3 - // - each should begin with `bfail`, `bpass`, or `rpass` - // - if `bfail`, expect compilation to fail - // - if `bpass`, expect compilation to succeed, don't execute - // - if `rpass`, expect compilation and execution to succeed - // - create a directory build/foo/bar.incremental - // - compile foo/bar.rs with -C incremental=.../foo/bar.incremental and -C rpass1 - // - because name of revision starts with "rpass", expect success - // - compile foo/bar.rs with -C incremental=.../foo/bar.incremental and -C bfail2 - // - because name of revision starts with "bfail", expect an error - // - load expected errors as usual, but filter for those with `[bfail2]` - // - compile foo/bar.rs with -C incremental=.../foo/bar.incremental and -C rpass3 - // - because name of revision starts with "rpass", expect success - // - execute build/foo/bar.exe and save output - // - // FIXME -- use non-incremental mode as an oracle? That doesn't apply - // to #[rustc_clean] tests I guess - let revision = self.revision.expect("incremental tests require a list of revisions"); // Incremental workproduct directory should have already been created. @@ -31,68 +48,63 @@ impl TestCx<'_> { write!(self.stdout, "revision={:?} props={:#?}", revision, self.props); } - if revision.starts_with("cpass") { - self.run_cpass_test(); - } else if revision.starts_with("bpass") { - self.run_bpass_test(); - } else if revision.starts_with("rpass") { - self.run_rpass_test(); - } else if revision.starts_with("bfail") { - self.run_bfail_test(); - } else { - self.fatal("revision name must begin with `bfail`, `bpass`, or `rpass`"); + // Determine the revision kind from the revision name. + // The revision kind should be matched exhaustively to ensure that no cases are missed. + let rev_kind = IncrRevKind::for_revision_name(revision).unwrap_or_else(|e| self.fatal(e)); + + // Compile the test for this revision. + let emit = match rev_kind { + IncrRevKind::CheckPass => Emit::Metadata, // Do a check build. + IncrRevKind::BuildFail | IncrRevKind::BuildPass | IncrRevKind::RunPass => Emit::None, + }; + let will_execute = match rev_kind { + IncrRevKind::CheckPass | IncrRevKind::BuildFail | IncrRevKind::BuildPass => { + WillExecute::No + } + IncrRevKind::RunPass => { + // Yes, unless running test binaries is disabled. + self.run_if_enabled() + } + }; + let proc_res = &self.compile_test(will_execute, emit); + + // Check the compiler's exit status. + match rev_kind { + IncrRevKind::CheckPass | IncrRevKind::BuildPass | IncrRevKind::RunPass => { + // Compilation should have succeeded. + if !proc_res.status.success() { + self.fatal_proc_rec("test compilation failed although it shouldn't!", proc_res); + } + } + + IncrRevKind::BuildFail => { + // Compilation should have failed, with the expected status code. + if proc_res.status.success() { + self.fatal_proc_rec("incremental test did not emit an error", proc_res); + } + if !self.props.dont_check_failure_status { + self.check_correct_failure_status(proc_res); + } + } } - } - - fn run_cpass_test(&self) { - let proc_res = self.compile_test(WillExecute::No, Emit::Metadata); - self.check_if_test_should_compile(None, Some(PassMode::Check), &proc_res); - self.check_compiler_output_for_incr(&proc_res); - } - - fn run_bpass_test(&self) { - let emit_metadata = self.should_emit_metadata(self.pass_mode()); - let proc_res = self.compile_test(WillExecute::No, emit_metadata); - - if !proc_res.status.success() { - self.fatal_proc_rec("compilation failed!", &proc_res); - } - - self.check_compiler_output_for_incr(&proc_res); - } - - fn run_rpass_test(&self) { - let emit_metadata = self.should_emit_metadata(self.pass_mode()); - let should_run = self.run_if_enabled(); - let proc_res = self.compile_test(should_run, emit_metadata); - if !proc_res.status.success() { - self.fatal_proc_rec("compilation failed!", &proc_res); - } - - self.check_compiler_output_for_incr(&proc_res); - - if let WillExecute::Disabled = should_run { - return; - } - - let proc_res = self.exec_compiled_test(); - if !proc_res.status.success() { - self.fatal_proc_rec("test run failed!", &proc_res); - } - } - - fn run_bfail_test(&self) { - let pm = self.pass_mode(); - let proc_res = self.compile_test(WillExecute::No, self.should_emit_metadata(pm)); - self.check_if_test_should_compile(Some(FailMode::Build), pm, &proc_res); - self.check_compiler_output_for_incr(&proc_res); - } - - fn check_compiler_output_for_incr(&self, proc_res: &ProcRes) { + // Check compilation output. let output_to_check = self.get_output(proc_res); self.check_expected_errors(&proc_res); self.check_all_error_patterns(&output_to_check, proc_res); self.check_forbid_output(&output_to_check, proc_res); + + // Run the binary and check its exit status, if appropriate. + match rev_kind { + IncrRevKind::CheckPass | IncrRevKind::BuildFail | IncrRevKind::BuildPass => {} + IncrRevKind::RunPass => { + if self.config.run_enabled() { + let run_proc_res = self.exec_compiled_test(); + if !run_proc_res.status.success() { + self.fatal_proc_rec("test run failed!", &run_proc_res); + } + } + } + } } }