25b8354959
Every E2E test that mutated the shared demo-project fixture store now mints its own tempdir project whose 2-line Aura.toml points at the once-built fixture crate by absolute [nodes] pointer; the runs/ store follows the project root, so no two tests contend and libtest's default thread parallelism applies. project_load keeps driving the fixture directly (the suite's only proof of the inline-crate routing arm); its single store-mutating test is that binary's only one, so no lock is needed there either. This also closes the documented cross-process race window (#223): there is no shared mutable store left to race on. Measured on the data-full host: cli_run 211s -> 57.2s (140 tests), research_docs 21.1s -> 2.1s (73 tests), project_load green, clippy clean. refs #250
111 lines
4.1 KiB
Rust
111 lines
4.1 KiB
Rust
//! Shared fixture helpers for the `aura-cli` end-to-end test binaries
|
|
//! (`research_docs.rs`, `cli_run.rs`, `project_load.rs`), all of which build
|
|
//! and drive the `tests/fixtures/demo-project` fixture through the real
|
|
//! `aura` binary (#223).
|
|
//!
|
|
//! **Per-test project directories, no shared store.** [`fresh_project`] mints
|
|
//! a unique tempdir per test, wired to the shared, once-built fixture crate
|
|
//! via an absolute `[nodes]` pointer (`project.rs`'s pointer-arm routing), so
|
|
//! no two tests ever race on the same `runs/` store — libtest's default
|
|
//! thread parallelism applies without a serializing lock (#250).
|
|
//!
|
|
//! Each of the three test binaries compiles this file as its own `mod
|
|
//! common`, so an item unused by one binary trips that binary's `-D
|
|
//! warnings` `dead_code` lint; see the `#[allow(dead_code)]` markers below
|
|
//! rather than deleting a helper just because one binary doesn't need it.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::OnceLock;
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
|
|
/// The demo-project fixture (`Aura.toml` present), built once per test-binary
|
|
/// process. Each test binary has its own `OnceLock` (this module is compiled
|
|
/// separately per binary), but `cargo build` is idempotent, so a second
|
|
/// binary building the same fixture is a no-op.
|
|
pub fn built_project() -> &'static PathBuf {
|
|
static BUILT: OnceLock<PathBuf> = OnceLock::new();
|
|
BUILT.get_or_init(|| {
|
|
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/demo-project");
|
|
let out = std::process::Command::new("cargo")
|
|
.arg("build")
|
|
.current_dir(&dir)
|
|
.output()
|
|
.expect("spawn cargo build for the fixture project");
|
|
assert!(
|
|
out.status.success(),
|
|
"fixture build failed:\n{}",
|
|
String::from_utf8_lossy(&out.stderr)
|
|
);
|
|
dir
|
|
})
|
|
}
|
|
|
|
/// A per-test project directory, removed on drop (including mid-test panic).
|
|
#[allow(dead_code)]
|
|
pub struct FreshProject {
|
|
root: PathBuf,
|
|
}
|
|
|
|
impl Drop for FreshProject {
|
|
fn drop(&mut self) {
|
|
let _ = std::fs::remove_dir_all(&self.root);
|
|
}
|
|
}
|
|
|
|
/// Mint a unique tempdir holding a 2-line data-level project (`Aura.toml`
|
|
/// with an absolute `[nodes]` pointer at the shared, once-built fixture
|
|
/// crate) plus a copy of `demo_signal.json`. Every test gets its own `runs/`
|
|
/// store under this root, so no two tests ever contend for the same store —
|
|
/// this is what lets `project_lock` disappear (#250). Bind the guard for the
|
|
/// whole test body: `let (dir, _g) = fresh_project();`.
|
|
#[allow(dead_code)]
|
|
pub fn fresh_project() -> (PathBuf, FreshProject) {
|
|
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
|
let root = std::env::temp_dir().join(format!(
|
|
"aura-e2e-{}-{}",
|
|
std::process::id(),
|
|
COUNTER.fetch_add(1, Ordering::Relaxed)
|
|
));
|
|
std::fs::create_dir_all(&root).expect("create fresh project tempdir");
|
|
let fixture = built_project();
|
|
std::fs::write(
|
|
root.join("Aura.toml"),
|
|
format!(
|
|
"[paths]\nruns = \"runs\"\n\n[nodes]\ncrates = [\"{}\"]\n",
|
|
fixture.display()
|
|
),
|
|
)
|
|
.expect("write fresh project Aura.toml");
|
|
std::fs::copy(fixture.join("demo_signal.json"), root.join("demo_signal.json"))
|
|
.expect("copy demo_signal.json into the fresh project");
|
|
(root.clone(), FreshProject { root })
|
|
}
|
|
|
|
/// A scratch filesystem entry a test writes under the git-tracked
|
|
/// demo-project fixture root (only `runs/` there is fixture-gitignored),
|
|
/// removed on drop — including during a mid-test panic — so a failed
|
|
/// assertion never leaks scratch docs into tracked working-tree state.
|
|
#[allow(dead_code)]
|
|
pub enum ScratchPath {
|
|
File(PathBuf),
|
|
Dir(PathBuf),
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub struct ScratchGuard(pub Vec<ScratchPath>);
|
|
|
|
impl Drop for ScratchGuard {
|
|
fn drop(&mut self) {
|
|
for p in &self.0 {
|
|
match p {
|
|
ScratchPath::File(p) => {
|
|
let _ = std::fs::remove_file(p);
|
|
}
|
|
ScratchPath::Dir(p) => {
|
|
let _ = std::fs::remove_dir_all(p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|