Added flag --keep-going to continue after the first error.

This commit is contained in:
Filipe Rodrigues 2024-09-05 01:17:20 +01:00
parent 54c5ac5b6a
commit 12f0aa1fb4
Signed by: zenithsiz
SSH Key Fingerprint: SHA256:Mb5ppb3Sh7IarBO/sBTXLHbYEOz37hJAlslLQPPAPaU
5 changed files with 147 additions and 2 deletions

View File

@ -7,6 +7,7 @@
"debouncer",
"filetime",
"indexmap",
"inotify",
"itertools",
"mapref",
"npath",

View File

@ -43,6 +43,17 @@ pub struct Args {
#[clap(long = "ignore-missing", short = 'i')]
pub ignore_missing: bool,
/// Keeps building files even if an error has occurred.
///
/// Normally, whenever an error occurs, further rules are forbidden
/// to execute, although currently executing rules continue running.
///
/// This makes it so that whenever an error occurs,
/// we continue searching and executing rules until there is nothing
/// else we can do
#[clap(long = "keep-going")]
pub keep_going: bool,
/// Watch for file changes and rebuild any necessary targets.
///
/// WARNING: If the log file is situated in the same directory as any watched
@ -71,6 +82,7 @@ impl Default for Args {
zbuild_path: None,
jobs: None,
ignore_missing: false,
keep_going: false,
watch: false,
watcher_debouncer_timeout_ms: None,
log_file: None,

View File

@ -144,8 +144,9 @@ pub async fn run(args: Args) -> Result<(), AppError> {
);
// Create the builder
// Note: We should stop builds on the first error if we're *not* watching.
let builder = Builder::new(jobs, rules, expander, !args.watch)
// Note: We should stop builds on the first error if we're *not* watching and the
// user doesn't want to keep going.
let builder = Builder::new(jobs, rules, expander, !args.watch && !args.keep_going)
.context("Unable to create builder")
.map_err(AppError::Other)?;
let builder = Arc::new(builder);

125
tests/keep_going.rs Normal file
View File

@ -0,0 +1,125 @@
// Features
#![feature(must_not_suspend, strict_provenance)]
// Lints
#![expect(clippy::tests_outside_test_module, reason = "We're an integration test")]
// Modules
mod util;
// Imports
use {
anyhow::Context,
std::fs,
tempdir::TempDir,
zbuild::{Args, ExitResult},
};
/// Test for `--keep-going`
#[tokio::test]
#[tracing_test::traced_test]
async fn keep_going() -> ExitResult {
self::inner(true).await.context("Unable to test with `--keep-going`")?;
self::inner(false)
.await
.context("Unable to test without `--keep-going`")?;
ExitResult::Ok
}
/// Inner function to test
///
/// This works by having the following tree:
///
/// ```no_compile
/// A -> B
/// \-> C1 -> C2
/// ```
///
/// Where `B` is always going to fail, after 200ms, to allow all other targets
/// to start running.
///
/// We make `C2` take a long time, to ensure `B` is executed (and fails)
/// before it can return.
///
/// When testing with `keep_going = true`, we ensure that `C1` is still built,
/// despite `C2` only finishing *after* `B` errors out.
///
/// When testing with `keep_going = false`, we ensure that `C1` is not built,
/// since `C2` exits after `B` errors, so nothing else should be built.
async fn inner(keep_going: bool) -> Result<(), anyhow::Error> {
let temp_dir = TempDir::new("zbuild").context("Unable to create temporary directory")?;
let zbuild_yaml = temp_dir.path().join("zbuild.yaml");
// TODO: Instead of sleeping, use `inotify` to wait for other
// actions to happen?
fs::write(
&zbuild_yaml,
r#"---
rules:
a:
out: [a]
deps: [b, c1]
exec:
- [touch, a]
b:
out: [b]
exec:
- [sleep, "0.1"]
- ["false"]
- [touch, b]
c1:
out: [c1]
deps: [c2]
exec:
- [touch, c1]
c2:
out: [c2]
exec:
- [sleep, "0.2"]
- [touch, c2]
"#,
)
.context("Unable to write zbuild manifest")?;
let args = Args {
targets: ["a".to_owned()].into(),
zbuild_path: Some(zbuild_yaml),
keep_going,
..Args::default()
};
tracing::info!(?args, "Arguments");
let res = zbuild::run(args).await;
anyhow::ensure!(res.is_err(), "Expected zbuild error");
let a = temp_dir.path().join("a");
let b = temp_dir.path().join("b");
let c1 = temp_dir.path().join("c1");
let c2 = temp_dir.path().join("c2");
anyhow::ensure!(
!a.try_exists().context("Unable to check if output file exists")?,
"Output file {a:?} was created"
);
anyhow::ensure!(
!b.try_exists().context("Unable to check if output file exists")?,
"Output file {b:?} was created"
);
match keep_going {
true => anyhow::ensure!(
c1.try_exists().context("Unable to check if output file exists")?,
"Output file {c1:?} was missing"
),
false => anyhow::ensure!(
!c1.try_exists().context("Unable to check if output file exists")?,
"Output file {c1:?} was created"
),
}
anyhow::ensure!(
c2.try_exists().context("Unable to check if output file exists")?,
"Output file {c2:?} was missing"
);
Ok(())
}

View File

@ -1,5 +1,11 @@
//! Utilities for all integration tests
// Lints
#![allow(
dead_code,
reason = "This module is used from many tests, which might not use everything"
)]
// Imports
use {anyhow::Context, std::fs, tempdir::TempDir, zbuild::Args};