Skip to content

Commit

Permalink
Stop tests early when Ctrl+C is pressed
Browse files Browse the repository at this point in the history
  • Loading branch information
oli-obk committed Aug 17, 2024
1 parent a4dc487 commit 9c0ae89
Show file tree
Hide file tree
Showing 20 changed files with 298 additions and 135 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

* default comment symbols for `Config::cargo` changed to `#`
* Ctrl+C now prints the summary of the tests that were run before Ctrl+C is pressed

### Removed

Expand Down
96 changes: 70 additions & 26 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ annotate-snippets = { version = "0.11.2" }
levenshtein = "1.0.5"
spanned = "0.3.0"

[dev-dependencies]
ctrlc = "3.4.5"

[dependencies.regex]
version = "1.5.5"
default-features = false
Expand Down
7 changes: 6 additions & 1 deletion examples/rustc_basic.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
use std::sync::atomic::Ordering;
use ui_test::{run_tests, Config};

fn main() -> ui_test::color_eyre::Result<()> {
let config = Config::rustc("examples_tests/rustc_basic");
let abort_check = config.abort_check.clone();
ctrlc::set_handler(move || abort_check.store(true, Ordering::Relaxed))?;

// Compile all `.rs` files in the given directory (relative to your
// Cargo.toml) and compare their output against the corresponding
// `.stderr` files.
run_tests(Config::rustc("examples_tests/rustc_basic"))
run_tests(config)
}
5 changes: 5 additions & 0 deletions src/aux_builds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ impl Build for AuxBuilder {
// Now run the command again to fetch the output filenames
aux_cmd.arg("--print").arg("file-names");
let output = aux_cmd.output().unwrap();

if build_manager.aborted() {
return Err(Errored::aborted());
}

assert!(output.status.success());

let mut extra_args = vec![];
Expand Down
12 changes: 11 additions & 1 deletion src/build_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ pub struct BuildManager {
new_job_submitter: Sender<NewJob>,
}

type NewJob = Box<dyn Send + for<'a> FnOnce(&'a Sender<TestRun>) -> Result<()>>;
/// Type of closure that is used to run individual tests.
pub type NewJob = Box<dyn Send + for<'a> FnOnce(&'a Sender<TestRun>) -> Result<()>>;

impl BuildManager {
/// Create a new `BuildManager` for a specific `Config`. Each `Config` needs
Expand All @@ -49,6 +50,9 @@ impl BuildManager {
/// Lazily add more jobs after a test has finished. These are added to the queue
/// as normally, but nested below the test.
pub fn add_new_job(&self, job: impl Send + 'static + FnOnce() -> TestRun) {
if self.aborted() {
return;
}
self.new_job_submitter
.send(Box::new(move |sender| Ok(sender.send(job())?)))
.unwrap()
Expand Down Expand Up @@ -114,6 +118,7 @@ impl BuildManager {
stderr: vec![],
stdout: vec![],
}),
self.aborted(),
);
res
})
Expand All @@ -132,4 +137,9 @@ impl BuildManager {
pub fn config(&self) -> &Config {
&self.config
}

/// Whether the build was cancelled
pub fn aborted(&self) -> bool {
self.config.aborted()
}
}
10 changes: 10 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use std::{
collections::BTreeMap,
num::NonZeroUsize,
path::{Path, PathBuf},
sync::{atomic::AtomicBool, Arc},
};

mod args;
Expand Down Expand Up @@ -63,6 +64,10 @@ pub struct Config {
pub custom_comments: BTreeMap<&'static str, CommandParserFunc>,
/// Custom diagnostic extractor (invoked on the output of tests)
pub diagnostic_extractor: fn(&Path, &[u8]) -> Diagnostics,
/// An atomic bool that can be set to `true` to abort all tests.
/// Will not cancel child processes, but if set from a Ctrl+C handler,
/// the pressing of Ctrl+C will already have cancelled child processes.
pub abort_check: Arc<AtomicBool>,
}

impl Config {
Expand Down Expand Up @@ -157,6 +162,7 @@ impl Config {
comment_start: "//",
custom_comments: Default::default(),
diagnostic_extractor: rustc_stderr::process,
abort_check: Default::default(),
};
config
.custom_comments
Expand Down Expand Up @@ -409,6 +415,10 @@ impl Config {
.all(|c| self.test_condition(c))
^ self.run_only_ignored
}

pub(crate) fn aborted(&self) -> bool {
self.abort_check.load(std::sync::atomic::Ordering::Relaxed)
}
}

#[derive(Debug, Clone)]
Expand Down
16 changes: 16 additions & 0 deletions src/custom_flags/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ impl Flag for Run {
build_manager.add_new_job(move || {
cmd.arg("--print").arg("file-names");
let output = cmd.output().unwrap();
if config.aborted() {
return TestRun {
result: Err(Errored::aborted()),
status: config.status,
abort_check: config.config.abort_check.clone(),
};
}
assert!(output.status.success(), "{cmd:#?}: {output:#?}");

let mut files = output.stdout.lines();
Expand All @@ -61,6 +68,14 @@ impl Flag for Run {
.output()
.unwrap_or_else(|err| panic!("exe file: {}: {err}", display(&exe_file)));

if config.aborted() {
return TestRun {
result: Err(Errored::aborted()),
status: config.status,
abort_check: config.config.abort_check.clone(),
};
}

let mut errors = vec![];

config.check_test_output(&mut errors, &output.stdout, &output.stderr);
Expand Down Expand Up @@ -93,6 +108,7 @@ impl Flag for Run {
})
},
status: config.status,
abort_check: config.config.abort_check,
}
});
Ok(())
Expand Down
5 changes: 4 additions & 1 deletion src/custom_flags/rustfix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,9 @@ fn compile_fixed(
.arg(format!("{crate_name}_________{}", i + 1));
build_manager.add_new_job(move || {
let output = cmd.output().unwrap();
let result = if output.status.success() {
let result = if fixed_config.aborted() {
Err(Errored::aborted())
} else if output.status.success() {
Ok(TestOk::Ok)
} else {
let diagnostics = fixed_config.process(&output.stderr);
Expand Down Expand Up @@ -247,6 +249,7 @@ fn compile_fixed(
TestRun {
result,
status: fixed_config.status,
abort_check: fixed_config.config.abort_check,
}
});
}
Expand Down
Loading

0 comments on commit 9c0ae89

Please sign in to comment.