// 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 ( "flag" "fmt" "io/ioutil" "os" "os/exec" "reflect" "strings" "testing" "golang.org/x/tools/internal/lsp/diff" ) const ( fileA = "a/a.go" fileB = "b/b.go" unifiedPrefix = "--- " + fileA + "\n+++ " + fileB + "\n" ) var verifyDiff = flag.Bool("verify-diff", false, "Check that the unified diff output matches `diff -u`") func TestDiff(t *testing.T) { for _, test := range []struct { a, b string lines []*diff.Op operations []*diff.Op unified string nodiff bool }{ { a: "A\nB\nC\n", b: "A\nB\nC\n", operations: []*diff.Op{}, unified: ` `[1:]}, { a: "A\n", b: "B\n", operations: []*diff.Op{ &diff.Op{Kind: diff.Delete, I1: 0, I2: 1, J1: 0}, &diff.Op{Kind: diff.Insert, Content: []string{"B\n"}, I1: 1, I2: 1, J1: 0}, }, unified: ` @@ -1 +1 @@ -A +B `[1:]}, { a: "A", b: "B", operations: []*diff.Op{ &diff.Op{Kind: diff.Delete, I1: 0, I2: 1, J1: 0}, &diff.Op{Kind: diff.Insert, Content: []string{"B"}, I1: 1, I2: 1, J1: 0}, }, unified: ` @@ -1 +1 @@ -A \ No newline at end of file +B \ No newline at end of file `[1:]}, { a: "A\nB\nC\nA\nB\nB\nA\n", b: "C\nB\nA\nB\nA\nC\n", operations: []*diff.Op{ &diff.Op{Kind: diff.Delete, I1: 0, I2: 1, J1: 0}, &diff.Op{Kind: diff.Delete, I1: 1, I2: 2, J1: 0}, &diff.Op{Kind: diff.Insert, Content: []string{"B\n"}, I1: 3, I2: 3, J1: 1}, &diff.Op{Kind: diff.Delete, I1: 5, I2: 6, J1: 4}, &diff.Op{Kind: diff.Insert, Content: []string{"C\n"}, I1: 7, I2: 7, J1: 5}, }, unified: ` @@ -1,7 +1,6 @@ -A -B C +B A B -B A +C `[1:], nodiff: true, // diff algorithm produces different delete/insert pattern }, { a: "A\nB\n", b: "A\nC\n\n", operations: []*diff.Op{ &diff.Op{Kind: diff.Delete, I1: 1, I2: 2, J1: 1}, &diff.Op{Kind: diff.Insert, Content: []string{"C\n"}, I1: 2, I2: 2, J1: 1}, &diff.Op{Kind: diff.Insert, Content: []string{"\n"}, I1: 2, I2: 2, J1: 2}, }, unified: ` @@ -1,2 +1,3 @@ A -B +C + `[1:], }, { a: "A\nB\nC\nD\nE\nF\nG\n", b: "A\nH\nI\nJ\nE\nF\nK\n", unified: ` @@ -1,7 +1,7 @@ A -B -C -D +H +I +J E F -G +K `[1:]}, } { a := diff.SplitLines(test.a) b := diff.SplitLines(test.b) ops := diff.Operations(a, b) if test.operations != nil { if len(ops) != len(test.operations) { t.Fatalf("expected %v operations, got %v", len(test.operations), len(ops)) } for i, got := range ops { want := test.operations[i] if !reflect.DeepEqual(want, got) { t.Errorf("expected %v, got %v", want, got) } } } applied := diff.ApplyEdits(a, ops) for i, want := range applied { got := b[i] if got != want { t.Errorf("expected %v got %v", want, got) } } if test.unified != "" { diff := diff.ToUnified(fileA, fileB, a, ops) got := fmt.Sprint(diff) if !strings.HasPrefix(got, unifiedPrefix) { t.Errorf("expected prefix:\n%s\ngot:\n%s", unifiedPrefix, got) continue } got = got[len(unifiedPrefix):] if test.unified != got { t.Errorf("expected:\n%q\ngot:\n%q", test.unified, got) } } if *verifyDiff && test.unified != "" && !test.nodiff { diff, err := getDiffOutput(test.a, test.b) if err != nil { t.Fatal(err) } if diff != test.unified { t.Errorf("unified:\n%q\ndiff -u:\n%q", test.unified, diff) } } } } func getDiffOutput(a, b string) (string, error) { fileA, err := ioutil.TempFile("", "diff.in") if err != nil { return "", err } defer os.Remove(fileA.Name()) if _, err := fileA.Write([]byte(a)); err != nil { return "", err } if err := fileA.Close(); err != nil { return "", err } fileB, err := ioutil.TempFile("", "diff.in") if err != nil { return "", err } defer os.Remove(fileB.Name()) if _, err := fileB.Write([]byte(b)); err != nil { return "", err } if err := fileB.Close(); err != nil { return "", err } cmd := exec.Command("diff", "-u", fileA.Name(), fileB.Name()) out, err := cmd.CombinedOutput() if err != nil { if _, ok := err.(*exec.ExitError); !ok { return "", fmt.Errorf("failed to run diff -u %v %v: %v\n%v", fileA.Name(), fileB.Name(), err, string(out)) } } diff := string(out) bits := strings.SplitN(diff, "\n", 3) if len(bits) != 3 { return "", fmt.Errorf("diff output did not have file prefix:\n%s", diff) } return bits[2], nil }