2019-01-17 09:59:05 -07:00
|
|
|
// 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.
|
|
|
|
|
2019-04-08 07:22:58 -06:00
|
|
|
package diff_test
|
2019-01-17 09:59:05 -07:00
|
|
|
|
|
|
|
import (
|
2019-04-04 15:42:34 -06:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2019-01-17 09:59:05 -07:00
|
|
|
"reflect"
|
2019-04-05 11:49:15 -06:00
|
|
|
"strings"
|
2019-01-17 09:59:05 -07:00
|
|
|
"testing"
|
2019-04-08 07:22:58 -06:00
|
|
|
|
|
|
|
"golang.org/x/tools/internal/lsp/diff"
|
2019-01-17 09:59:05 -07:00
|
|
|
)
|
|
|
|
|
2019-04-04 15:42:34 -06:00
|
|
|
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`")
|
|
|
|
|
2019-01-17 09:59:05 -07:00
|
|
|
func TestDiff(t *testing.T) {
|
2019-04-05 11:49:15 -06:00
|
|
|
for _, test := range []struct {
|
|
|
|
a, b string
|
2019-04-08 07:22:58 -06:00
|
|
|
lines []*diff.Op
|
|
|
|
operations []*diff.Op
|
2019-04-04 15:42:34 -06:00
|
|
|
unified string
|
|
|
|
nodiff bool
|
2019-01-17 09:59:05 -07:00
|
|
|
}{
|
|
|
|
{
|
2019-04-04 15:42:34 -06:00
|
|
|
a: "A\nB\nC\n",
|
|
|
|
b: "A\nB\nC\n",
|
2019-04-08 07:22:58 -06:00
|
|
|
operations: []*diff.Op{},
|
2019-04-04 15:42:34 -06:00
|
|
|
unified: `
|
|
|
|
`[1:]}, {
|
|
|
|
a: "A\n",
|
|
|
|
b: "B\n",
|
2019-04-08 07:22:58 -06:00
|
|
|
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},
|
2019-04-04 15:42:34 -06:00
|
|
|
},
|
|
|
|
unified: `
|
|
|
|
@@ -1 +1 @@
|
|
|
|
-A
|
|
|
|
+B
|
|
|
|
`[1:]}, {
|
|
|
|
a: "A",
|
|
|
|
b: "B",
|
2019-04-08 07:22:58 -06:00
|
|
|
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},
|
2019-04-04 15:42:34 -06:00
|
|
|
},
|
|
|
|
unified: `
|
|
|
|
@@ -1 +1 @@
|
|
|
|
-A
|
|
|
|
\ No newline at end of file
|
|
|
|
+B
|
|
|
|
\ No newline at end of file
|
|
|
|
`[1:]}, {
|
2019-04-05 11:49:15 -06:00
|
|
|
a: "A\nB\nC\nA\nB\nB\nA\n",
|
|
|
|
b: "C\nB\nA\nB\nA\nC\n",
|
2019-04-08 07:22:58 -06:00
|
|
|
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},
|
2019-01-17 09:59:05 -07:00
|
|
|
},
|
2019-04-04 15:42:34 -06:00
|
|
|
unified: `
|
|
|
|
@@ -1,7 +1,6 @@
|
|
|
|
-A
|
|
|
|
-B
|
|
|
|
C
|
|
|
|
+B
|
|
|
|
A
|
|
|
|
B
|
|
|
|
-B
|
|
|
|
A
|
|
|
|
+C
|
|
|
|
`[1:],
|
|
|
|
nodiff: true, // diff algorithm produces different delete/insert pattern
|
2019-01-17 09:59:05 -07:00
|
|
|
},
|
2019-02-12 11:51:16 -07:00
|
|
|
{
|
2019-04-05 11:49:15 -06:00
|
|
|
a: "A\nB\n",
|
|
|
|
b: "A\nC\n\n",
|
2019-04-08 07:22:58 -06:00
|
|
|
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},
|
2019-02-12 11:51:16 -07:00
|
|
|
},
|
2019-04-04 15:42:34 -06:00
|
|
|
unified: `
|
|
|
|
@@ -1,2 +1,3 @@
|
|
|
|
A
|
|
|
|
-B
|
|
|
|
+C
|
|
|
|
+
|
|
|
|
`[1:],
|
2019-02-12 11:51:16 -07:00
|
|
|
},
|
2019-04-04 15:42:34 -06:00
|
|
|
{
|
|
|
|
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:]},
|
2019-01-17 09:59:05 -07:00
|
|
|
} {
|
2019-04-08 07:22:58 -06:00
|
|
|
a := diff.SplitLines(test.a)
|
|
|
|
b := diff.SplitLines(test.b)
|
|
|
|
ops := diff.Operations(a, b)
|
2019-04-04 15:42:34 -06:00
|
|
|
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)
|
|
|
|
}
|
2019-01-17 09:59:05 -07:00
|
|
|
}
|
|
|
|
}
|
2019-04-08 07:22:58 -06:00
|
|
|
applied := diff.ApplyEdits(a, ops)
|
2019-04-05 11:49:15 -06:00
|
|
|
for i, want := range applied {
|
2019-01-17 09:59:05 -07:00
|
|
|
got := b[i]
|
|
|
|
if got != want {
|
|
|
|
t.Errorf("expected %v got %v", want, got)
|
|
|
|
}
|
|
|
|
}
|
2019-04-04 15:42:34 -06:00
|
|
|
if test.unified != "" {
|
2019-04-08 07:22:58 -06:00
|
|
|
diff := diff.ToUnified(fileA, fileB, a, ops)
|
2019-04-04 15:42:34 -06:00
|
|
|
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)
|
2019-01-17 09:59:05 -07:00
|
|
|
}
|
2019-04-04 15:42:34 -06:00
|
|
|
return bits[2], nil
|
2019-01-17 09:59:05 -07:00
|
|
|
}
|