Download
104 lines 3 KiBGoModified 2026-10-26 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
package diff

import (
	"strings"
	"testing"
)

func TestUnifiedRoundTrip(t *testing.T) {
	a := "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n"
	b := "a\nB\nc\nd\ne\nf\ng\nh\nI\nj\nk\n"
	u := Unified("f", a, b, 2)
	if !strings.Contains(u, "-b\n+B\n") || strings.Count(u, "@@ ") != 2 {
		t.Fatalf("unexpected diff:\n%s", u)
	}
	got, err := Apply(a, u)
	if err != nil {
		t.Fatal(err)
	}
	if got != b {
		t.Errorf("Apply = %q, want %q", got, b)
	}
	if Unified("f", a, a, 3) != "" {
		t.Error("diff of equal texts")
	}
	if add, del := Stat(a, b); add != 3 || del != 2 {
		t.Errorf("Stat = +%d -%d", add, del)
	}
}

func TestApplyIgnoresWrongLineNumbers(t *testing.T) {
	text := "func a() {\n\treturn 1\n}\n\nfunc b() {\n\treturn 2\n}\n"
	patch := `--- x.go
+++ x.go
@@ -40,3 +40,3 @@
 func b() {
-	return 2
+	return 3
 }
`
	got, err := Apply(text, patch)
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(got, "return 3") || !strings.Contains(got, "return 1") {
		t.Errorf("got %q", got)
	}
}

func TestApplyErrors(t *testing.T) {
	text := "x\ny\nx\ny\n"
	if _, err := Apply(text, "@@\n x\n-y\n+z\n"); err == nil || !strings.Contains(err.Error(), "2 places") {
		t.Errorf("ambiguous hunk: %v", err)
	}
	if _, err := Apply(text, "@@\n q\n-y\n+z\n"); err == nil || !strings.Contains(err.Error(), "not found") {
		t.Errorf("missing hunk: %v", err)
	}
	if _, err := Apply(text, "no hunks here"); err == nil {
		t.Error("garbage applied")
	}
}

func TestApplyAmbiguousIgnoresHint(t *testing.T) {
	// The header points at the second match, but a wrong number would
	// look the same, so the hunk is refused rather than placed by it.
	text := "x\ny\nx\ny\n"
	_, err := Apply(text, "@@ -3,2 +3,2 @@\n x\n-y\n+z\n")
	if err == nil || !strings.Contains(err.Error(), "2 places (line 1, line 3)") {
		t.Errorf("ambiguous hunk: %v", err)
	}
	// Hunks are applied in order, so only matches after the earlier
	// hunks count.
	got, err := Apply("a\n"+text, "@@\n-a\n+b\n x\n y\n@@\n x\n-y\n+z\n")
	if err != nil || got != "b\nx\ny\nx\nz\n" {
		t.Errorf("unique after the first hunk: %q, %v", got, err)
	}
	// After an earlier hunk, the lines are named as the model read them.
	_, err = Apply("q\n"+text, "@@\n-q\n+a\n+b\n@@\n x\n-y\n+z\n")
	if err == nil || !strings.Contains(err.Error(), "(line 2, line 4)") {
		t.Errorf("ambiguous after a hunk: %v", err)
	}
}

func TestApplyAdditionNeedsContext(t *testing.T) {
	// A header that names the right line still does not place the lines:
	// a wrong one would look the same.
	_, err := Apply("a\nb\n", "@@ -1,0 +2 @@\n+x\n")
	if err == nil || !strings.Contains(err.Error(), "without any context") {
		t.Errorf("addition without context: %v", err)
	}
}

func TestApplyNewFile(t *testing.T) {
	got, err := Apply("", "--- /dev/null\n+++ new.txt\n@@ -0,0 +1,2 @@\n+one\n+two\n")
	if err != nil || got != "one\ntwo\n" {
		t.Errorf("got %q, %v", got, err)
	}
}

func TestApplyLooseWhitespace(t *testing.T) {
	got, err := Apply("a  \nb\n", "@@\n a\n-b\n+c\n")
	if err != nil || got != "a  \nc\n" {
		t.Errorf("got %q, %v", got, err)
	}
}