| // Copyright 2019 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package diff_test | |
| import ( | |
| "bytes" | |
| "math/rand" | |
| "os" | |
| "os/exec" | |
| "path/filepath" | |
| "reflect" | |
| "runtime" | |
| "strings" | |
| "testing" | |
| "unicode/utf8" | |
| "golang.org/x/tools/internal/diff" | |
| "golang.org/x/tools/internal/diff/difftest" | |
| "golang.org/x/tools/internal/testenv" | |
| ) | |
| func TestApply(t *testing.T) { | |
| for _, tc := range difftest.TestCases { | |
| t.Run(tc.Name, func(t *testing.T) { | |
| got, err := diff.Apply(tc.In, tc.Edits) | |
| if err != nil { | |
| t.Fatalf("Apply(Edits) failed: %v", err) | |
| } | |
| if got != tc.Out { | |
| t.Errorf("Apply(Edits): got %q, want %q", got, tc.Out) | |
| } | |
| if tc.LineEdits != nil { | |
| got, err := diff.Apply(tc.In, tc.LineEdits) | |
| if err != nil { | |
| t.Fatalf("Apply(LineEdits) failed: %v", err) | |
| } | |
| if got != tc.Out { | |
| t.Errorf("Apply(LineEdits): got %q, want %q", got, tc.Out) | |
| } | |
| } | |
| }) | |
| } | |
| } | |
| func TestNEdits(t *testing.T) { | |
| for _, tc := range difftest.TestCases { | |
| edits := diff.Strings(tc.In, tc.Out) | |
| got, err := diff.Apply(tc.In, edits) | |
| if err != nil { | |
| t.Fatalf("Apply failed: %v", err) | |
| } | |
| if got != tc.Out { | |
| t.Fatalf("%s: got %q wanted %q", tc.Name, got, tc.Out) | |
| } | |
| if len(edits) < len(tc.Edits) { // should find subline edits | |
| t.Errorf("got %v, expected %v for %#v", edits, tc.Edits, tc) | |
| } | |
| } | |
| } | |
| func TestNRandom(t *testing.T) { | |
| rnd := rand.New(rand.NewSource(1)) | |
| for i := range 1000 { | |
| a := randstr(rnd, "abω", 16) | |
| b := randstr(rnd, "abωc", 16) | |
| edits := diff.Strings(a, b) | |
| got, err := diff.Apply(a, edits) | |
| if err != nil { | |
| t.Fatalf("Apply failed: %v", err) | |
| } | |
| if got != b { | |
| t.Fatalf("%d: got %q, wanted %q, starting with %q", i, got, b, a) | |
| } | |
| } | |
| } | |
| // $ go test -fuzz=FuzzRoundTrip ./internal/diff | |
| func FuzzRoundTrip(f *testing.F) { | |
| f.Fuzz(func(t *testing.T, a, b string) { | |
| if !utf8.ValidString(a) || !utf8.ValidString(b) { | |
| return // inputs must be text | |
| } | |
| edits := diff.Strings(a, b) | |
| got, err := diff.Apply(a, edits) | |
| if err != nil { | |
| t.Fatalf("Apply failed: %v", err) | |
| } | |
| if got != b { | |
| t.Fatalf("applying diff(%q, %q) gives %q; edits=%v", a, b, got, edits) | |
| } | |
| }) | |
| } | |
| func TestLineEdits(t *testing.T) { | |
| for _, tc := range difftest.TestCases { | |
| t.Run(tc.Name, func(t *testing.T) { | |
| want := tc.LineEdits | |
| if want == nil { | |
| want = tc.Edits // already line-aligned | |
| } | |
| got, err := diff.LineEdits(tc.In, tc.Edits) | |
| if err != nil { | |
| t.Fatalf("LineEdits: %v", err) | |
| } | |
| if !reflect.DeepEqual(got, want) { | |
| t.Errorf("in=<<%s>>\nout=<<%s>>\nraw edits=%s\nline edits=%s\nwant: %s", | |
| tc.In, tc.Out, tc.Edits, got, want) | |
| } | |
| // make sure that applying the edits gives the expected result | |
| fixed, err := diff.Apply(tc.In, got) | |
| if err != nil { | |
| t.Error(err) | |
| } | |
| if fixed != tc.Out { | |
| t.Errorf("Apply(LineEdits): got %q, want %q", fixed, tc.Out) | |
| } | |
| }) | |
| } | |
| } | |
| func TestToUnified(t *testing.T) { | |
| // Plan 9 has no patch command, so use ape/patch, the POSIX patch. | |
| patch := "patch" | |
| if runtime.GOOS == "plan9" { | |
| patch = "/bin/ape/patch" | |
| } | |
| testenv.NeedsTool(t, patch) | |
| for _, tc := range difftest.TestCases { | |
| t.Run(tc.Name, func(t *testing.T) { | |
| nedits := diff.Lines(tc.In, tc.Out) | |
| xunified, err := diff.ToUnified(difftest.FileA, difftest.FileB, tc.In, nedits, diff.DefaultContextLines) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if xunified == "" { | |
| return | |
| } | |
| orig := filepath.Join(t.TempDir(), "original") | |
| err = os.WriteFile(orig, []byte(tc.In), 0644) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| temp := filepath.Join(t.TempDir(), "patched") | |
| err = os.WriteFile(temp, []byte(tc.In), 0644) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| cmd := exec.Command(patch, "-p0", "-u", "-s", "-o", temp, orig) | |
| cmd.Stdin = strings.NewReader(xunified) | |
| cmd.Stdout = new(bytes.Buffer) | |
| cmd.Stderr = new(bytes.Buffer) | |
| if err = cmd.Run(); err != nil { | |
| t.Fatalf("%v: %q (%q) (%q)", err, cmd.String(), | |
| cmd.Stderr, cmd.Stdout) | |
| } | |
| got, err := os.ReadFile(temp) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if string(got) != tc.Out { | |
| t.Errorf("applying unified failed: got\n%q, wanted\n%q unified\n%q", | |
| got, tc.Out, xunified) | |
| } | |
| }) | |
| } | |
| } | |
| func TestRegressionOld001(t *testing.T) { | |
| a := "// Copyright 2019 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage diff_test\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"golang.org/x/tools/gopls/internal/lsp/diff\"\n\t\"golang.org/x/tools/internal/diff/difftest\"\n\t\"golang.org/x/tools/gopls/internal/span\"\n)\n" | |
| b := "// Copyright 2019 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage diff_test\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/safehtml/template\"\n\t\"golang.org/x/tools/gopls/internal/lsp/diff\"\n\t\"golang.org/x/tools/internal/diff/difftest\"\n\t\"golang.org/x/tools/gopls/internal/span\"\n)\n" | |
| diffs := diff.Strings(a, b) | |
| got, err := diff.Apply(a, diffs) | |
| if err != nil { | |
| t.Fatalf("Apply failed: %v", err) | |
| } | |
| if got != b { | |
| i := 0 | |
| for ; i < len(a) && i < len(b) && got[i] == b[i]; i++ { | |
| } | |
| t.Errorf("oops %vd\n%q\n%q", diffs, got, b) | |
| t.Errorf("\n%q\n%q", got[i:], b[i:]) | |
| } | |
| } | |
| func TestRegressionOld002(t *testing.T) { | |
| a := "n\"\n)\n" | |
| b := "n\"\n\t\"golang.org/x//nnal/stack\"\n)\n" | |
| diffs := diff.Strings(a, b) | |
| got, err := diff.Apply(a, diffs) | |
| if err != nil { | |
| t.Fatalf("Apply failed: %v", err) | |
| } | |
| if got != b { | |
| i := 0 | |
| for ; i < len(a) && i < len(b) && got[i] == b[i]; i++ { | |
| } | |
| t.Errorf("oops %vd\n%q\n%q", diffs, got, b) | |
| t.Errorf("\n%q\n%q", got[i:], b[i:]) | |
| } | |
| } | |
| // return a random string of length n made of characters from s | |
| func randstr(rnd *rand.Rand, s string, n int) string { | |
| src := []rune(s) | |
| x := make([]rune, n) | |
| for i := range n { | |
| x[i] = src[rnd.Intn(len(src))] | |
| } | |
| return string(x) | |
| } |