first commit

This commit is contained in:
2026-08-14 23:41:57 +08:00
commit 086803a8dd
471 changed files with 91938 additions and 0 deletions
@@ -0,0 +1,152 @@
// Tests for the file-snapshot rewind journal: per-turn dedup of recorded paths,
// commit grouping (an untouched turn produces no restore point), and restore
// semantics — reverse replay across turns rolls a file to its oldest baseline,
// files that did not exist are deleted, and the journal is truncated to the tip.
package agenttool
import (
"os"
"path/filepath"
"testing"
)
func writeFileT(t *testing.T, path, content string) {
t.Helper()
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("write %s: %v", path, err)
}
}
func readFileT(t *testing.T, path string) string {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
return string(data)
}
// A turn's first Record for a path is the baseline; later Records that turn are
// ignored, and Commit groups the turn's files into one point.
func TestRecorderRecordDedupAndCommit(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "a.txt")
writeFileT(t, f, "v0")
r := NewFileSnapshotRecorder()
r.Record(f) // baseline "v0"
writeFileT(t, f, "v1") // model's first edit
r.Record(f) // second edit same turn — must be ignored
writeFileT(t, f, "v2")
if !r.Commit("leaf0", "edit a") {
t.Fatal("Commit reported no restore point despite a recorded file")
}
// An untouched turn creates nothing.
if r.Commit("leaf1", "no edits") {
t.Fatal("Commit created a restore point for a turn with no records")
}
points := r.Points()
if len(points) != 1 || len(points[0].Snapshots) != 1 {
t.Fatalf("want 1 point with 1 snapshot, got %+v", points)
}
if got := string(points[0].Snapshots[0].Content); got != "v0" {
t.Errorf("baseline content = %q, want v0", got)
}
}
// Restoring rolls files back and deletes ones that did not exist before, and
// returns the pre-turn leaf id.
func TestRecorderRestore(t *testing.T) {
dir := t.TempDir()
existing := filepath.Join(dir, "keep.txt")
created := filepath.Join(dir, "new.txt")
writeFileT(t, existing, "orig")
r := NewFileSnapshotRecorder()
// Turn 1: edit an existing file.
r.Record(existing)
writeFileT(t, existing, "edited")
r.Commit("leafA", "turn1")
// Turn 2: create a brand-new file.
r.Record(created)
writeFileT(t, created, "brand new")
r.Commit("leafB", "turn2")
// Rewind to before turn 1 (index 0): both turns roll back.
leaf, restored, warnings, err := r.Restore(0)
if err != nil {
t.Fatalf("Restore: %v", err)
}
if leaf != "leafA" {
t.Errorf("target leaf = %q, want leafA", leaf)
}
if len(warnings) != 0 {
t.Errorf("unexpected warnings: %v", warnings)
}
if len(restored) != 2 {
t.Errorf("restored %d files, want 2", len(restored))
}
if got := readFileT(t, existing); got != "orig" {
t.Errorf("existing file = %q, want orig", got)
}
if _, err := os.Stat(created); !os.IsNotExist(err) {
t.Errorf("created file should have been deleted, stat err = %v", err)
}
if len(r.Points()) != 0 {
t.Errorf("journal should be empty after restoring from index 0")
}
}
// When a file is edited across several turns, restoring to before the earliest
// of them lands it at its oldest baseline (not an intermediate version).
func TestRecorderRestoreOldestBaselineWins(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "a.txt")
writeFileT(t, f, "v0")
r := NewFileSnapshotRecorder()
r.Record(f) // baseline v0
writeFileT(t, f, "v1")
r.Commit("leaf0", "t1")
r.Record(f) // baseline v1
writeFileT(t, f, "v2")
r.Commit("leaf1", "t2")
// Rewind to before t2 only (index 1): file returns to v1.
if _, _, _, err := r.Restore(1); err != nil {
t.Fatalf("Restore(1): %v", err)
}
if got := readFileT(t, f); got != "v1" {
t.Errorf("after rewind to before t2, file = %q, want v1", got)
}
// One point remains (t1); rewind it too → v0.
if _, _, _, err := r.Restore(0); err != nil {
t.Fatalf("Restore(0): %v", err)
}
if got := readFileT(t, f); got != "v0" {
t.Errorf("after full rewind, file = %q, want v0", got)
}
}
func TestRecorderRestoreOutOfRange(t *testing.T) {
r := NewFileSnapshotRecorder()
if _, _, _, err := r.Restore(0); err == nil {
t.Error("Restore on empty journal should error")
}
}
// A nil recorder is a safe no-op so tools can hold an optional handle.
func TestRecorderNilSafe(t *testing.T) {
var r *FileSnapshotRecorder
r.Record("/nonexistent")
if r.Commit("x", "y") {
t.Error("nil Commit should report no point")
}
if r.Points() != nil {
t.Error("nil Points should be nil")
}
}