187 lines
6.4 KiB
Rust
187 lines
6.4 KiB
Rust
//! Faux-model coding calls use the real world journal, shell, Git, and undo CLI.
|
|
use sausage::{
|
|
exec::{Command, Exec, Options},
|
|
process::Runtime,
|
|
world::World,
|
|
};
|
|
use sausage_vfs::journal::{Entry, Outcome};
|
|
use std::{
|
|
path::{Path, PathBuf},
|
|
sync::Arc,
|
|
};
|
|
fn git(repo: &Path, args: &[&str]) -> String {
|
|
let output = std::process::Command::new("git")
|
|
.args(args)
|
|
.current_dir(repo)
|
|
.output()
|
|
.unwrap();
|
|
assert!(
|
|
output.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
String::from_utf8(output.stdout).unwrap()
|
|
}
|
|
async fn command(runtime: &Arc<Runtime>, args: &[&str]) -> String {
|
|
let mut bytes = Vec::new();
|
|
let code = runtime
|
|
.exec(
|
|
Command::Argv(args.iter().map(|s| s.to_string()).collect()),
|
|
Options {
|
|
cwd: "/repo".into(),
|
|
..Default::default()
|
|
},
|
|
&mut |_, b| bytes.extend_from_slice(b),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(code, 0, "{}", String::from_utf8_lossy(&bytes));
|
|
String::from_utf8(bytes).unwrap()
|
|
}
|
|
#[tokio::test(flavor = "current_thread")]
|
|
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()
|
|
);
|
|
assert!(
|
|
std::process::Command::new("cargo")
|
|
.args([
|
|
"build",
|
|
"--release",
|
|
"--locked",
|
|
"--target",
|
|
"wasm32-wasip2",
|
|
"-p",
|
|
"git",
|
|
"-p",
|
|
"undo",
|
|
"--target-dir"
|
|
])
|
|
.arg(&target)
|
|
.args(["-j", "2"])
|
|
.current_dir(repo.join("userland"))
|
|
.status()
|
|
.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()
|
|
);
|
|
for since in [false, true] {
|
|
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 (source, name) in [("brush", "sh"), ("git", "git"), ("undo", "undo")] {
|
|
std::fs::copy(
|
|
target.join(format!("wasm32-wasip2/release/{source}.wasm")),
|
|
root.join(format!("bin/{name}")),
|
|
)
|
|
.unwrap();
|
|
}
|
|
let repo = root.join("repo");
|
|
git(&repo, &["init", "-b", "main"]);
|
|
git(&repo, &["config", "user.name", "Test"]);
|
|
git(&repo, &["config", "user.email", "test@example.com"]);
|
|
for name in ["a.txt", "b.txt"] {
|
|
std::fs::write(repo.join(name), "old\n").unwrap();
|
|
}
|
|
git(&repo, &["add", "."]);
|
|
git(&repo, &["commit", "--no-gpg-sign", "-m", "initial"]);
|
|
let before = git(&repo, &["log", "--oneline"]);
|
|
let runtime = Runtime::new(World {
|
|
root: world.root.clone(),
|
|
host_dir: world.host_dir.clone(),
|
|
processes: world.processes.clone(),
|
|
})
|
|
.unwrap();
|
|
sausage::harness::run_module(
|
|
world,
|
|
Arc::new(runtime.clone()),
|
|
include_str!("../js/undo-harness-test-bundle.js"),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
let extensions: Vec<Entry> = serde_json::from_str(
|
|
&command(
|
|
&runtime,
|
|
&["/bin/undo", "list", "--conversation", "extension"],
|
|
)
|
|
.await,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(extensions.len(), 2);
|
|
assert_eq!(extensions[0].tag, "extension-call");
|
|
assert_eq!(extensions[1].tag, "extension:test-extension:background");
|
|
assert_eq!(extensions[1].program, "extension:test-extension");
|
|
assert_eq!(extensions[1].changed_paths, ["/extension.txt"]);
|
|
let listed = command(&runtime, &["/bin/undo", "list", "--conversation", "1"]).await;
|
|
let entries: Vec<Entry> = serde_json::from_str(&listed).unwrap();
|
|
assert_eq!(
|
|
entries.iter().map(|e| e.tag.as_str()).collect::<Vec<_>>(),
|
|
["third", "second", "first"]
|
|
);
|
|
for (tag, tool, path) in [
|
|
("first", "edit", "/repo/a.txt"),
|
|
("second", "bash", "/repo/b.txt"),
|
|
("third", "write", "/repo/c.txt"),
|
|
] {
|
|
let entry = entries.iter().find(|e| e.tag == tag).unwrap();
|
|
assert_eq!(entry.program, tool);
|
|
assert!(entry.changed_paths.contains(&path.to_string()));
|
|
}
|
|
assert!(
|
|
entries[1]
|
|
.changed_paths
|
|
.iter()
|
|
.any(|p| p.starts_with("/repo/.git/"))
|
|
);
|
|
let first = entries.last().unwrap().entry.to_string();
|
|
let args = if since {
|
|
vec!["/bin/undo", "--since", &first]
|
|
} else {
|
|
vec!["/bin/undo", "second"]
|
|
};
|
|
let restored: Outcome = serde_json::from_str(&command(&runtime, &args).await).unwrap();
|
|
assert!(restored.conflicts.is_empty());
|
|
assert!(restored.tag.is_some());
|
|
assert_eq!(
|
|
std::fs::read_to_string(repo.join("b.txt")).unwrap(),
|
|
"old\n"
|
|
);
|
|
assert_eq!(git(&repo, &["log", "--oneline"]), before);
|
|
if since {
|
|
assert_eq!(
|
|
std::fs::read_to_string(repo.join("a.txt")).unwrap(),
|
|
"old\n"
|
|
);
|
|
assert!(!repo.join("c.txt").exists());
|
|
assert_eq!(git(&repo, &["status", "--short"]), "");
|
|
} else {
|
|
assert_eq!(
|
|
std::fs::read_to_string(repo.join("a.txt")).unwrap(),
|
|
"edited\n"
|
|
);
|
|
assert_eq!(
|
|
std::fs::read_to_string(repo.join("c.txt")).unwrap(),
|
|
"written\n"
|
|
);
|
|
assert_eq!(git(&repo, &["status", "--short"]), " M a.txt\n?? c.txt\n");
|
|
}
|
|
}
|
|
}
|