From 8319228e7c219c5114fa4fb9719216adb404a33d Mon Sep 17 00:00:00 2001 From: Surma Date: Sat, 7 Nov 2026 19:41:16 +0000 Subject: [PATCH] host: consolidate integration tests into one executable --- AGENTS.md | 14 ++++ host/Cargo.toml | 5 ++ host/tests/README.md | 28 ++++++++ host/tests/{ => it}/api.rs | 0 host/tests/{ => it}/api_auth.rs | 0 host/tests/{ => it}/api_listener.rs | 0 host/tests/{ => it}/automations.rs | 19 ++---- host/tests/{ => it}/builder.rs | 26 ++------ host/tests/{ => it}/chain.rs | 19 ++---- host/tests/it/common.rs | 67 ++++++++++++++++++++ host/tests/{ => it}/core_ext.rs | 25 +------- host/tests/{ => it}/cred.rs | 0 host/tests/{ => it}/cred_accounting.rs | 0 host/tests/{ => it}/cred_authorization.rs | 0 host/tests/{ => it}/cred_challenges.rs | 0 host/tests/{ => it}/cred_core.rs | 0 host/tests/{ => it}/cred_edits.rs | 0 host/tests/{ => it}/cred_entry.rs | 0 host/tests/{ => it}/cred_grants.rs | 0 host/tests/{ => it}/cred_maintenance.rs | 0 host/tests/{ => it}/cred_oauth2.rs | 0 host/tests/{ => it}/cred_policy.rs | 0 host/tests/{ => it}/cred_presentation.rs | 0 host/tests/{ => it}/cred_proofs.rs | 0 host/tests/{ => it}/cred_quickjs.rs | 0 host/tests/{ => it}/cred_quickjs_security.rs | 0 host/tests/{ => it}/cred_regressions.rs | 0 host/tests/{ => it}/cred_replay.rs | 0 host/tests/{ => it}/cred_security.rs | 0 host/tests/{ => it}/cred_sign.rs | 0 host/tests/{ => it}/cred_storage.rs | 0 host/tests/{ => it}/cred_summary.rs | 0 host/tests/{ => it}/cred_webauthn.rs | 0 host/tests/{ => it}/dream.rs | 27 +------- host/tests/{ => it}/durable.rs | 12 ++-- host/tests/{ => it}/events.rs | 29 ++------- host/tests/{ => it}/extensions.rs | 10 +-- host/tests/{ => it}/goal_questions.rs | 25 +------- host/tests/{ => it}/harness.rs | 8 +-- host/tests/{ => it}/harness_api.rs | 10 +-- host/tests/{ => it}/harness_foundation.rs | 12 ++-- host/tests/{ => it}/hook.rs | 3 +- host/tests/{ => it}/hook_api.rs | 4 +- host/tests/{ => it}/hook_git.rs | 39 +----------- host/tests/{ => it}/hook_guest.rs | 6 +- host/tests/{ => it}/init.rs | 0 host/tests/it/main.rs | 60 ++++++++++++++++++ host/tests/{ => it}/messages.rs | 27 +------- host/tests/{ => it}/net.rs | 2 +- host/tests/{ => it}/p1.rs | 3 +- host/tests/{ => it}/p2.rs | 13 ++-- host/tests/{ => it}/p3.rs | 29 +-------- host/tests/{ => it}/p4.rs | 9 ++- host/tests/{ => it}/plan_todos.rs | 25 +------- host/tests/{ => it}/process.rs | 10 +-- host/tests/{ => it}/subagents.rs | 27 +------- host/tests/{ => it}/undo.rs | 0 host/tests/{ => it}/undo_api.rs | 0 host/tests/{ => it}/undo_cost.rs | 9 +-- host/tests/{ => it}/undo_exec.rs | 9 +-- host/tests/{ => it}/undo_harness.rs | 21 +----- host/tests/{ => it}/watchdog.rs | 27 +------- host/tests/{ => it}/workspaces.rs | 36 ++--------- 63 files changed, 268 insertions(+), 427 deletions(-) create mode 100644 host/tests/README.md rename host/tests/{ => it}/api.rs (100%) rename host/tests/{ => it}/api_auth.rs (100%) rename host/tests/{ => it}/api_listener.rs (100%) rename host/tests/{ => it}/automations.rs (93%) rename host/tests/{ => it}/builder.rs (93%) rename host/tests/{ => it}/chain.rs (90%) create mode 100644 host/tests/it/common.rs rename host/tests/{ => it}/core_ext.rs (84%) rename host/tests/{ => it}/cred.rs (100%) rename host/tests/{ => it}/cred_accounting.rs (100%) rename host/tests/{ => it}/cred_authorization.rs (100%) rename host/tests/{ => it}/cred_challenges.rs (100%) rename host/tests/{ => it}/cred_core.rs (100%) rename host/tests/{ => it}/cred_edits.rs (100%) rename host/tests/{ => it}/cred_entry.rs (100%) rename host/tests/{ => it}/cred_grants.rs (100%) rename host/tests/{ => it}/cred_maintenance.rs (100%) rename host/tests/{ => it}/cred_oauth2.rs (100%) rename host/tests/{ => it}/cred_policy.rs (100%) rename host/tests/{ => it}/cred_presentation.rs (100%) rename host/tests/{ => it}/cred_proofs.rs (100%) rename host/tests/{ => it}/cred_quickjs.rs (100%) rename host/tests/{ => it}/cred_quickjs_security.rs (100%) rename host/tests/{ => it}/cred_regressions.rs (100%) rename host/tests/{ => it}/cred_replay.rs (100%) rename host/tests/{ => it}/cred_security.rs (100%) rename host/tests/{ => it}/cred_sign.rs (100%) rename host/tests/{ => it}/cred_storage.rs (100%) rename host/tests/{ => it}/cred_summary.rs (100%) rename host/tests/{ => it}/cred_webauthn.rs (100%) rename host/tests/{ => it}/dream.rs (80%) rename host/tests/{ => it}/durable.rs (96%) rename host/tests/{ => it}/events.rs (80%) rename host/tests/{ => it}/extensions.rs (92%) rename host/tests/{ => it}/goal_questions.rs (82%) rename host/tests/{ => it}/harness.rs (94%) rename host/tests/{ => it}/harness_api.rs (95%) rename host/tests/{ => it}/harness_foundation.rs (93%) rename host/tests/{ => it}/hook.rs (99%) rename host/tests/{ => it}/hook_api.rs (98%) rename host/tests/{ => it}/hook_git.rs (83%) rename host/tests/{ => it}/hook_guest.rs (94%) rename host/tests/{ => it}/init.rs (100%) create mode 100644 host/tests/it/main.rs rename host/tests/{ => it}/messages.rs (86%) rename host/tests/{ => it}/net.rs (99%) rename host/tests/{ => it}/p1.rs (98%) rename host/tests/{ => it}/p2.rs (97%) rename host/tests/{ => it}/p3.rs (88%) rename host/tests/{ => it}/p4.rs (97%) rename host/tests/{ => it}/plan_todos.rs (70%) rename host/tests/{ => it}/process.rs (99%) rename host/tests/{ => it}/subagents.rs (84%) rename host/tests/{ => it}/undo.rs (100%) rename host/tests/{ => it}/undo_api.rs (100%) rename host/tests/{ => it}/undo_cost.rs (95%) rename host/tests/{ => it}/undo_exec.rs (94%) rename host/tests/{ => it}/undo_harness.rs (91%) rename host/tests/{ => it}/watchdog.rs (86%) rename host/tests/{ => it}/workspaces.rs (89%) diff --git a/AGENTS.md b/AGENTS.md index 31cf10f..f9f106f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,6 +56,20 @@ nix develop -c cargo build --manifest-path userland/Cargo.toml --release `nix flake check` still describes v2 and is updated later in P1. +## Tests + +Host integration tests share one executable: + +```sh +cargo test -p sausage --test it +cargo test -p sausage --test it -- chain:: +``` + +Use the required Nix environment and build slot wrapper for these commands. +Add test modules under `host/tests/it/` and declare them in `it/main.rs`. +Use fully qualified child test filters with `--exact`. +See `host/tests/README.md` for fixture and isolation details. + Tests must stay fast: - Only a few tests may wait for a timing (grace periods, deadlines). diff --git a/host/Cargo.toml b/host/Cargo.toml index 6625717..cd5e224 100644 --- a/host/Cargo.toml +++ b/host/Cargo.toml @@ -3,6 +3,11 @@ name = "sausage" version = "0.3.0" edition = "2024" description = "A world of WASI programs and durable agents." +autotests = false + +[[test]] +name = "it" +path = "tests/it/main.rs" [[bin]] name = "sausage" diff --git a/host/tests/README.md b/host/tests/README.md new file mode 100644 index 0000000..4278b34 --- /dev/null +++ b/host/tests/README.md @@ -0,0 +1,28 @@ +# Host integration tests + +All host integration tests share the `it` executable. Each module keeps its former file name. + +```sh +cargo test -p sausage --test it +cargo test -p sausage --test it -- chain:: +cargo test -p sausage --test it -- --test-threads=1 +``` + +Use `nix develop -c` and the required build slot wrapper for these commands. +Set `CARGO_TARGET_DIR` to your executor's target directory. + +Add new modules under `host/tests/it/` and declare them in `it/main.rs`. +Cargo does not discover separate test executables in `host/tests/`. +Fixture sources remain in `builder-fixture/`. + +`common.rs` shares the target lookup and the cached userland Git build. +A shared mutex protects the cwd-init output and the brush checkout during fixture builds. +The cwd-init build runs at most once per process when its output is absent. +The first parallel run reproduced a collision when two compiler processes wrote `init.o`. +Tests use temporary worlds and ephemeral listener ports. +Child processes receive environment variables through `Command`, not through changes to the test process. +SIGKILL parents use fully qualified test filters with `--exact`. + +The `hook::hook_git` and `hook_git` registrations both existed before this move. +Both remain to preserve the test count. +The shared `hook_guest` module now builds its fixture once per process. diff --git a/host/tests/api.rs b/host/tests/it/api.rs similarity index 100% rename from host/tests/api.rs rename to host/tests/it/api.rs diff --git a/host/tests/api_auth.rs b/host/tests/it/api_auth.rs similarity index 100% rename from host/tests/api_auth.rs rename to host/tests/it/api_auth.rs diff --git a/host/tests/api_listener.rs b/host/tests/it/api_listener.rs similarity index 100% rename from host/tests/api_listener.rs rename to host/tests/it/api_listener.rs diff --git a/host/tests/automations.rs b/host/tests/it/automations.rs similarity index 93% rename from host/tests/automations.rs rename to host/tests/it/automations.rs index cce5f4b..c01b96d 100644 --- a/host/tests/automations.rs +++ b/host/tests/it/automations.rs @@ -6,7 +6,7 @@ use std::{ process::Command, time::{Duration, Instant}, }; -const BUNDLE: &str = include_str!("../js/automations-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/automations-test-bundle.js"); fn copy_tree(source: &Path, destination: &Path) -> Result<()> { std::fs::create_dir_all(destination)?; for entry in std::fs::read_dir(source)? { @@ -24,14 +24,7 @@ fn setup(directory: &Path) -> Result<()> { let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland"); let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); if !userland.join("cwd-init/init.o").is_file() { - ensure!( - Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success(), - "cwd build failed" - ); + crate::common::cwd_init(); } ensure!( Command::new("cargo") @@ -57,11 +50,7 @@ fn setup(directory: &Path) -> Result<()> { let release = target.join("wasm32-wasip2/release"); if !release.join("brush.wasm").is_file() { ensure!( - Command::new("nu") - .arg("brush/build.nu") - .current_dir(&userland) - .status()? - .success(), + crate::common::build_brush()?.success(), "shell build failed" ); } @@ -251,7 +240,7 @@ async fn fire_after_sigkill() -> Result<()> { let directory = tempfile::tempdir()?; setup(directory.path())?; let mut child = Command::new(std::env::current_exe()?) - .args(["--exact", "automations_child", "--nocapture"]) + .args(["--exact", "automations::automations_child", "--nocapture"]) .env("SAUSAGE_AUTOMATIONS_CHILD", directory.path()) .spawn()?; let deadline = Instant::now() + Duration::from_secs(60); diff --git a/host/tests/builder.rs b/host/tests/it/builder.rs similarity index 93% rename from host/tests/builder.rs rename to host/tests/it/builder.rs index 019823a..35be6ce 100644 --- a/host/tests/builder.rs +++ b/host/tests/it/builder.rs @@ -13,13 +13,7 @@ fn programs() -> PathBuf { static PATH: OnceLock = OnceLock::new(); PATH.get_or_init(|| { let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap(); - assert!( - std::process::Command::new("nu") - .arg(root.join("userland/cwd-init/build.nu")) - .status() - .unwrap() - .success() - ); + crate::common::cwd_init(); assert!( std::process::Command::new("cargo") .args(["build", "--release", "-p", "world-cargo"]) @@ -28,13 +22,7 @@ fn programs() -> PathBuf { .unwrap() .success() ); - assert!( - std::process::Command::new("nu") - .arg(root.join("userland/brush/build.nu")) - .status() - .unwrap() - .success() - ); + assert!(crate::common::build_brush().unwrap().success()); PathBuf::from(std::env::var_os("CARGO_TARGET_DIR").expect("executor target required")) .join("wasm32-wasip2/release") }) @@ -108,23 +96,23 @@ async fn cargo_build_test_and_source_changes() -> anyhow::Result<()> { for (path, bytes) in [ ( "Cargo.toml", - include_bytes!("builder-fixture/Cargo.toml").as_slice(), + include_bytes!("../builder-fixture/Cargo.toml").as_slice(), ), ( "build.rs", - include_bytes!("builder-fixture/build.rs").as_slice(), + include_bytes!("../builder-fixture/build.rs").as_slice(), ), ( "src/main.rs", - include_bytes!("builder-fixture/src/main.rs").as_slice(), + include_bytes!("../builder-fixture/src/main.rs").as_slice(), ), ( "tests/integration.rs", - include_bytes!("builder-fixture/tests/integration.rs").as_slice(), + include_bytes!("../builder-fixture/tests/integration.rs").as_slice(), ), ( "src/add.c", - include_bytes!("builder-fixture/src/add.c").as_slice(), + include_bytes!("../builder-fixture/src/add.c").as_slice(), ), ] { std::fs::write(source.join(path), bytes)?; diff --git a/host/tests/chain.rs b/host/tests/it/chain.rs similarity index 90% rename from host/tests/chain.rs rename to host/tests/it/chain.rs index 5662f3f..e77ed87 100644 --- a/host/tests/chain.rs +++ b/host/tests/it/chain.rs @@ -16,14 +16,7 @@ fn components() -> Result { let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland"); let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); if !userland.join("cwd-init/init.o").is_file() { - ensure!( - std::process::Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success(), - "cwd fixture build failed" - ); + crate::common::cwd_init(); } ensure!( std::process::Command::new("cargo") @@ -48,11 +41,7 @@ fn components() -> Result { let release = target.join("wasm32-wasip2/release"); if !release.join("brush.wasm").is_file() { ensure!( - std::process::Command::new("nu") - .arg("brush/build.nu") - .current_dir(&userland) - .status()? - .success(), + crate::common::build_brush()?.success(), "shell fixture build failed" ); } @@ -101,7 +90,7 @@ async fn ancestry_reaches_the_real_hook() -> Result<()> { }, runtime.clone(), cred.clone(), - include_str!("../js/chain-test-bundle.js"), + include_str!("../../js/chain-test-bundle.js"), ) .await?; let records: serde_json::Value = @@ -155,7 +144,7 @@ async fn ancestry_reaches_the_real_hook() -> Result<()> { }); let source = format!( "globalThis.chainReady = true; globalThis.chainPort = {port};\n{}", - include_str!("../js/chain-test-bundle.js") + include_str!("../../js/chain-test-bundle.js") ); let result = tokio::time::timeout( Duration::from_secs(90), diff --git a/host/tests/it/common.rs b/host/tests/it/common.rs new file mode 100644 index 0000000..e8a00b8 --- /dev/null +++ b/host/tests/it/common.rs @@ -0,0 +1,67 @@ +//! Shared fixture builds serialize writes to cwd-init and the brush checkout. +use std::{ + path::{Path, PathBuf}, + process::{Command, ExitStatus}, + sync::{Mutex, OnceLock}, +}; +pub static BUILD: Mutex<()> = Mutex::new(()); + +pub fn target_dir() -> PathBuf { + std::env::var_os("CARGO_TARGET_DIR") + .map(PathBuf::from) + .unwrap_or_else(|| { + Path::new(env!("CARGO_TARGET_TMPDIR")) + .parent() + .unwrap() + .to_owned() + }) +} + +pub fn cwd_init() { + static INIT: OnceLock<()> = OnceLock::new(); + INIT.get_or_init(|| { + let _guard = BUILD.lock().unwrap(); + let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland"); + if !userland.join("cwd-init/init.o").is_file() { + assert!( + Command::new("nu") + .args(["--no-config-file", "cwd-init/build.nu"]) + .current_dir(userland) + .status() + .unwrap() + .success() + ); + } + }); +} + +pub fn build_brush() -> std::io::Result { + let _guard = BUILD.lock().unwrap(); + Command::new("nu") + .args(["--no-config-file", "brush/build.nu"]) + .env("CARGO_TARGET_DIR", target_dir()) + .current_dir(Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland")) + .status() +} + +pub fn build_git() -> &'static Path { + static GIT: OnceLock = OnceLock::new(); + GIT.get_or_init(|| { + let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland"); + let target = target_dir(); + cwd_init(); + assert!( + Command::new("cargo") + .args(["build", "--release", "-p", "git", "--manifest-path"]) + .arg(userland.join("Cargo.toml")) + .args(["--target", "wasm32-wasip2", "--target-dir"]) + .arg(&target) + .args(["-j", "2", "--locked"]) + .current_dir(&userland) + .status() + .unwrap() + .success() + ); + target + }) +} diff --git a/host/tests/core_ext.rs b/host/tests/it/core_ext.rs similarity index 84% rename from host/tests/core_ext.rs rename to host/tests/it/core_ext.rs index 9fff7d7..7cd3f23 100644 --- a/host/tests/core_ext.rs +++ b/host/tests/it/core_ext.rs @@ -4,7 +4,7 @@ use sausage::{ world::World, }; use std::{path::Path, sync::Arc}; -const BUNDLE: &str = include_str!("../js/core-ext-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/core-ext-test-bundle.js"); struct Author(Arc); #[async_trait::async_trait] impl Exec for Author { @@ -47,28 +47,7 @@ fn write(root: &Path, path: &str, text: &str) -> anyhow::Result<()> { #[tokio::test(flavor = "current_thread")] async fn core_extensions() -> anyhow::Result<()> { let repo = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap(); - let userland = repo.join("userland"); - let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); - if !userland.join("cwd-init/init.o").exists() { - assert!( - std::process::Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success() - ); - } - assert!( - std::process::Command::new("cargo") - .args(["build", "--release", "-p", "git", "--manifest-path"]) - .arg(userland.join("Cargo.toml")) - .args(["--target", "wasm32-wasip2", "--target-dir"]) - .arg(target) - .args(["-j", "2", "--locked"]) - .current_dir(&userland) - .status()? - .success() - ); + let target = crate::common::build_git(); let dir = tempfile::tempdir()?; let world = World::open(dir.path())?; let disk = dir.path().join("world"); diff --git a/host/tests/cred.rs b/host/tests/it/cred.rs similarity index 100% rename from host/tests/cred.rs rename to host/tests/it/cred.rs diff --git a/host/tests/cred_accounting.rs b/host/tests/it/cred_accounting.rs similarity index 100% rename from host/tests/cred_accounting.rs rename to host/tests/it/cred_accounting.rs diff --git a/host/tests/cred_authorization.rs b/host/tests/it/cred_authorization.rs similarity index 100% rename from host/tests/cred_authorization.rs rename to host/tests/it/cred_authorization.rs diff --git a/host/tests/cred_challenges.rs b/host/tests/it/cred_challenges.rs similarity index 100% rename from host/tests/cred_challenges.rs rename to host/tests/it/cred_challenges.rs diff --git a/host/tests/cred_core.rs b/host/tests/it/cred_core.rs similarity index 100% rename from host/tests/cred_core.rs rename to host/tests/it/cred_core.rs diff --git a/host/tests/cred_edits.rs b/host/tests/it/cred_edits.rs similarity index 100% rename from host/tests/cred_edits.rs rename to host/tests/it/cred_edits.rs diff --git a/host/tests/cred_entry.rs b/host/tests/it/cred_entry.rs similarity index 100% rename from host/tests/cred_entry.rs rename to host/tests/it/cred_entry.rs diff --git a/host/tests/cred_grants.rs b/host/tests/it/cred_grants.rs similarity index 100% rename from host/tests/cred_grants.rs rename to host/tests/it/cred_grants.rs diff --git a/host/tests/cred_maintenance.rs b/host/tests/it/cred_maintenance.rs similarity index 100% rename from host/tests/cred_maintenance.rs rename to host/tests/it/cred_maintenance.rs diff --git a/host/tests/cred_oauth2.rs b/host/tests/it/cred_oauth2.rs similarity index 100% rename from host/tests/cred_oauth2.rs rename to host/tests/it/cred_oauth2.rs diff --git a/host/tests/cred_policy.rs b/host/tests/it/cred_policy.rs similarity index 100% rename from host/tests/cred_policy.rs rename to host/tests/it/cred_policy.rs diff --git a/host/tests/cred_presentation.rs b/host/tests/it/cred_presentation.rs similarity index 100% rename from host/tests/cred_presentation.rs rename to host/tests/it/cred_presentation.rs diff --git a/host/tests/cred_proofs.rs b/host/tests/it/cred_proofs.rs similarity index 100% rename from host/tests/cred_proofs.rs rename to host/tests/it/cred_proofs.rs diff --git a/host/tests/cred_quickjs.rs b/host/tests/it/cred_quickjs.rs similarity index 100% rename from host/tests/cred_quickjs.rs rename to host/tests/it/cred_quickjs.rs diff --git a/host/tests/cred_quickjs_security.rs b/host/tests/it/cred_quickjs_security.rs similarity index 100% rename from host/tests/cred_quickjs_security.rs rename to host/tests/it/cred_quickjs_security.rs diff --git a/host/tests/cred_regressions.rs b/host/tests/it/cred_regressions.rs similarity index 100% rename from host/tests/cred_regressions.rs rename to host/tests/it/cred_regressions.rs diff --git a/host/tests/cred_replay.rs b/host/tests/it/cred_replay.rs similarity index 100% rename from host/tests/cred_replay.rs rename to host/tests/it/cred_replay.rs diff --git a/host/tests/cred_security.rs b/host/tests/it/cred_security.rs similarity index 100% rename from host/tests/cred_security.rs rename to host/tests/it/cred_security.rs diff --git a/host/tests/cred_sign.rs b/host/tests/it/cred_sign.rs similarity index 100% rename from host/tests/cred_sign.rs rename to host/tests/it/cred_sign.rs diff --git a/host/tests/cred_storage.rs b/host/tests/it/cred_storage.rs similarity index 100% rename from host/tests/cred_storage.rs rename to host/tests/it/cred_storage.rs diff --git a/host/tests/cred_summary.rs b/host/tests/it/cred_summary.rs similarity index 100% rename from host/tests/cred_summary.rs rename to host/tests/it/cred_summary.rs diff --git a/host/tests/cred_webauthn.rs b/host/tests/it/cred_webauthn.rs similarity index 100% rename from host/tests/cred_webauthn.rs rename to host/tests/it/cred_webauthn.rs diff --git a/host/tests/dream.rs b/host/tests/it/dream.rs similarity index 80% rename from host/tests/dream.rs rename to host/tests/it/dream.rs index d3edc2d..617e998 100644 --- a/host/tests/dream.rs +++ b/host/tests/it/dream.rs @@ -1,7 +1,7 @@ //! World-loaded dream built-ins and messages survive a harness reopen. use sausage::{harness::run_module, world::World}; use std::{path::Path, process::Command}; -const BUNDLE: &str = include_str!("../js/dream-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/dream-test-bundle.js"); fn git(repo: &Path, args: &[&str]) -> anyhow::Result { let output = Command::new("git") .args(args) @@ -36,30 +36,7 @@ fn prepare(dir: &Path) -> anyhow::Result<()> { let disk = dir.join("world"); let repo = disk.join("src/sausage"); let source = Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); - let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); - let userland = source.join("userland"); - if !userland.join("cwd-init/init.o").exists() { - anyhow::ensure!( - Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success(), - "cwd-init build failed" - ); - } - anyhow::ensure!( - Command::new("cargo") - .args(["build", "--release", "-p", "git", "--manifest-path"]) - .arg(userland.join("Cargo.toml")) - .args(["--target", "wasm32-wasip2", "--target-dir"]) - .arg(target) - .args(["-j", "2", "--locked"]) - .current_dir(&userland) - .status()? - .success(), - "world git build failed" - ); + let target = crate::common::build_git(); std::fs::create_dir_all(disk.join("bin"))?; std::fs::copy( target.join("wasm32-wasip2/release/git.wasm"), diff --git a/host/tests/durable.rs b/host/tests/it/durable.rs similarity index 96% rename from host/tests/durable.rs rename to host/tests/it/durable.rs index 7b718b6..47044c9 100644 --- a/host/tests/durable.rs +++ b/host/tests/it/durable.rs @@ -20,7 +20,7 @@ async fn restart_child() -> anyhow::Result<()> { let dir = std::path::Path::new(&dir); let source = format!( "globalThis.expectedState = 'approved';\n{}", - include_str!("../js/durable-test-bundle.js") + include_str!("../../js/durable-test-bundle.js") ); run_module( World::open(dir)?, @@ -59,7 +59,7 @@ fn sigkill_preserves_request_wait() -> anyhow::Result<()> { cred.unlocked()?, )?; let mut child = Command::new(std::env::current_exe()?) - .args(["--exact", "restart_child", "--nocapture"]) + .args(["--exact", "durable::restart_child", "--nocapture"]) .env("DURABLE_RESTART_DIR", &dir) .spawn()?; let deadline = Instant::now() + Duration::from_secs(30); @@ -117,7 +117,7 @@ fn sigkill_preserves_request_wait() -> anyhow::Result<()> { )?; for _ in 0..2 { let mut resumed = Command::new(std::env::current_exe()?) - .args(["--exact", "restart_child", "--nocapture"]) + .args(["--exact", "durable::restart_child", "--nocapture"]) .env("DURABLE_RESTART_DIR", &dir) .spawn()?; let deadline = Instant::now() + Duration::from_secs(30); @@ -147,7 +147,7 @@ async fn proxy_child() -> anyhow::Result<()> { let dir = std::path::Path::new(&dir); let source = format!( "globalThis.proxyOnly = true; const proxy = Deno.core.ops.op_model_proxy(); if (proxy.base_url !== 'https://proxy.invalid' || proxy.api_key !== 'faux-private-token') throw new Error('Wrong proxy item');\n{}", - include_str!("../js/durable-test-bundle.js") + include_str!("../../js/durable-test-bundle.js") ); run_module( World::open(dir)?, @@ -172,7 +172,7 @@ fn proxy_bootstrap_once() -> anyhow::Result<()> { let dir = std::env::temp_dir().join(format!("sausage-durable-proxy-{}", std::process::id())); for start in 0..2 { let status = std::process::Command::new(std::env::current_exe()?) - .args(["--exact", "proxy_child", "--nocapture"]) + .args(["--exact", "durable::proxy_child", "--nocapture"]) .env("DURABLE_PROXY_DIR", &dir) .env( "PI_PROXY_BASE_URL", @@ -259,7 +259,7 @@ async fn decisions_wake_idle_conversation() -> anyhow::Result<()> { )?; let source = format!( "globalThis.expectedState = {state:?};\n{}", - include_str!("../js/durable-test-bundle.js") + include_str!("../../js/durable-test-bundle.js") ); let decide = async { let conversation = loop { diff --git a/host/tests/events.rs b/host/tests/it/events.rs similarity index 80% rename from host/tests/events.rs rename to host/tests/it/events.rs index 490d41d..7d40ff3 100644 --- a/host/tests/events.rs +++ b/host/tests/it/events.rs @@ -5,7 +5,7 @@ use std::{ process::Command, time::{Duration, Instant}, }; -const BUNDLE: &str = include_str!("../js/events-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/events-test-bundle.js"); struct Author(std::sync::Arc); #[async_trait::async_trait] impl sausage::exec::Exec for Author { @@ -51,7 +51,7 @@ async fn restart_child() -> anyhow::Result<()> { fn sigkill_between_emit_and_reaction() -> anyhow::Result<()> { let dir = tempfile::tempdir()?; let mut child = Command::new(std::env::current_exe()?) - .args(["--exact", "restart_child", "--nocapture"]) + .args(["--exact", "events::restart_child", "--nocapture"]) .env("EVENTS_RESTART_DIR", dir.path()) .env("EVENTS_MODE", "emit") .spawn()?; @@ -68,7 +68,7 @@ fn sigkill_between_emit_and_reaction() -> anyhow::Result<()> { child.wait()?; for _ in 0..2 { let mut child = Command::new(std::env::current_exe()?) - .args(["--exact", "restart_child", "--nocapture"]) + .args(["--exact", "events::restart_child", "--nocapture"]) .env("EVENTS_RESTART_DIR", dir.path()) .env("EVENTS_MODE", "consume") .spawn()?; @@ -90,28 +90,7 @@ fn sigkill_between_emit_and_reaction() -> anyhow::Result<()> { } #[tokio::test(flavor = "current_thread")] async fn world_extensions() -> anyhow::Result<()> { - let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland"); - let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); - if !userland.join("cwd-init/init.o").is_file() { - assert!( - Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success() - ); - } - assert!( - Command::new("cargo") - .args(["build", "--release", "-p", "git", "--manifest-path"]) - .arg(userland.join("Cargo.toml")) - .args(["--target", "wasm32-wasip2", "--target-dir"]) - .arg(target) - .args(["-j", "2", "--locked"]) - .current_dir(&userland) - .status()? - .success() - ); + let target = crate::common::build_git(); let dir = tempfile::tempdir()?; let world = World::open(dir.path())?; let disk = dir.path().join("world"); diff --git a/host/tests/extensions.rs b/host/tests/it/extensions.rs similarity index 92% rename from host/tests/extensions.rs rename to host/tests/it/extensions.rs index d6ae8eb..39d9d05 100644 --- a/host/tests/extensions.rs +++ b/host/tests/it/extensions.rs @@ -4,7 +4,7 @@ use sausage::{ world::World, }; use std::{path::Path, sync::Arc}; -const BUNDLE: &str = include_str!("../js/extensions-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/extensions-test-bundle.js"); struct Author(Arc); #[async_trait::async_trait] impl Exec for Author { @@ -30,13 +30,7 @@ async fn commits_and_restart() -> anyhow::Result<()> { let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland"); let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); if !userland.join("cwd-init/init.o").is_file() { - assert!( - std::process::Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success() - ); + crate::common::cwd_init(); } assert!( std::process::Command::new("cargo") diff --git a/host/tests/goal_questions.rs b/host/tests/it/goal_questions.rs similarity index 82% rename from host/tests/goal_questions.rs rename to host/tests/it/goal_questions.rs index 2879276..3721dad 100644 --- a/host/tests/goal_questions.rs +++ b/host/tests/it/goal_questions.rs @@ -1,7 +1,7 @@ //! Goal and question-card server cases execute through the world loader. use sausage::{harness::run_module, world::World}; use std::{path::Path, process::Command}; -const BUNDLE: &str = include_str!("../js/goal-questions-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/goal-questions-test-bundle.js"); static PREPARE: std::sync::Mutex<()> = std::sync::Mutex::new(()); fn prepare(dir: &Path) -> anyhow::Result<()> { let _guard = PREPARE.lock().unwrap(); @@ -11,28 +11,7 @@ fn prepare(dir: &Path) -> anyhow::Result<()> { return Ok(()); } let source = Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); - let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); - let userland = source.join("userland"); - if !userland.join("cwd-init/init.o").exists() { - assert!( - Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success() - ); - } - assert!( - Command::new("cargo") - .args(["build", "--release", "-p", "git", "--manifest-path"]) - .arg(userland.join("Cargo.toml")) - .args(["--target", "wasm32-wasip2", "--target-dir"]) - .arg(target) - .args(["-j", "2", "--locked"]) - .current_dir(&userland) - .status()? - .success() - ); + let target = crate::common::build_git(); std::fs::create_dir_all(disk.join("bin"))?; std::fs::copy( target.join("wasm32-wasip2/release/git.wasm"), diff --git a/host/tests/harness.rs b/host/tests/it/harness.rs similarity index 94% rename from host/tests/harness.rs rename to host/tests/it/harness.rs index d4c08ae..df4ed3d 100644 --- a/host/tests/harness.rs +++ b/host/tests/it/harness.rs @@ -45,7 +45,7 @@ async fn crash_child() -> anyhow::Result<()> { let source = format!( "globalThis.testCrash = {};\n{}", serde_json::to_string(&phase)?, - include_str!("../js/test-bundle.js") + include_str!("../../js/test-bundle.js") ); tokio::time::timeout( std::time::Duration::from_secs(20), @@ -69,7 +69,7 @@ fn sigkill_resumes_model_and_tool() -> anyhow::Result<()> { let dir = std::env::temp_dir().join(format!("sausage-crash-{}-{phase}", std::process::id())); let mut child = Command::new(std::env::current_exe()?) - .args(["--exact", "crash_child", "--nocapture"]) + .args(["--exact", "harness::crash_child", "--nocapture"]) .env("HARNESS_CRASH_DIR", &dir) .env("HARNESS_CRASH_PHASE", phase) .stdout(Stdio::piped()) @@ -92,7 +92,7 @@ fn sigkill_resumes_model_and_tool() -> anyhow::Result<()> { child.wait()?; ready?; let resumed = Command::new(std::env::current_exe()?) - .args(["--exact", "crash_child", "--nocapture"]) + .args(["--exact", "harness::crash_child", "--nocapture"]) .env("HARNESS_CRASH_DIR", &dir) .env("HARNESS_CRASH_PHASE", "resume") .status()?; @@ -110,7 +110,7 @@ async fn file_tools_and_exec() -> anyhow::Result<()> { let result = run_module( World::open(&dir)?, exec.clone(), - include_str!("../js/test-bundle.js"), + include_str!("../../js/test-bundle.js"), ) .await; result?; diff --git a/host/tests/harness_api.rs b/host/tests/it/harness_api.rs similarity index 95% rename from host/tests/harness_api.rs rename to host/tests/it/harness_api.rs index e163e47..bb73e3b 100644 --- a/host/tests/harness_api.rs +++ b/host/tests/it/harness_api.rs @@ -2,7 +2,7 @@ use sausage::{harness::run_module_with_input, world::World}; use std::sync::Arc; use std::{path::Path, process::Command}; -const BUNDLE: &str = include_str!("../js/harness-api-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/harness-api-test-bundle.js"); static PREPARE: std::sync::Mutex<()> = std::sync::Mutex::new(()); fn prepare(dir: &Path) -> anyhow::Result<()> { let _guard = PREPARE.lock().unwrap(); @@ -15,13 +15,7 @@ fn prepare(dir: &Path) -> anyhow::Result<()> { let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); let userland = source.join("userland"); if !userland.join("cwd-init/init.o").exists() { - assert!( - Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success() - ); + crate::common::cwd_init(); } assert!( Command::new("cargo") diff --git a/host/tests/harness_foundation.rs b/host/tests/it/harness_foundation.rs similarity index 93% rename from host/tests/harness_foundation.rs rename to host/tests/it/harness_foundation.rs index 00f95ec..05149b8 100644 --- a/host/tests/harness_foundation.rs +++ b/host/tests/it/harness_foundation.rs @@ -13,16 +13,16 @@ use std::{ time::{Duration, Instant}, }; -const BUNDLE: &str = include_str!("../js/foundation-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/foundation-test-bundle.js"); #[test] fn bundle_has_patched_hooks() { - for bundle in [BUNDLE, include_str!("../js/bundle.js")] { + for bundle in [BUNDLE, include_str!("../../js/bundle.js")] { assert!(bundle.contains("handlers.beforeInput")); assert!(bundle.contains("input: true")); assert!(bundle.contains("#held")); } - assert!(!include_str!("../js/patches/pi-durable-1.5.0.patch").contains("dist/env/node")); + assert!(!include_str!("../../js/patches/pi-durable-1.5.0.patch").contains("dist/env/node")); } async fn probe(case: &str) -> anyhow::Result<()> { @@ -87,7 +87,11 @@ async fn headless_child() -> anyhow::Result<()> { #[test] fn channel_with_closed_stdin() -> anyhow::Result<()> { let mut child = Command::new(std::env::current_exe()?) - .args(["--exact", "headless_child", "--nocapture"]) + .args([ + "--exact", + "harness_foundation::headless_child", + "--nocapture", + ]) .env("HEADLESS_CHILD", "1") .stdin(Stdio::null()) .spawn()?; diff --git a/host/tests/hook.rs b/host/tests/it/hook.rs similarity index 99% rename from host/tests/hook.rs rename to host/tests/it/hook.rs index 2abe6c0..ed3263d 100644 --- a/host/tests/hook.rs +++ b/host/tests/it/hook.rs @@ -1,6 +1,7 @@ //! Real guest HTTP requests pass through caller identity, credential injection, and streaming transport. +#[path = "hook_git.rs"] mod hook_git; -mod hook_guest; +use super::hook_guest; use sausage::{ cred::{ self, diff --git a/host/tests/hook_api.rs b/host/tests/it/hook_api.rs similarity index 98% rename from host/tests/hook_api.rs rename to host/tests/it/hook_api.rs index bbec3f1..2da3a51 100644 --- a/host/tests/hook_api.rs +++ b/host/tests/it/hook_api.rs @@ -10,8 +10,8 @@ use std::{ io::{Read, Write}, sync::Arc, }; -#[path = "hook_guest.rs"] -mod hook_guest; + +use super::hook_guest; #[tokio::test] async fn one_api_for_pages_and_guest_requests() -> anyhow::Result<()> { diff --git a/host/tests/hook_git.rs b/host/tests/it/hook_git.rs similarity index 83% rename from host/tests/hook_git.rs rename to host/tests/it/hook_git.rs index 8e4139c..280ad24 100644 --- a/host/tests/hook_git.rs +++ b/host/tests/it/hook_git.rs @@ -8,7 +8,7 @@ use std::{ io::Write, path::Path, process::Stdio, - sync::{Arc, OnceLock}, + sync::Arc, }; fn git(cwd: &Path, args: &[&str]) { assert!( @@ -22,42 +22,7 @@ fn git(cwd: &Path, args: &[&str]) { } #[tokio::test] async fn several_mb_git_push() { - static BUILT: OnceLock = OnceLock::new(); - let component = BUILT.get_or_init(|| { - let target = std::env::var_os("CARGO_TARGET_DIR") - .map(std::path::PathBuf::from) - .unwrap_or_else(|| { - Path::new(env!("CARGO_TARGET_TMPDIR")) - .parent() - .unwrap() - .to_owned() - }); - let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland"); - if !userland.join("cwd-init/init.o").is_file() { - assert!( - std::process::Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status() - .unwrap() - .success() - ); - } - let manifest = userland.join("Cargo.toml"); - assert!( - std::process::Command::new("cargo") - .args(["build", "--release", "-p", "git", "--manifest-path"]) - .arg(manifest) - .current_dir(&userland) - .args(["--target", "wasm32-wasip2", "--target-dir"]) - .arg(&target) - .args(["-j", "2", "--locked"]) - .status() - .unwrap() - .success() - ); - target.join("wasm32-wasip2/release/git.wasm") - }); + let component = crate::common::build_git().join("wasm32-wasip2/release/git.wasm"); let temp = tempfile::tempdir().unwrap(); let world = World::open(temp.path()).unwrap(); let root = temp.path().join("world"); diff --git a/host/tests/hook_guest.rs b/host/tests/it/hook_guest.rs similarity index 94% rename from host/tests/hook_guest.rs rename to host/tests/it/hook_guest.rs index ab8b22b..6efc853 100644 --- a/host/tests/hook_guest.rs +++ b/host/tests/it/hook_guest.rs @@ -12,12 +12,12 @@ fn write(path: PathBuf, text: &str) -> std::io::Result<()> { pub fn component() -> &'static Path { static PATH: OnceLock = OnceLock::new(); PATH.get_or_init(|| { - let target = std::env::var_os("CARGO_TARGET_DIR").map(PathBuf::from).unwrap_or_else(|| Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap().to_owned()); + let target = crate::common::target_dir(); let directory = Path::new(env!("CARGO_TARGET_TMPDIR")).join("hook-guest"); std::fs::create_dir_all(directory.join("src")).unwrap(); std::fs::create_dir_all(directory.join("wit/deps")).unwrap(); - write(directory.join("wit/process.wit"), include_str!("../wit/process.wit")).unwrap(); - write(directory.join("wit/deps/io.wit"), include_str!("../wit/deps/io.wit")).unwrap(); + write(directory.join("wit/process.wit"), include_str!("../../wit/process.wit")).unwrap(); + write(directory.join("wit/deps/io.wit"), include_str!("../../wit/deps/io.wit")).unwrap(); write(directory.join("Cargo.toml"), "[package]\nname='hook-guest'\nversion='0.1.0'\nedition='2024'\n[workspace]\n[dependencies]\nwstd='=0.6.8'\nanyhow='1'\nfutures-lite='2'\nwit-bindgen='0.46'\n").unwrap(); write(directory.join("src/main.rs"), r#" use wstd::http::{Body, BodyExt, Client, Request}; diff --git a/host/tests/init.rs b/host/tests/it/init.rs similarity index 100% rename from host/tests/init.rs rename to host/tests/it/init.rs diff --git a/host/tests/it/main.rs b/host/tests/it/main.rs new file mode 100644 index 0000000..8f040da --- /dev/null +++ b/host/tests/it/main.rs @@ -0,0 +1,60 @@ +//! Host integration tests share one executable. Modules retain their original test registrations. +mod api; +mod api_auth; +mod api_listener; +mod automations; +mod builder; +mod chain; +mod common; +mod core_ext; +mod cred; +mod cred_accounting; +mod cred_authorization; +mod cred_challenges; +mod cred_core; +mod cred_edits; +mod cred_entry; +mod cred_grants; +mod cred_maintenance; +mod cred_oauth2; +mod cred_policy; +mod cred_presentation; +mod cred_proofs; +mod cred_quickjs; +mod cred_quickjs_security; +mod cred_regressions; +mod cred_replay; +mod cred_security; +mod cred_sign; +mod cred_storage; +mod cred_summary; +mod cred_webauthn; +mod dream; +mod durable; +mod events; +mod extensions; +mod goal_questions; +mod harness; +mod harness_api; +mod harness_foundation; +mod hook; +mod hook_api; +mod hook_git; +mod hook_guest; +mod init; +mod messages; +mod net; +mod p1; +mod p2; +mod p3; +mod p4; +mod plan_todos; +mod process; +mod subagents; +mod undo; +mod undo_api; +mod undo_cost; +mod undo_exec; +mod undo_harness; +mod watchdog; +mod workspaces; diff --git a/host/tests/messages.rs b/host/tests/it/messages.rs similarity index 86% rename from host/tests/messages.rs rename to host/tests/it/messages.rs index 628d3b5..b5d0953 100644 --- a/host/tests/messages.rs +++ b/host/tests/it/messages.rs @@ -5,7 +5,7 @@ use std::{ process::Command, time::{Duration, Instant}, }; -const BUNDLE: &str = include_str!("../js/messages-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/messages-test-bundle.js"); static PREPARE: std::sync::Mutex<()> = std::sync::Mutex::new(()); fn prepare(dir: &Path) -> anyhow::Result<()> { let _guard = PREPARE.lock().unwrap(); @@ -15,28 +15,7 @@ fn prepare(dir: &Path) -> anyhow::Result<()> { return Ok(()); } let source = Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); - let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); - let userland = source.join("userland"); - if !userland.join("cwd-init/init.o").exists() { - assert!( - Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success() - ); - } - assert!( - Command::new("cargo") - .args(["build", "--release", "-p", "git", "--manifest-path"]) - .arg(userland.join("Cargo.toml")) - .args(["--target", "wasm32-wasip2", "--target-dir"]) - .arg(target) - .args(["-j", "2", "--locked"]) - .current_dir(&userland) - .status()? - .success() - ); + let target = crate::common::build_git(); std::fs::create_dir_all(disk.join("bin"))?; std::fs::copy( target.join("wasm32-wasip2/release/git.wasm"), @@ -143,7 +122,7 @@ fn sigkill_after_reply_placement() -> anyhow::Result<()> { prepare(dir.path())?; let spawn = |mode: &str| { Command::new(std::env::current_exe().unwrap()) - .args(["--exact", "restart_child", "--nocapture"]) + .args(["--exact", "messages::restart_child", "--nocapture"]) .env("MESSAGES_DIR", dir.path()) .env("MESSAGES_MODE", mode) .spawn() diff --git a/host/tests/net.rs b/host/tests/it/net.rs similarity index 99% rename from host/tests/net.rs rename to host/tests/it/net.rs index 51a6998..8a98a9f 100644 --- a/host/tests/net.rs +++ b/host/tests/it/net.rs @@ -1,5 +1,5 @@ //! Real components can accept TCP connections. Host port registration and host-only config deny access. -mod hook_guest; +use super::hook_guest; use sausage::{ exec::{Command, Exec, Options}, process::{Runtime, net::NetPolicy}, diff --git a/host/tests/p1.rs b/host/tests/it/p1.rs similarity index 98% rename from host/tests/p1.rs rename to host/tests/it/p1.rs index ef4c22d..d770f9f 100644 --- a/host/tests/p1.rs +++ b/host/tests/it/p1.rs @@ -8,6 +8,7 @@ async fn bundle_copy_and_real_shell() -> anyhow::Result<()> { let repo = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".."); let bundle = PathBuf::from(std::env::var("CARGO_TARGET_DIR").unwrap()).join("bundle"); if !bundle.join("bin/sh.wasm").exists() { + let _guard = crate::common::BUILD.lock().unwrap(); assert!( std::process::Command::new("nu") .arg(repo.join("userland/bundle.nu")) @@ -101,7 +102,7 @@ async fn bundle_copy_and_real_shell() -> anyhow::Result<()> { assert!(status.is_empty(), "{}", String::from_utf8_lossy(&status)); let source = format!( "globalThis.testP1 = true;\n{}", - include_str!("../js/test-bundle.js") + include_str!("../../js/test-bundle.js") ); run_module(world, exec.clone(), &source).await?; assert_eq!( diff --git a/host/tests/p2.rs b/host/tests/it/p2.rs similarity index 97% rename from host/tests/p2.rs rename to host/tests/it/p2.rs index aac61a5..4897ca1 100644 --- a/host/tests/p2.rs +++ b/host/tests/it/p2.rs @@ -166,9 +166,9 @@ impl Browser { } fn components() -> Result { let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland"); - let target = Path::new(env!("CARGO_TARGET_TMPDIR")).join("p2-userland"); + let target = crate::common::target_dir(); if !userland.join("cwd-init/init.o").is_file() { - command(&userland, &["nu", "--no-config-file", "cwd-init/build.nu"])?; + crate::common::cwd_init(); } let status = Command::new("cargo") .current_dir(&userland) @@ -192,12 +192,7 @@ fn components() -> Result { let release = target.join("wasm32-wasip2/release"); if !release.join("sh.wasm").is_file() { ensure!( - Command::new("nu") - .arg("brush/build.nu") - .env("CARGO_TARGET_DIR", &target) - .current_dir(&userland) - .status()? - .success(), + crate::common::build_brush()?.success(), "shell build failed" ); std::fs::copy(release.join("brush.wasm"), release.join("sh.wasm"))?; @@ -413,7 +408,7 @@ async fn request_approve_fetch_and_ssh_push() -> Result<()> { browser.enter(&ssh_item, &key).await?; let source = format!( "globalThis.p2Http = {http_port};\n{}", - include_str!("../js/p2-test-bundle.js") + include_str!("../../js/p2-test-bundle.js") ); let approve = async { for item in [&http_item, &ssh_item] { diff --git a/host/tests/p3.rs b/host/tests/it/p3.rs similarity index 88% rename from host/tests/p3.rs rename to host/tests/it/p3.rs index a8eb70b..5b36a52 100644 --- a/host/tests/p3.rs +++ b/host/tests/it/p3.rs @@ -5,7 +5,7 @@ use std::{ process::Command, time::{Duration, Instant}, }; -const BUNDLE: &str = include_str!("../js/p3-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/p3-test-bundle.js"); fn git(repo: &Path, args: &[&str]) -> anyhow::Result { let output = Command::new("git") .args(args) @@ -40,30 +40,7 @@ fn prepare(dir: &Path, all_extensions: bool) -> anyhow::Result<()> { let disk = dir.join("world"); let repo = disk.join("src/sausage"); let source = Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); - let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); - let userland = source.join("userland"); - if !userland.join("cwd-init/init.o").exists() { - anyhow::ensure!( - Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success(), - "cwd-init build failed" - ); - } - anyhow::ensure!( - Command::new("cargo") - .args(["build", "--release", "-p", "git", "--manifest-path"]) - .arg(userland.join("Cargo.toml")) - .args(["--target", "wasm32-wasip2", "--target-dir"]) - .arg(target) - .args(["-j", "2", "--locked"]) - .current_dir(&userland) - .status()? - .success(), - "world git build failed" - ); + let target = crate::common::build_git(); std::fs::create_dir_all(disk.join("bin"))?; std::fs::copy( target.join("wasm32-wasip2/release/git.wasm"), @@ -162,7 +139,7 @@ fn worker_sigkill_recovery() -> anyhow::Result<()> { prepare(dir.path(), false)?; let spawn = |mode: &str| { Command::new(std::env::current_exe().unwrap()) - .args(["--exact", "restart_child", "--nocapture"]) + .args(["--exact", "p3::restart_child", "--nocapture"]) .env("P3_DIR", dir.path()) .env("P3_MODE", mode) .spawn() diff --git a/host/tests/p4.rs b/host/tests/it/p4.rs similarity index 97% rename from host/tests/p4.rs rename to host/tests/it/p4.rs index ef166d4..f0021bb 100644 --- a/host/tests/p4.rs +++ b/host/tests/it/p4.rs @@ -26,7 +26,7 @@ fn prepare(dir: &Path) -> anyhow::Result { ensure!(revision.status.success(), "fixture revision"); if fs::read(bundle.join("revision")).ok().as_deref() != Some(revision.stdout.as_slice()) { if !userland.join("cwd-init/init.o").exists() { - checked(Command::new("nu").arg(userland.join("cwd-init/build.nu")))?; + crate::common::cwd_init(); } checked(Command::new("cargo").current_dir(&userland).args([ "build", @@ -43,7 +43,10 @@ fn prepare(dir: &Path) -> anyhow::Result { "-p", "sausage-coreutils", ]))?; - checked(Command::new("nu").arg(userland.join("brush/build.nu")))?; + ensure!( + crate::common::build_brush()?.success(), + "shell build failed" + ); fs::create_dir_all(bundle.join("bin"))?; fs::create_dir_all(bundle.join("src"))?; for (binary, name) in [ @@ -68,7 +71,7 @@ fn prepare(dir: &Path) -> anyhow::Result { fs::create_dir_all(repo.join("extensions/p4"))?; fs::write( repo.join("extensions/p4/server.ts"), - include_str!("../js/p4-test-bundle.js"), + include_str!("../../js/p4-test-bundle.js"), )?; for args in [ vec!["init", "-b", "main"], diff --git a/host/tests/plan_todos.rs b/host/tests/it/plan_todos.rs similarity index 70% rename from host/tests/plan_todos.rs rename to host/tests/it/plan_todos.rs index 32bdda5..7040859 100644 --- a/host/tests/plan_todos.rs +++ b/host/tests/it/plan_todos.rs @@ -1,32 +1,11 @@ //! The plan and todos ports run through real world extension snapshots and survive restart. use sausage::world::World; use std::path::Path; -const BUNDLE: &str = include_str!("../js/plan-todos-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/plan-todos-test-bundle.js"); #[tokio::test(flavor = "current_thread")] async fn plan_todos_and_restart() -> anyhow::Result<()> { - let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland"); - let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); - if !userland.join("cwd-init/init.o").is_file() { - assert!( - std::process::Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success() - ); - } - assert!( - std::process::Command::new("cargo") - .args(["build", "--release", "-p", "git", "--manifest-path"]) - .arg(userland.join("Cargo.toml")) - .args(["--target", "wasm32-wasip2", "--target-dir"]) - .arg(target) - .args(["-j", "2", "--locked"]) - .current_dir(&userland) - .status()? - .success() - ); + let target = crate::common::build_git(); let dir = tempfile::tempdir()?; let disk = dir.path().join("world"); let _world = World::open(dir.path())?; diff --git a/host/tests/process.rs b/host/tests/it/process.rs similarity index 99% rename from host/tests/process.rs rename to host/tests/it/process.rs index 27fc711..9b5dfbe 100644 --- a/host/tests/process.rs +++ b/host/tests/it/process.rs @@ -375,10 +375,7 @@ fn build() { "guest build failed: {args:?}" ); }; - run( - &["nu", "--no-config-file", "userland/cwd-init/build.nu"], - &root, - ); + crate::common::cwd_init(); run( &[ "cargo", @@ -394,10 +391,7 @@ fn build() { ], &root.join("userland"), ); - run( - &["nu", "--no-config-file", "userland/brush/build.nu"], - &root, - ); + assert!(crate::common::build_brush().unwrap().success()); run( &[ "cargo", diff --git a/host/tests/subagents.rs b/host/tests/it/subagents.rs similarity index 84% rename from host/tests/subagents.rs rename to host/tests/it/subagents.rs index 12a24f9..1fd08ff 100644 --- a/host/tests/subagents.rs +++ b/host/tests/it/subagents.rs @@ -5,7 +5,7 @@ use std::{ process::Command, time::{Duration, Instant}, }; -const BUNDLE: &str = include_str!("../js/subagents-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/subagents-test-bundle.js"); fn prepare(dir: &Path) -> anyhow::Result<()> { let disk = dir.join("world"); let repo = disk.join("src/sausage"); @@ -13,28 +13,7 @@ fn prepare(dir: &Path) -> anyhow::Result<()> { return Ok(()); } let source = Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); - let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); - let userland = source.join("userland"); - if !userland.join("cwd-init/init.o").exists() { - assert!( - Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success() - ); - } - assert!( - Command::new("cargo") - .args(["build", "--release", "-p", "git", "--manifest-path"]) - .arg(userland.join("Cargo.toml")) - .args(["--target", "wasm32-wasip2", "--target-dir"]) - .arg(target) - .args(["-j", "2", "--locked"]) - .current_dir(&userland) - .status()? - .success() - ); + let target = crate::common::build_git(); std::fs::create_dir_all(disk.join("bin"))?; std::fs::copy( target.join("wasm32-wasip2/release/git.wasm"), @@ -131,7 +110,7 @@ fn sigkill_between_deliver_and_report() -> anyhow::Result<()> { prepare(dir.path())?; let spawn = |mode: &str| { Command::new(std::env::current_exe().unwrap()) - .args(["--exact", "restart_child", "--nocapture"]) + .args(["--exact", "subagents::restart_child", "--nocapture"]) .env("SUBAGENTS_DIR", dir.path()) .env("SUBAGENTS_MODE", mode) .spawn() diff --git a/host/tests/undo.rs b/host/tests/it/undo.rs similarity index 100% rename from host/tests/undo.rs rename to host/tests/it/undo.rs diff --git a/host/tests/undo_api.rs b/host/tests/it/undo_api.rs similarity index 100% rename from host/tests/undo_api.rs rename to host/tests/it/undo_api.rs diff --git a/host/tests/undo_cost.rs b/host/tests/it/undo_cost.rs similarity index 95% rename from host/tests/undo_cost.rs rename to host/tests/it/undo_cost.rs index 0d077d3..0496d56 100644 --- a/host/tests/undo_cost.rs +++ b/host/tests/it/undo_cost.rs @@ -11,14 +11,7 @@ async fn status_cost() { let target = PathBuf::from(std::env::var("CARGO_TARGET_DIR").unwrap()); if !target.join("wasm32-wasip2/release/git.wasm").is_file() { let userland = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../userland"); - assert!( - std::process::Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status() - .unwrap() - .success() - ); + crate::common::cwd_init(); assert!( std::process::Command::new("cargo") .args(["build", "--release", "-p", "git"]) diff --git a/host/tests/undo_exec.rs b/host/tests/it/undo_exec.rs similarity index 94% rename from host/tests/undo_exec.rs rename to host/tests/it/undo_exec.rs index 0b7aaca..7f279f9 100644 --- a/host/tests/undo_exec.rs +++ b/host/tests/it/undo_exec.rs @@ -39,14 +39,7 @@ async fn tool_call_commit_and_independent_change() { || !guest_dir.join("touch.wasm").is_file() || !guest_dir.join("undo.wasm").is_file() { - assert!( - std::process::Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../userland")) - .status() - .unwrap() - .success() - ); + crate::common::cwd_init(); assert!( std::process::Command::new("cargo") .args([ diff --git a/host/tests/undo_harness.rs b/host/tests/it/undo_harness.rs similarity index 91% rename from host/tests/undo_harness.rs rename to host/tests/it/undo_harness.rs index b7b272f..341fe20 100644 --- a/host/tests/undo_harness.rs +++ b/host/tests/it/undo_harness.rs @@ -42,14 +42,7 @@ async fn command(runtime: &Arc, args: &[&str]) -> String { async fn tools_restore_one_call_or_the_whole_turn() { let target = PathBuf::from(std::env::var("CARGO_TARGET_DIR").unwrap()); let repo = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap(); - assert!( - std::process::Command::new("nu") - .args(["--no-config-file", "userland/cwd-init/build.nu"]) - .current_dir(repo) - .status() - .unwrap() - .success() - ); + crate::common::cwd_init(); assert!( std::process::Command::new("cargo") .args([ @@ -71,15 +64,7 @@ async fn tools_restore_one_call_or_the_whole_turn() { .unwrap() .success() ); - assert!( - std::process::Command::new("nu") - .args(["--no-config-file", "userland/brush/build.nu"]) - .env("CARGO_TARGET_DIR", &target) - .current_dir(repo) - .status() - .unwrap() - .success() - ); + assert!(crate::common::build_brush().unwrap().success()); for since in [false, true] { let temp = tempfile::tempdir().unwrap(); let world = World::open(temp.path()).unwrap(); @@ -112,7 +97,7 @@ async fn tools_restore_one_call_or_the_whole_turn() { sausage::harness::run_module( world, Arc::new(runtime.clone()), - include_str!("../js/undo-harness-test-bundle.js"), + include_str!("../../js/undo-harness-test-bundle.js"), ) .await .unwrap(); diff --git a/host/tests/watchdog.rs b/host/tests/it/watchdog.rs similarity index 86% rename from host/tests/watchdog.rs rename to host/tests/it/watchdog.rs index eb25878..5cc1a6a 100644 --- a/host/tests/watchdog.rs +++ b/host/tests/it/watchdog.rs @@ -5,7 +5,7 @@ use std::{ process::Command, time::{Duration, Instant}, }; -const BUNDLE: &str = include_str!("../js/watchdog-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/watchdog-test-bundle.js"); static PREPARE: std::sync::Mutex<()> = std::sync::Mutex::new(()); fn prepare(dir: &Path) -> anyhow::Result<()> { let _guard = PREPARE.lock().unwrap(); @@ -15,28 +15,7 @@ fn prepare(dir: &Path) -> anyhow::Result<()> { return Ok(()); } let source = Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); - let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); - let userland = source.join("userland"); - if !userland.join("cwd-init/init.o").exists() { - assert!( - Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success() - ); - } - assert!( - Command::new("cargo") - .args(["build", "--release", "-p", "git", "--manifest-path"]) - .arg(userland.join("Cargo.toml")) - .args(["--target", "wasm32-wasip2", "--target-dir"]) - .arg(target) - .args(["-j", "2", "--locked"]) - .current_dir(&userland) - .status()? - .success() - ); + let target = crate::common::build_git(); std::fs::create_dir_all(disk.join("bin"))?; std::fs::copy( target.join("wasm32-wasip2/release/git.wasm"), @@ -150,7 +129,7 @@ fn sigkill_during_durable_wait() -> anyhow::Result<()> { prepare(dir.path())?; let spawn = |mode: &str| { Command::new(std::env::current_exe().unwrap()) - .args(["--exact", "restart_child", "--nocapture"]) + .args(["--exact", "watchdog::restart_child", "--nocapture"]) .env("WATCHDOG_DIR", dir.path()) .env("WATCHDOG_MODE", mode) .spawn() diff --git a/host/tests/workspaces.rs b/host/tests/it/workspaces.rs similarity index 89% rename from host/tests/workspaces.rs rename to host/tests/it/workspaces.rs index f51154c..f05bdd8 100644 --- a/host/tests/workspaces.rs +++ b/host/tests/it/workspaces.rs @@ -7,7 +7,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -const BUNDLE: &str = include_str!("../js/workspaces-test-bundle.js"); +const BUNDLE: &str = include_str!("../../js/workspaces-test-bundle.js"); fn git(path: &Path, args: &[&str]) -> anyhow::Result<()> { assert!( Command::new("git") @@ -29,37 +29,9 @@ fn prepare(dir: &Path) -> anyhow::Result<()> { return Ok(()); } let source = Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); - let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); - let userland = source.join("userland"); - if !userland.join("cwd-init/init.o").exists() { - assert!( - Command::new("nu") - .args(["--no-config-file", "cwd-init/build.nu"]) - .current_dir(&userland) - .status()? - .success() - ); - } - assert!( - Command::new("cargo") - .args(["build", "--release", "-p", "git", "--manifest-path"]) - .arg(userland.join("Cargo.toml")) - .args(["--target", "wasm32-wasip2", "--target-dir"]) - .arg(target) - .args(["-j", "2", "--locked"]) - .current_dir(&userland) - .status()? - .success() - ); + let target = crate::common::build_git(); if !target.join("wasm32-wasip2/release/brush.wasm").exists() { - assert!( - Command::new("nu") - .arg("brush/build.nu") - .env("CARGO_TARGET_DIR", target) - .current_dir(&userland) - .status()? - .success() - ); + assert!(crate::common::build_brush()?.success()); } std::fs::create_dir_all(disk.join("bin"))?; for (binary, name) in [("git", "git"), ("brush", "sh")] { @@ -254,7 +226,7 @@ fn sigkill_between_fast_forward_and_push() -> anyhow::Result<()> { let _server = serve(dir.path())?; let spawn = |mode: &str| { Command::new(std::env::current_exe().unwrap()) - .args(["--exact", "restart_child", "--nocapture"]) + .args(["--exact", "workspaces::restart_child", "--nocapture"]) .env("WORKSPACES_DIR", dir.path()) .env("WORKSPACES_MODE", mode) .spawn()