use std::time::Duration;
use tempfile::TempDir;
use baraddur::App;
use baraddur::config::{Config, OnFailureConfig, OutputConfig, Step, WatchConfig};
use baraddur::output::{DisplayConfig, Verbosity};
fn trivial_app(td: &TempDir, step_cmd: &str) -> App {
let root = td.path().to_path_buf();
let config = Config {
watch: WatchConfig {
extensions: vec!["rs".into()],
debounce_ms: 100,
ignore: vec![],
},
output: OutputConfig::default(),
on_failure: OnFailureConfig::default(),
steps: vec![Step {
name: "noop".into(),
cmd: step_cmd.into(),
parallel: false,
if_changed: Vec::new(),
}],
};
App {
config,
config_path: root.join(".baraddur.toml"),
root,
display_config: DisplayConfig {
is_tty: false,
no_clear: true,
verbosity: Verbosity::Quiet,
},
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn run_until_exits_on_stop_signal() {
let td = TempDir::new().unwrap();
let root = td.path().to_path_buf();
let app = trivial_app(&td, "true");
let stop = async {
tokio::time::sleep(Duration::from_millis(400)).await;
};
let result = tokio::time::timeout(Duration::from_secs(5), app.run_until(stop))
.await
.expect("run_until did not return within 5s");
result.expect("run_until returned an error");
let log = root.join(".baraddur").join("last-run.log");
assert!(
log.exists(),
"expected {} to exist after one pipeline run",
log.display()
);
let contents = std::fs::read_to_string(&log).unwrap();
assert!(
contents.contains("noop"),
"log should mention the step name; got:\n{contents}"
);
assert!(
contents.contains("pass"),
"log should mark the step as passing; got:\n{contents}"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn on_failure_hook_runs_after_failing_run() {
let td = TempDir::new().unwrap();
let root = td.path().to_path_buf();
let sentinel = root.join("hook-input.txt");
let mut app = trivial_app(&td, "false");
app.config.on_failure = OnFailureConfig {
enabled: true,
cmd: format!("tee {}", sentinel.display()),
prompt: "PROMPT_LINE".into(),
timeout_secs: 5,
};
let stop = async {
tokio::time::sleep(Duration::from_millis(800)).await;
};
let _ = tokio::time::timeout(Duration::from_secs(10), app.run_until(stop))
.await
.expect("run_until did not return within 10s");
let captured = std::fs::read_to_string(&sentinel)
.unwrap_or_else(|_| panic!("expected {} to exist", sentinel.display()));
assert!(
captured.contains("PROMPT_LINE"),
"hook stdin missing prompt prefix; got:\n{captured}"
);
assert!(
captured.contains("noop"),
"hook stdin missing failed step name; got:\n{captured}"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn run_until_records_failures() {
let td = TempDir::new().unwrap();
let root = td.path().to_path_buf();
let app = trivial_app(&td, "false");
let stop = async {
tokio::time::sleep(Duration::from_millis(400)).await;
};
let _ = tokio::time::timeout(Duration::from_secs(5), app.run_until(stop))
.await
.expect("run_until did not return within 5s");
let log = root.join(".baraddur").join("last-run.log");
let contents = std::fs::read_to_string(&log).unwrap();
assert!(
contents.contains("FAIL"),
"log should mark the step as failing; got:\n{contents}"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn run_until_exits_promptly() {
let td = TempDir::new().unwrap();
let app = trivial_app(&td, "true");
let start = std::time::Instant::now();
let stop = async {
tokio::time::sleep(Duration::from_millis(50)).await;
};
let _ = tokio::time::timeout(Duration::from_secs(5), app.run_until(stop))
.await
.expect("run_until did not return within 5s");
let elapsed = start.elapsed();
assert!(
elapsed < Duration::from_secs(2),
"expected prompt shutdown; took {elapsed:?}"
);
}