Files
zero/internal/tools/format_on_write_test.go
KRATOS faec8ee201 feat(acp): emit structured file changes in tool updates (#999)
* feat(tools): retain bounded structured file diffs

* feat(acp): emit structured file changes in tool updates

* fix(acp): harden structured file diff transport

* fix(tools): reject unsafe rich diff previews

* fix(acp): preserve safe rich diff semantics

* fix(tools): bind structured diffs to committed changes

* fix(acp): preserve structured diff identity

* fix(tools): close rich diff race gaps

* fix(tools): report partial patch paths from workspace

* fix(tools): finalize structured diff evidence

* fix(tools): separate unverified patch writes

* test(tools): cover failed formatter recovery

* fix(acp): serialize file deletions as nullable diffs

* fix(tools): recreate files after formatter failure

* test(acp): require nullable diff fields on wire
2026-09-15 08:31:39 +08:00

534 lines
20 KiB
Go

package tools
import (
"context"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)
// gofmt ships with the Go toolchain, so it is the one formatter guaranteed to
// exist wherever these tests run.
func requireGofmt(t *testing.T) {
t.Helper()
if _, err := exec.LookPath("gofmt"); err != nil {
t.Skip("gofmt not on PATH")
}
}
func TestFormatOnWriteDisabledByDefault(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "")
dir := t.TempDir()
ugly := "package a\n\nfunc A( ) { }\n"
result := NewScopedWriteFileTool(dir, nil).(optionsAwareTool).RunWithOptions(context.Background(), map[string]any{
"path": "a.go",
"content": ugly,
}, RunOptions{})
if result.Status != StatusOK {
t.Fatalf("write failed: %q", result.Output)
}
onDisk, err := os.ReadFile(filepath.Join(dir, "a.go"))
if err != nil {
t.Fatal(err)
}
if string(onDisk) != ugly {
t.Fatalf("formatting must be off by default, got %q", onDisk)
}
}
func TestFormatOnWriteFormatsAndKeepsTrackerConsistent(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
dir := t.TempDir()
tracker := NewFileTracker()
write := NewScopedWriteFileTool(dir, nil).(optionsAwareTool).RunWithOptions(context.Background(), map[string]any{
"path": "a.go",
"content": "package a\n\nfunc A( ) { }\n",
}, RunOptions{FileTracker: tracker})
if write.Status != StatusOK {
t.Fatalf("write failed: %q", write.Output)
}
onDisk, err := os.ReadFile(filepath.Join(dir, "a.go"))
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(onDisk), "func A() {") {
t.Fatalf("expected gofmt-formatted content, got %q", onDisk)
}
// The tracker is re-baselined to the post-format bytes, but those bytes were
// not returned exactly to the model, so an edit must require an exact read.
edit := NewScopedEditFileTool(dir, nil).(optionsAwareTool).RunWithOptions(context.Background(), map[string]any{
"path": "a.go",
"old_string": "func A() {",
"new_string": "func B() {",
}, RunOptions{FileTracker: tracker})
if edit.Status != StatusError || !strings.Contains(edit.Output, "has not been read exactly") {
t.Fatalf("formatter-modified content must require an exact read: %q", edit.Output)
}
read := NewScopedReadFileTool(dir, nil).(optionsAwareTool).RunWithOptions(context.Background(), map[string]any{
"path": "a.go",
}, RunOptions{FileTracker: tracker})
if read.Status != StatusOK {
t.Fatalf("exact read failed: %q", read.Output)
}
edit = NewScopedEditFileTool(dir, nil).(optionsAwareTool).RunWithOptions(context.Background(), map[string]any{
"path": "a.go",
"old_string": "func A() {",
"new_string": "func B( ) {",
}, RunOptions{FileTracker: tracker})
if edit.Status != StatusOK {
t.Fatalf("follow-up edit after exact read failed: %q", edit.Output)
}
edit = NewScopedEditFileTool(dir, nil).(optionsAwareTool).RunWithOptions(context.Background(), map[string]any{
"path": "a.go",
"old_string": "func B() {",
"new_string": "func C() {",
}, RunOptions{FileTracker: tracker})
if edit.Status != StatusError || !strings.Contains(edit.Output, "has not been read exactly") {
t.Fatalf("formatter-modified edit must require an exact read: %q", edit.Output)
}
}
func TestFormatOnWriteSkipsUnknownExtensions(t *testing.T) {
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
formatting := maybeFormatWrittenFile(context.Background(), filepath.Join(t.TempDir(), "notes.xyz"), "raw text")
if formatting.Content != "raw text" || !formatting.ContentKnown {
t.Fatalf("unknown extension must pass through: %q", formatting.Content)
}
if notice := formatting.notice("notes.xyz"); notice != "" {
t.Fatalf("an extension with no formatter is not a miss worth reporting, got %q", notice)
}
}
func TestFormatOnWriteFormatterLookupFailure(t *testing.T) {
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
t.Setenv("PATH", t.TempDir())
targetPath := filepath.Join(t.TempDir(), "a.go")
uglyContent := "package a\n\nfunc A( ) { }\n"
if err := os.WriteFile(targetPath, []byte(uglyContent), 0o644); err != nil {
t.Fatal(err)
}
formatting := maybeFormatWrittenFile(context.Background(), targetPath, uglyContent)
if formatting.Content != uglyContent || !formatting.ContentKnown {
t.Fatalf("missing formatter must return written content, got %q", formatting.Content)
}
if notice := formatting.notice("a.go"); notice != "" {
t.Fatalf("an uninstalled formatter is a standing fact, not a miss worth reporting, got %q", notice)
}
}
func TestFormatOnWriteReadsMutatedFileAfterFormatterFailure(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
targetPath := filepath.Join(t.TempDir(), "a.go")
if err := os.WriteFile(targetPath, []byte("requested"), 0o644); err != nil {
t.Fatal(err)
}
priorRunner := runFormatOnWriteCommand
runFormatOnWriteCommand = func(_ context.Context, _ string, _ []string, _ string) error {
if err := os.WriteFile(targetPath, []byte("formatter-mutated"), 0o644); err != nil {
t.Fatal(err)
}
return exec.ErrNotFound
}
t.Cleanup(func() { runFormatOnWriteCommand = priorRunner })
formatting := maybeFormatWrittenFile(context.Background(), targetPath, "requested")
if !formatting.ContentKnown || formatting.Content != "requested" {
t.Fatalf("formatter failure content = %q, known=%t", formatting.Content, formatting.ContentKnown)
}
}
func TestFormatOnWriteReportsWhenFormatterFailureCannotBeRestored(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
targetPath := filepath.Join(t.TempDir(), "a.go")
if err := os.WriteFile(targetPath, []byte("requested"), 0o644); err != nil {
t.Fatal(err)
}
priorRunner := runFormatOnWriteCommand
runFormatOnWriteCommand = func(_ context.Context, _ string, _ []string, _ string) error {
if err := os.Remove(targetPath); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(targetPath, 0o755); err != nil {
t.Fatal(err)
}
return exec.ErrNotFound
}
t.Cleanup(func() { runFormatOnWriteCommand = priorRunner })
formatting := maybeFormatWrittenFile(context.Background(), targetPath, "requested")
if !formatting.RestoreFailed {
t.Fatal("failed recovery was not reported")
}
if formatting.ContentKnown {
t.Fatalf("failed recovery reported known content %q", formatting.Content)
}
if notice := formatting.notice("a.go"); !strings.Contains(notice, "WARNING: a.go may not hold what was written") {
t.Fatalf("failed recovery notice = %q", notice)
}
}
func TestWriteFileRecreatesFormatterDeletedTargetWithOriginalMode(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
for _, tc := range []struct {
name string
existing bool
wantMode os.FileMode
overwrite bool
}{
{name: "create", wantMode: 0o644},
{name: "overwrite", existing: true, wantMode: 0o751, overwrite: true},
} {
t.Run(tc.name, func(t *testing.T) {
root := t.TempDir()
targetPath := filepath.Join(root, "a.go")
if tc.existing {
if err := os.WriteFile(targetPath, []byte("before\n"), tc.wantMode); err != nil {
t.Fatal(err)
}
}
priorRunner := runFormatOnWriteCommand
runFormatOnWriteCommand = func(_ context.Context, _ string, _ []string, _ string) error {
if err := os.Remove(targetPath); err != nil {
t.Fatal(err)
}
return exec.ErrNotFound
}
t.Cleanup(func() { runFormatOnWriteCommand = priorRunner })
result := NewScopedWriteFileTool(root, nil).Run(context.Background(), map[string]any{
"path": "a.go", "content": "requested\n", "overwrite": tc.overwrite,
})
if result.Status != StatusOK {
t.Fatalf("write status = %s: %s", result.Status, result.Output)
}
content, err := os.ReadFile(targetPath)
if err != nil || string(content) != "requested\n" {
t.Fatalf("recreated content = %q, err=%v", content, err)
}
if runtime.GOOS != "windows" {
info, err := os.Stat(targetPath)
if err != nil {
t.Fatal(err)
}
if got := info.Mode().Perm(); got != tc.wantMode {
t.Fatalf("recreated mode = %o, want %o", got, tc.wantMode)
}
}
})
}
}
func TestFormatOnWriteMarksUnreadableFinalStateUnknown(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
targetPath := filepath.Join(t.TempDir(), "a.go")
if err := os.WriteFile(targetPath, []byte("requested"), 0o644); err != nil {
t.Fatal(err)
}
priorRunner := runFormatOnWriteCommand
priorReader := readFormattedFile
runFormatOnWriteCommand = func(context.Context, string, []string, string) error { return nil }
readFormattedFile = func(*os.Root, string) ([]byte, os.FileInfo, error) { return nil, nil, os.ErrPermission }
t.Cleanup(func() {
runFormatOnWriteCommand = priorRunner
readFormattedFile = priorReader
})
formatting := maybeFormatWrittenFile(context.Background(), targetPath, "requested")
if formatting.ContentKnown || formatting.Content != "requested" {
t.Fatalf("unreadable formatter result = %q, known=%t", formatting.Content, formatting.ContentKnown)
}
}
func TestWriteFileUsesVerifiedBytesAfterFormatterFailure(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
root := t.TempDir()
targetPath := filepath.Join(root, "a.go")
priorRunner := runFormatOnWriteCommand
runFormatOnWriteCommand = func(_ context.Context, _ string, _ []string, _ string) error {
if err := os.WriteFile(targetPath, []byte("formatter-mutated\n"), 0o644); err != nil {
t.Fatal(err)
}
return exec.ErrNotFound
}
t.Cleanup(func() { runFormatOnWriteCommand = priorRunner })
result := NewScopedWriteFileTool(root, nil).Run(context.Background(), map[string]any{
"path": "a.go", "content": "requested\n",
})
if result.Status != StatusOK {
t.Fatalf("write status = %s: %s", result.Status, result.Output)
}
if got := result.FileDiffs; len(got) != 1 || got[0].NewText != "requested\n" {
t.Fatalf("formatter-failure FileDiff = %#v", got)
}
}
func TestEditFileUsesVerifiedBytesAfterFormatterFailure(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
root := t.TempDir()
targetPath := filepath.Join(root, "a.go")
if err := os.WriteFile(targetPath, []byte("before\n"), 0o644); err != nil {
t.Fatal(err)
}
priorRunner := runFormatOnWriteCommand
runFormatOnWriteCommand = func(_ context.Context, _ string, _ []string, _ string) error {
if err := os.WriteFile(targetPath, []byte("formatter-mutated\n"), 0o644); err != nil {
t.Fatal(err)
}
return exec.ErrNotFound
}
t.Cleanup(func() { runFormatOnWriteCommand = priorRunner })
result := NewScopedEditFileTool(root, nil).Run(context.Background(), map[string]any{
"path": "a.go", "old_string": "before", "new_string": "requested",
})
if result.Status != StatusOK {
t.Fatalf("edit status = %s: %s", result.Status, result.Output)
}
if got := result.FileDiffs; len(got) != 1 || got[0].OldText != "before\n" || got[0].NewText != "requested\n" {
t.Fatalf("formatter-failure edit FileDiff = %#v", got)
}
}
func TestWriteFileOmitsRichDiffWhenFormatterFinalReadFails(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
root := t.TempDir()
priorRunner := runFormatOnWriteCommand
priorReader := readFormattedFile
runFormatOnWriteCommand = func(context.Context, string, []string, string) error { return nil }
readFormattedFile = func(*os.Root, string) ([]byte, os.FileInfo, error) { return nil, nil, os.ErrPermission }
t.Cleanup(func() {
runFormatOnWriteCommand = priorRunner
readFormattedFile = priorReader
})
result := NewScopedWriteFileTool(root, nil).Run(context.Background(), map[string]any{
"path": "a.go", "content": "requested\n",
})
if result.Status != StatusOK || len(result.ChangedFiles) != 1 || len(result.FileDiffs) != 0 {
t.Fatalf("unverified formatter result = status=%s changed=%#v diffs=%#v", result.Status, result.ChangedFiles, result.FileDiffs)
}
if result.Display.Preview != "" {
t.Fatalf("unverified formatter result exposed stale preview: %q", result.Display.Preview)
}
}
func TestEditFileOmitsPreviewWhenFormatterFinalReadFails(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
root := t.TempDir()
targetPath := filepath.Join(root, "a.go")
if err := os.WriteFile(targetPath, []byte("before\n"), 0o644); err != nil {
t.Fatal(err)
}
priorRunner := runFormatOnWriteCommand
priorReader := readFormattedFile
runFormatOnWriteCommand = func(context.Context, string, []string, string) error { return nil }
readFormattedFile = func(*os.Root, string) ([]byte, os.FileInfo, error) { return nil, nil, os.ErrPermission }
t.Cleanup(func() {
runFormatOnWriteCommand = priorRunner
readFormattedFile = priorReader
})
result := NewScopedEditFileTool(root, nil).Run(context.Background(), map[string]any{
"path": "a.go", "old_string": "before", "new_string": "requested",
})
if result.Status != StatusOK || len(result.ChangedFiles) != 1 || len(result.FileDiffs) != 0 {
t.Fatalf("unverified formatter result = status=%s changed=%#v diffs=%#v", result.Status, result.ChangedFiles, result.FileDiffs)
}
if result.Display.Preview != "" {
t.Fatalf("unverified formatter result exposed stale preview: %q", result.Display.Preview)
}
}
func TestWriteFileOmitsRichEvidenceWhenFormatterReplacesTargetWithOutOfRootSymlink(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
root := t.TempDir()
targetPath := filepath.Join(root, "a.go")
resolvedRoot, err := filepath.EvalSymlinks(root)
if err != nil {
t.Fatal(err)
}
trackedPath := filepath.Join(resolvedRoot, "a.go")
outsidePath := filepath.Join(t.TempDir(), "outside.go")
outsideContent := "package external\n\nconst Secret = \"outside\"\n"
if err := os.WriteFile(outsidePath, []byte(outsideContent), 0o644); err != nil {
t.Fatal(err)
}
priorRunner := runFormatOnWriteCommand
runFormatOnWriteCommand = func(context.Context, string, []string, string) error {
if err := os.Remove(targetPath); err != nil {
t.Fatal(err)
}
if err := os.Symlink(outsidePath, targetPath); err != nil {
t.Skipf("symlinks unavailable: %v", err)
}
return nil
}
t.Cleanup(func() { runFormatOnWriteCommand = priorRunner })
tracker := NewFileTracker()
diagnosticsCalled := false
result := NewScopedWriteFileTool(root, nil).(optionsAwareTool).RunWithOptions(context.Background(), map[string]any{
"path": "a.go", "content": "package requested\n",
}, RunOptions{FileTracker: tracker, Diagnostics: func(context.Context, string) string {
diagnosticsCalled = true
return "must not run"
}})
if result.Status != StatusOK || len(result.ChangedFiles) != 1 || len(result.FileDiffs) != 0 || result.Display.Preview != "" {
t.Fatalf("out-of-root formatter result = status=%s changed=%#v diffs=%#v preview=%q output=%q", result.Status, result.ChangedFiles, result.FileDiffs, result.Display.Preview, result.Output)
}
if diagnosticsCalled {
t.Fatal("diagnostics must not inspect an unverified formatter target")
}
if _, tracked := tracker.Version(trackedPath); tracked {
t.Fatal("out-of-root formatter target must not be recorded in the tracker")
}
}
func TestEditFileOmitsRichEvidenceWhenFormatterReplacesTargetWithOutOfRootSymlink(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
root := t.TempDir()
targetPath := filepath.Join(root, "a.go")
resolvedRoot, err := filepath.EvalSymlinks(root)
if err != nil {
t.Fatal(err)
}
trackedPath := filepath.Join(resolvedRoot, "a.go")
if err := os.WriteFile(targetPath, []byte("package before\n"), 0o644); err != nil {
t.Fatal(err)
}
outsidePath := filepath.Join(t.TempDir(), "outside.go")
outsideContent := "package external\n\nconst Secret = \"outside\"\n"
if err := os.WriteFile(outsidePath, []byte(outsideContent), 0o644); err != nil {
t.Fatal(err)
}
priorRunner := runFormatOnWriteCommand
runFormatOnWriteCommand = func(context.Context, string, []string, string) error {
if err := os.Remove(targetPath); err != nil {
t.Fatal(err)
}
if err := os.Symlink(outsidePath, targetPath); err != nil {
t.Skipf("symlinks unavailable: %v", err)
}
return nil
}
t.Cleanup(func() { runFormatOnWriteCommand = priorRunner })
tracker := NewFileTracker()
read := NewScopedReadFileTool(root, nil).(optionsAwareTool).RunWithOptions(context.Background(), map[string]any{
"path": "a.go",
}, RunOptions{FileTracker: tracker})
if read.Status != StatusOK {
t.Fatalf("read before edit failed: %s", read.Output)
}
diagnosticsCalled := false
result := NewScopedEditFileTool(root, nil).(optionsAwareTool).RunWithOptions(context.Background(), map[string]any{
"path": "a.go", "old_string": "before", "new_string": "requested",
}, RunOptions{FileTracker: tracker, Diagnostics: func(context.Context, string) string {
diagnosticsCalled = true
return "must not run"
}})
if result.Status != StatusOK || len(result.ChangedFiles) != 1 || len(result.FileDiffs) != 0 || result.Display.Preview != "" {
t.Fatalf("out-of-root formatter result = status=%s changed=%#v diffs=%#v preview=%q output=%q", result.Status, result.ChangedFiles, result.FileDiffs, result.Display.Preview, result.Output)
}
if diagnosticsCalled {
t.Fatal("diagnostics must not inspect an unverified formatter target")
}
if _, tracked := tracker.Version(trackedPath); tracked {
t.Fatal("out-of-root formatter target must not be recorded in the tracker")
}
}
func TestWriteFileAcceptsInRootAtomicFormatterReplacement(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
root := t.TempDir()
targetPath := filepath.Join(root, "a.go")
formatted := "package formatted\n"
priorRunner := runFormatOnWriteCommand
runFormatOnWriteCommand = func(context.Context, string, []string, string) error {
tempPath := filepath.Join(root, "formatter.tmp")
if err := os.WriteFile(tempPath, []byte(formatted), 0o644); err != nil {
t.Fatal(err)
}
return os.Rename(tempPath, targetPath)
}
t.Cleanup(func() { runFormatOnWriteCommand = priorRunner })
tracker := NewFileTracker()
result := NewScopedWriteFileTool(root, nil).(optionsAwareTool).RunWithOptions(context.Background(), map[string]any{
"path": "a.go", "content": "package requested\n",
}, RunOptions{FileTracker: tracker})
if result.Status != StatusOK || len(result.FileDiffs) != 1 || result.FileDiffs[0].NewText != formatted {
t.Fatalf("atomic formatter write = status=%s diffs=%#v output=%q", result.Status, result.FileDiffs, result.Output)
}
resolvedRoot, err := filepath.EvalSymlinks(root)
if err != nil {
t.Fatal(err)
}
version, tracked := tracker.Version(filepath.Join(resolvedRoot, "a.go"))
if !tracked || version.Hash != HashContent([]byte(formatted)) {
t.Fatalf("atomic formatter tracker = %#v, tracked=%t", version, tracked)
}
}
func TestEditFileAcceptsInRootAtomicFormatterReplacement(t *testing.T) {
requireGofmt(t)
t.Setenv("ZERO_FORMAT_ON_WRITE", "1")
root := t.TempDir()
targetPath := filepath.Join(root, "a.go")
if err := os.WriteFile(targetPath, []byte("package before\n"), 0o644); err != nil {
t.Fatal(err)
}
formatted := "package formatted\n"
priorRunner := runFormatOnWriteCommand
runFormatOnWriteCommand = func(context.Context, string, []string, string) error {
tempPath := filepath.Join(root, "formatter.tmp")
if err := os.WriteFile(tempPath, []byte(formatted), 0o644); err != nil {
t.Fatal(err)
}
return os.Rename(tempPath, targetPath)
}
t.Cleanup(func() { runFormatOnWriteCommand = priorRunner })
tracker := NewFileTracker()
read := NewScopedReadFileTool(root, nil).(optionsAwareTool).RunWithOptions(context.Background(), map[string]any{
"path": "a.go",
}, RunOptions{FileTracker: tracker})
if read.Status != StatusOK {
t.Fatalf("read before edit failed: %s", read.Output)
}
result := NewScopedEditFileTool(root, nil).(optionsAwareTool).RunWithOptions(context.Background(), map[string]any{
"path": "a.go", "old_string": "before", "new_string": "requested",
}, RunOptions{FileTracker: tracker})
if result.Status != StatusOK || len(result.FileDiffs) != 1 || result.FileDiffs[0].NewText != formatted {
t.Fatalf("atomic formatter edit = status=%s diffs=%#v output=%q", result.Status, result.FileDiffs, result.Output)
}
resolvedRoot, err := filepath.EvalSymlinks(root)
if err != nil {
t.Fatal(err)
}
version, tracked := tracker.Version(filepath.Join(resolvedRoot, "a.go"))
if !tracked || version.Hash != HashContent([]byte(formatted)) {
t.Fatalf("atomic formatter tracker = %#v, tracked=%t", version, tracked)
}
}