mirror of
https://github.com/golang/go
synced 2024-11-18 16:14:46 -07:00
744a51dd88
This allows us to use the diff.ApplyEdits in tests, saving us from a different implementation. It also prepares for command lines that need to use diff features based on the results of a protocol message. Splitting content into lines is too easy to get wrong, and needs to be done correctly or the diff results make no sense. This adds the SplitLines function to the diff pacakge to do it right and then uses it everwhere we we already doing it wrong. It also makes all the diff tests external black box tests. Change-Id: I698227d5769a2bfbfd22a64ea42906b1df9268d9 Reviewed-on: https://go-review.googlesource.com/c/tools/+/171027 Run-TryBot: Ian Cottrell <iancottrell@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
205 lines
4.5 KiB
Go
205 lines
4.5 KiB
Go
// 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
|
|
}
|