use super::*;
use std::collections::BTreeMap;
fn counting_hook(marker: &Path) -> String {
format!("echo ran >> {}", marker.display())
}
#[cfg(unix)]
fn sleeping_hook(seconds: u64) -> String {
format!("sleep {seconds}")
}
#[cfg(windows)]
fn sleeping_hook(seconds: u64) -> String {
format!("ping -n {} 127.0.0.1", seconds + 1)
}
fn spec(language: Language, working_directory: &Path, before: String, env: BTreeMap<String, String>) -> SessionSpec {
SessionSpec {
language,
working_directory: working_directory.to_path_buf(),
manifest: None,
before: vec![before],
env,
include_paths: Vec::new(),
rust_features: Vec::new(),
rust_dependencies: BTreeMap::new(),
}
}
fn times_run(marker: &Path) -> usize {
std::fs::read_to_string(marker)
.unwrap_or_default()
.lines()
.filter(|line| !line.trim().is_empty())
.count()
}
#[test]
fn one_hook_serves_every_target_over_the_same_package() {
let package = tempfile::tempdir().expect("package directory");
let markers = tempfile::tempdir().expect("marker directory");
let marker = markers.path().join("runs.txt");
let hook = counting_hook(&marker);
let mut specs = HashMap::new();
specs.insert(
"kotlin".to_string(),
spec(Language::Kotlin, package.path(), hook.clone(), BTreeMap::new()),
);
specs.insert(
"kotlin_android".to_string(),
spec(Language::Kotlin, package.path(), hook, BTreeMap::new()),
);
let prepared = prepare_sessions_isolated(&specs, 30);
assert!(prepared.errors.is_empty(), "both targets must prepare: {:?}", {
prepared.errors.keys().collect::<Vec<_>>()
});
assert_eq!(prepared.sessions.len(), 2, "both targets must still get a session");
assert_eq!(times_run(&marker), 1, "the shared before hook must run exactly once");
}
#[test]
fn a_hook_over_a_different_package_is_not_reused() {
let first_package = tempfile::tempdir().expect("first package");
let second_package = tempfile::tempdir().expect("second package");
let markers = tempfile::tempdir().expect("marker directory");
let first = markers.path().join("first.txt");
let second = markers.path().join("second.txt");
let mut specs = HashMap::new();
specs.insert(
"node".to_string(),
spec(
Language::TypeScript,
first_package.path(),
counting_hook(&first),
BTreeMap::new(),
),
);
specs.insert(
"wasm".to_string(),
spec(
Language::TypeScript,
second_package.path(),
counting_hook(&second),
BTreeMap::new(),
),
);
let prepared = prepare_sessions_isolated(&specs, 30);
assert!(prepared.errors.is_empty(), "both targets must prepare");
assert_eq!(times_run(&first), 1, "the first package's hook must run");
assert_eq!(times_run(&second), 1, "the second package's hook must run");
}
#[test]
fn a_hook_handed_a_different_environment_is_not_reused() {
let package = tempfile::tempdir().expect("package directory");
let markers = tempfile::tempdir().expect("marker directory");
let marker = markers.path().join("runs.txt");
let hook = counting_hook(&marker);
let mut specs = HashMap::new();
specs.insert(
"node".to_string(),
spec(Language::TypeScript, package.path(), hook.clone(), BTreeMap::new()),
);
specs.insert(
"wasm".to_string(),
spec(
Language::TypeScript,
package.path(),
hook,
BTreeMap::from([("ALEF_SNIPPET_PROFILE".to_string(), "wasm".to_string())]),
),
);
let prepared = prepare_sessions_isolated(&specs, 30);
assert!(prepared.errors.is_empty(), "both targets must prepare");
assert_eq!(times_run(&marker), 2, "a differing environment must not reuse a hook");
}
#[test]
fn a_timed_out_hook_is_charged_once_and_still_fails_every_target() {
let package = tempfile::tempdir().expect("package directory");
let hook = sleeping_hook(30);
let mut specs = HashMap::new();
specs.insert(
"kotlin".to_string(),
spec(Language::Kotlin, package.path(), hook.clone(), BTreeMap::new()),
);
specs.insert(
"kotlin_android".to_string(),
spec(Language::Kotlin, package.path(), hook, BTreeMap::new()),
);
let timeout_secs = 2;
let started = std::time::Instant::now();
let prepared = prepare_sessions_isolated(&specs, timeout_secs);
let elapsed = started.elapsed();
for target in ["kotlin", "kotlin_android"] {
let error = prepared
.errors
.get(target)
.unwrap_or_else(|| panic!("{target} must be reported as unprepared"));
assert!(
error.ordering,
"{target} must keep the ordering classification of the hook it shared: {}",
error.message
);
}
assert!(
prepared.sessions.is_empty(),
"a target whose hook timed out must not be handed a session"
);
crate::test_support::assert_elapsed_under(
"the shared hook's timeout was paid more than once",
elapsed,
std::time::Duration::from_secs(timeout_secs * 2),
);
}