use atomcode_core::setup::{self, RunOptions};
use serial_test::serial;
use std::path::Path;
fn run_in_tempdir<F, G>(
project_setup: F,
mutate_opts: G,
) -> (
setup::SetupResult<setup::SetupReport>,
tempfile::TempDir,
tempfile::TempDir,
)
where
F: FnOnce(&Path),
G: FnOnce(&mut RunOptions),
{
let proj = tempfile::tempdir().unwrap();
let user = tempfile::tempdir().unwrap();
project_setup(proj.path());
let old = std::env::var_os("ATOMCODE_HOME");
std::env::set_var("ATOMCODE_HOME", user.path());
let mut opts = RunOptions::new(proj.path().to_path_buf());
mutate_opts(&mut opts);
let result = setup::run(opts);
match old {
Some(v) => std::env::set_var("ATOMCODE_HOME", v),
None => std::env::remove_var("ATOMCODE_HOME"),
}
(result, proj, user)
}
#[test]
#[serial]
fn setup_installs_seeds_in_empty_project() {
let (result, _proj, _user) = run_in_tempdir(|_| {}, |_| {});
let report = result.expect("setup run should succeed");
let total =
report.summary.installed.len() + report.summary.skipped.len() + report.summary.failed.len();
assert!(
total > 0,
"expected at least one seed install attempted, got installed={} skipped={} failed={}",
report.summary.installed.len(),
report.summary.skipped.len(),
report.summary.failed.len(),
);
}
#[test]
#[serial]
fn second_run_skips_already_installed() {
let proj = tempfile::tempdir().unwrap();
let user = tempfile::tempdir().unwrap();
let old = std::env::var_os("ATOMCODE_HOME");
std::env::set_var("ATOMCODE_HOME", user.path());
let make_opts = || {
RunOptions::new(proj.path().to_path_buf())
};
let report1 = setup::run(make_opts()).unwrap();
let report2 = setup::run(make_opts()).unwrap();
match old {
Some(v) => std::env::set_var("ATOMCODE_HOME", v),
None => std::env::remove_var("ATOMCODE_HOME"),
}
let first_attempted = report1.summary.installed.len()
+ report1.summary.skipped.len()
+ report1.summary.failed.len();
assert!(
first_attempted > 0,
"first run should attempt at least one item"
);
assert!(
report2.summary.installed.len() <= report1.summary.installed.len(),
"second run should install at most as many as the first (first={}, second={})",
report1.summary.installed.len(),
report2.summary.installed.len(),
);
if !report1.summary.installed.is_empty() {
let has_already = report2.summary.skipped.iter().any(|(_, reason)| {
matches!(
reason,
atomcode_core::setup::install::SkipReason::AlreadyInstalled
)
});
assert!(
has_already,
"second run skipped items: {:?}",
report2.summary.skipped
);
}
}
#[test]
#[serial_test::serial]
fn concurrent_runs_second_fails_lock() {
let proj = tempfile::tempdir().unwrap();
let user = tempfile::tempdir().unwrap();
let old = std::env::var_os("ATOMCODE_HOME");
std::env::set_var("ATOMCODE_HOME", user.path());
let proj_a = proj.path().to_path_buf();
let proj_b = proj.path().to_path_buf();
let (r1, r2) = std::thread::scope(|s| {
let t1 = s.spawn(|| {
let o = atomcode_core::setup::RunOptions::new(proj_a);
atomcode_core::setup::run(o)
});
std::thread::sleep(std::time::Duration::from_millis(20));
let t2 = s.spawn(|| {
let o = atomcode_core::setup::RunOptions::new(proj_b);
atomcode_core::setup::run(o)
});
let r1 = t1.join().unwrap();
let r2 = t2.join().unwrap();
(r1, r2)
});
if let Some(v) = old {
std::env::set_var("ATOMCODE_HOME", v);
} else {
std::env::remove_var("ATOMCODE_HOME");
}
let succeeded = r1.is_ok() as usize + r2.is_ok() as usize;
assert!(
succeeded >= 1,
"at least one concurrent run should succeed; both failed:\n r1={:?}\n r2={:?}",
r1,
r2
);
for r in [&r1, &r2] {
if let Err(e) = r {
assert!(
matches!(
e,
atomcode_core::setup::SetupError::LockHeld { .. }
| atomcode_core::setup::SetupError::LockIo(_)
),
"if a concurrent run failed, it should be LockHeld or LockIo, got: {e:?}"
);
}
}
}