//! 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, 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(); crate::common::cwd_init(); 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!(crate::common::build_brush().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 = 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 = serde_json::from_str(&listed).unwrap(); assert_eq!( entries.iter().map(|e| e.tag.as_str()).collect::>(), ["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"); } } }