174 lines
5.8 KiB
Rust
174 lines
5.8 KiB
Rust
//! P1 integration: independent bundle copies 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_copy_and_real_shell() -> anyhow::Result<()> {
|
|
let repo = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..");
|
|
let bundle = repo.join("target/userland/bundle");
|
|
if !bundle.join("bin/sh.wasm").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.wasm", true).await?.size > 0);
|
|
use std::os::unix::fs::MetadataExt;
|
|
for entry in std::fs::read_dir(dir.path().join("world/bin"))? {
|
|
let entry = entry?;
|
|
assert_eq!(entry.path().extension().unwrap(), "wasm");
|
|
assert_eq!(entry.metadata()?.nlink(), 1);
|
|
assert!(!entry.file_type()?.is_symlink());
|
|
}
|
|
assert!(world.root.stat("/usr/local/bin", true).await?.directory);
|
|
let writable = world
|
|
.root
|
|
.open(
|
|
"/bin/forbidden",
|
|
Open {
|
|
write: true,
|
|
create: true,
|
|
..Default::default()
|
|
},
|
|
)
|
|
.await;
|
|
assert!(writable.is_ok());
|
|
let exec = sausage::process::exec(World {
|
|
root: world.root.clone(),
|
|
host_dir: world.host_dir.clone(),
|
|
processes: world.processes.clone(),
|
|
})?;
|
|
use sausage::exec::{Command, Options, Stream};
|
|
for (script, expected) in [
|
|
("command -v ls", "/bin/ls.wasm\n"),
|
|
("ls", "bin"),
|
|
("git --version", "git version"),
|
|
("printf 'a\\n' | xargs echo", "a\n"),
|
|
("env FOO=1 printenv FOO", "1\n"),
|
|
] {
|
|
let mut out = Vec::new();
|
|
let code = exec
|
|
.exec(
|
|
Command::Shell(script.into()),
|
|
Options::default(),
|
|
&mut |stream, bytes| {
|
|
if matches!(stream, Stream::Stdout) {
|
|
out.extend_from_slice(bytes);
|
|
}
|
|
},
|
|
)
|
|
.await?;
|
|
assert_eq!(code, 0, "{script}");
|
|
assert!(String::from_utf8(out)?.contains(expected), "{script}");
|
|
}
|
|
let mut history = Vec::new();
|
|
assert_eq!(
|
|
exec.exec(
|
|
Command::Shell("cd /src/sausage && git log --oneline".into()),
|
|
Options::default(),
|
|
&mut |stream, bytes| {
|
|
if matches!(stream, Stream::Stdout) {
|
|
history.extend_from_slice(bytes);
|
|
}
|
|
},
|
|
)
|
|
.await?,
|
|
0,
|
|
);
|
|
let history = String::from_utf8(history)?;
|
|
assert_eq!(history.lines().count(), 1);
|
|
assert!(history.contains("Bundle source "));
|
|
let mut status = Vec::new();
|
|
assert_eq!(
|
|
exec.exec(
|
|
Command::Shell("cd /src/sausage && git status --short".into()),
|
|
Options::default(),
|
|
&mut |stream, bytes| {
|
|
if matches!(stream, Stream::Stdout) {
|
|
status.extend_from_slice(bytes);
|
|
}
|
|
},
|
|
)
|
|
.await?,
|
|
0,
|
|
);
|
|
assert!(status.is_empty(), "{}", String::from_utf8_lossy(&status));
|
|
let source = format!(
|
|
"globalThis.testP1 = true;\n{}",
|
|
include_str!("../js/test-bundle.js")
|
|
);
|
|
run_module(world, exec.clone(), &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);
|
|
let source_cat = std::fs::read(bundle.join("bin/cat.wasm"))?;
|
|
let head = std::fs::read(dir.path().join("world/bin/head.wasm"))?;
|
|
let mut out = Vec::new();
|
|
assert_eq!(
|
|
exec.exec(
|
|
Command::Shell(
|
|
"cp /bin/true.wasm /bin/cat.wasm; cat /test.txt; head -n 1 /test.txt".into()
|
|
),
|
|
Options::default(),
|
|
&mut |stream, bytes| {
|
|
if matches!(stream, Stream::Stdout) {
|
|
out.extend_from_slice(bytes);
|
|
}
|
|
}
|
|
)
|
|
.await?,
|
|
0
|
|
);
|
|
assert_eq!(out, b"after\n");
|
|
assert_eq!(std::fs::read(bundle.join("bin/cat.wasm"))?, source_cat);
|
|
assert_eq!(std::fs::read(dir.path().join("world/bin/head.wasm"))?, head);
|
|
assert_eq!(
|
|
exec.exec(
|
|
Command::Shell("cp /bin/true.wasm /usr/local/bin/head.wasm; head /test.txt".into()),
|
|
Options::default(),
|
|
&mut |_, bytes| assert!(bytes.is_empty())
|
|
)
|
|
.await?,
|
|
0
|
|
);
|
|
let changed = std::fs::read(dir.path().join("world/bin/cat.wasm"))?;
|
|
let reopened = World::open_with_userland(
|
|
dir.path(),
|
|
Some(std::path::Path::new("/nonexistent-bundle")),
|
|
)?;
|
|
assert_eq!(
|
|
reopened
|
|
.root
|
|
.open("/bin/cat.wasm", Open::default())
|
|
.await?
|
|
.read(0, changed.len())
|
|
.await?,
|
|
changed
|
|
);
|
|
// 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(())
|
|
}
|