Files
sausage/host/tests/it/undo_exec.rs
T

134 lines
4.8 KiB
Rust

//! Real guest mutations share tool-call tags and restore a committed repository.
use sausage::{
exec::{Command, Exec, Options, Stream},
process::Runtime,
world::World,
};
use sausage_vfs::journal::Outcome;
use std::{path::PathBuf, sync::Arc};
async fn run(runtime: &Arc<Runtime>, id: &str, argv: &[&str]) -> String {
let mut out = Vec::new();
let mut err = Vec::new();
let code = runtime
.exec(
Command::Argv(argv.iter().map(|s| s.to_string()).collect()),
Options {
cwd: "/repo".into(),
tool_call: Some(id.into()),
conversations: vec!["conversation".into()],
..Default::default()
},
&mut |stream, bytes| match stream {
Stream::Stdout => out.extend_from_slice(bytes),
Stream::Stderr => err.extend_from_slice(bytes),
},
)
.await
.unwrap();
assert_eq!(code, 0, "{}", String::from_utf8_lossy(&err));
String::from_utf8(out).unwrap()
}
#[tokio::test(flavor = "multi_thread")]
async fn tool_call_commit_and_independent_change() {
let target = PathBuf::from(
std::env::var("CARGO_TARGET_DIR").expect("set the executor target directory"),
);
let guest_dir = target.join("wasm32-wasip2/release");
if !guest_dir.join("git.wasm").is_file()
|| !guest_dir.join("cp.wasm").is_file()
|| !guest_dir.join("touch.wasm").is_file()
|| !guest_dir.join("undo.wasm").is_file()
{
crate::common::cwd_init();
assert!(
std::process::Command::new("cargo")
.args([
"build",
"--release",
"-p",
"git",
"-p",
"sausage-coreutils",
"-p",
"undo"
])
.current_dir(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../userland"))
.status()
.unwrap()
.success()
);
}
let temp = tempfile::tempdir().unwrap();
let world = World::open(temp.path()).unwrap();
let root = temp.path().join("world");
std::fs::create_dir_all(root.join("bin")).unwrap();
std::fs::create_dir_all(root.join("repo")).unwrap();
for (binary, guest) in [
("git", "git"),
("cp", "cp"),
("touch", "touch"),
("undo", "undo"),
] {
std::fs::copy(
target.join(format!("wasm32-wasip2/release/{binary}.wasm")),
root.join(format!("bin/{guest}")),
)
.unwrap();
}
let git = |args: &[&str]| {
let output = std::process::Command::new("git")
.args(args)
.current_dir(root.join("repo"))
.output()
.unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout).unwrap()
};
git(&["init", "-b", "main"]);
git(&["config", "user.name", "Test"]);
git(&["config", "user.email", "test@example.com"]);
std::fs::write(root.join("repo/file"), "old\n").unwrap();
git(&["add", "file"]);
git(&["commit", "--no-gpg-sign", "-m", "initial"]);
let before = git(&["log", "--oneline"]);
std::fs::write(root.join("input"), "new\n").unwrap();
let runtime = Runtime::new(world).unwrap();
run(&runtime, "A", &["/bin/cp", "/input", "file"]).await;
run(&runtime, "A", &["/bin/git", "add", "file"]).await;
run(&runtime, "A", &["/bin/git", "commit", "-m", "change"]).await;
run(&runtime, "B", &["/bin/touch", "independent"]).await;
let entries = run(
&runtime,
"list",
&["/bin/undo", "list", "--conversation", "conversation"],
)
.await;
let entries: Vec<sausage_vfs::journal::Entry> = serde_json::from_str(&entries).unwrap();
assert!(entries.iter().any(|entry| entry.tag == "A"));
let snapshots = run(&runtime, "show", &["/bin/undo", "show", "A"]).await;
let snapshots: Vec<sausage_vfs::journal::Snapshot> = serde_json::from_str(&snapshots).unwrap();
assert!(
snapshots
.iter()
.any(|snapshot| snapshot.path == "/repo/file")
);
let output = run(&runtime, "undo-call", &["/bin/undo", "A"]).await;
let outcome: Outcome = serde_json::from_str(&output).unwrap();
assert!(outcome.conflicts.is_empty(), "{:?}", outcome);
assert!(outcome.tag.is_some());
assert_eq!(std::fs::read(root.join("repo/file")).unwrap(), b"old\n");
assert!(root.join("repo/independent").is_file());
assert_eq!(
run(&runtime, "inspect", &["/bin/git", "log", "--oneline"]).await,
before
);
assert_eq!(
run(&runtime, "inspect", &["/bin/git", "status", "--short"]).await,
"?? independent\n"
);
}