- userland/bundle.nu builds the userland and assembles target/userland/bin (bin names, tools.txt aliases, brush as sh and bash). - run and sh take --userland (default SAUSAGE_USERLAND); /bin is read-only. - The console's bash tool runs through the process table with the conversation id; large output spills to the world's /tmp. - sausage run starts the services supervisor. P1 exit session (openai/gpt-6.1-sol, fresh world): write, read, edit, git init/add/commit/diff/log, grep -rn, rg, find and a sed|sort|uniq|wc pipeline.
68 lines
2.4 KiB
Rust
68 lines
2.4 KiB
Rust
//! P1 integration: a read-only bundle and real shell calls from the durable harness.
|
|
use sausage::{harness::run_module, world::World};
|
|
use sausage_vfs::Open;
|
|
use std::path::PathBuf;
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn bundle_mount_and_real_shell() -> anyhow::Result<()> {
|
|
let repo = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..");
|
|
let bundle = repo.join("target/userland/bin");
|
|
if !bundle.join("sh").exists() {
|
|
assert!(
|
|
std::process::Command::new("nu")
|
|
.env_remove("CARGO_TARGET_DIR")
|
|
.arg(repo.join("userland/bundle.nu"))
|
|
.status()?
|
|
.success()
|
|
);
|
|
}
|
|
let dir = tempfile::tempdir()?;
|
|
let world = World::open_with_userland(dir.path(), Some(&bundle))?;
|
|
assert!(world.root.stat("/bin/sh", true).await?.size > 0);
|
|
assert!(world.root.stat("/usr/local/bin", true).await?.directory);
|
|
let denied = world
|
|
.root
|
|
.open(
|
|
"/bin/forbidden",
|
|
Open {
|
|
write: true,
|
|
create: true,
|
|
..Default::default()
|
|
},
|
|
)
|
|
.await;
|
|
assert!(matches!(denied, Err(e) if e.kind() == std::io::ErrorKind::ReadOnlyFilesystem));
|
|
let exec = sausage::process::exec(World {
|
|
root: world.root.clone(),
|
|
host_dir: world.host_dir.clone(),
|
|
processes: world.processes.clone(),
|
|
})?;
|
|
let source = format!(
|
|
"globalThis.testP1 = true;\n{}",
|
|
include_str!("../js/test-bundle.js")
|
|
);
|
|
run_module(world, exec, &source).await?;
|
|
assert_eq!(
|
|
std::fs::read_to_string(dir.path().join("world/test.txt"))?,
|
|
"after\n"
|
|
);
|
|
assert_eq!(
|
|
std::fs::read_to_string(dir.path().join("world/parallel/a"))?,
|
|
"a"
|
|
);
|
|
assert_eq!(
|
|
std::fs::read_to_string(dir.path().join("world/parallel/b"))?,
|
|
"b"
|
|
);
|
|
let spills = std::fs::read_dir(dir.path().join("world/tmp"))?.collect::<Result<Vec<_>, _>>()?;
|
|
assert_eq!(spills.len(), 1);
|
|
let actual = std::fs::read_to_string(spills[0].path())?;
|
|
let expected: String = (1..=10000).map(|n| format!("{n}\n")).collect();
|
|
assert_eq!(actual, expected);
|
|
// An unbundled world remains usable by process tests and file-only callers.
|
|
let unbundled = tempfile::tempdir()?;
|
|
let world = World::open(unbundled.path())?;
|
|
assert!(world.root.stat("/bin/sh", true).await.is_err());
|
|
Ok(())
|
|
}
|