| // 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 difftest supplies a set of tests that will operate on any |
| // implementation of a diff algorithm as exposed by |
| // "golang.org/x/tools/internal/lsp/diff" |
| package difftest |
| |
| import ( |
| "fmt" |
| "testing" |
| |
| "golang.org/x/tools/internal/lsp/diff" |
| "golang.org/x/tools/internal/span" |
| ) |
| |
| const ( |
| FileA = "from" |
| FileB = "to" |
| UnifiedPrefix = "--- " + FileA + "\n+++ " + FileB + "\n" |
| ) |
| |
| var TestCases = []struct { |
| Name, In, Out, Unified string |
| Edits, LineEdits []diff.TextEdit |
| NoDiff bool |
| }{{ |
| Name: "empty", |
| In: "", |
| Out: "", |
| }, { |
| Name: "no_diff", |
| In: "gargantuan\n", |
| Out: "gargantuan\n", |
| }, { |
| Name: "replace_all", |
| In: "fruit\n", |
| Out: "cheese\n", |
| Unified: UnifiedPrefix + ` |
| @@ -1 +1 @@ |
| -fruit |
| +cheese |
| `[1:], |
| Edits: []diff.TextEdit{{Span: newSpan(0, 5), NewText: "cheese"}}, |
| LineEdits: []diff.TextEdit{{Span: newSpan(0, 6), NewText: "cheese\n"}}, |
| }, { |
| Name: "insert_rune", |
| In: "gord\n", |
| Out: "gourd\n", |
| Unified: UnifiedPrefix + ` |
| @@ -1 +1 @@ |
| -gord |
| +gourd |
| `[1:], |
| Edits: []diff.TextEdit{{Span: newSpan(2, 2), NewText: "u"}}, |
| LineEdits: []diff.TextEdit{{Span: newSpan(0, 5), NewText: "gourd\n"}}, |
| }, { |
| Name: "delete_rune", |
| In: "groat\n", |
| Out: "goat\n", |
| Unified: UnifiedPrefix + ` |
| @@ -1 +1 @@ |
| -groat |
| +goat |
| `[1:], |
| Edits: []diff.TextEdit{{Span: newSpan(1, 2), NewText: ""}}, |
| LineEdits: []diff.TextEdit{{Span: newSpan(0, 6), NewText: "goat\n"}}, |
| }, { |
| Name: "replace_rune", |
| In: "loud\n", |
| Out: "lord\n", |
| Unified: UnifiedPrefix + ` |
| @@ -1 +1 @@ |
| -loud |
| +lord |
| `[1:], |
| Edits: []diff.TextEdit{{Span: newSpan(2, 3), NewText: "r"}}, |
| LineEdits: []diff.TextEdit{{Span: newSpan(0, 5), NewText: "lord\n"}}, |
| }, { |
| Name: "replace_partials", |
| In: "blanket\n", |
| Out: "bunker\n", |
| Unified: UnifiedPrefix + ` |
| @@ -1 +1 @@ |
| -blanket |
| +bunker |
| `[1:], |
| Edits: []diff.TextEdit{ |
| {Span: newSpan(1, 3), NewText: "u"}, |
| {Span: newSpan(6, 7), NewText: "r"}, |
| }, |
| LineEdits: []diff.TextEdit{{Span: newSpan(0, 8), NewText: "bunker\n"}}, |
| }, { |
| Name: "insert_line", |
| In: "1: one\n3: three\n", |
| Out: "1: one\n2: two\n3: three\n", |
| Unified: UnifiedPrefix + ` |
| @@ -1,2 +1,3 @@ |
| 1: one |
| +2: two |
| 3: three |
| `[1:], |
| Edits: []diff.TextEdit{{Span: newSpan(7, 7), NewText: "2: two\n"}}, |
| }, { |
| Name: "replace_no_newline", |
| In: "A", |
| Out: "B", |
| Unified: UnifiedPrefix + ` |
| @@ -1 +1 @@ |
| -A |
| \ No newline at end of file |
| +B |
| \ No newline at end of file |
| `[1:], |
| Edits: []diff.TextEdit{{Span: newSpan(0, 1), NewText: "B"}}, |
| }, { |
| Name: "add_end", |
| In: "A", |
| Out: "AB", |
| Unified: UnifiedPrefix + ` |
| @@ -1 +1 @@ |
| -A |
| \ No newline at end of file |
| +AB |
| \ No newline at end of file |
| `[1:], |
| Edits: []diff.TextEdit{{Span: newSpan(1, 1), NewText: "B"}}, |
| LineEdits: []diff.TextEdit{{Span: newSpan(0, 1), NewText: "AB"}}, |
| }, { |
| Name: "add_newline", |
| In: "A", |
| Out: "A\n", |
| Unified: UnifiedPrefix + ` |
| @@ -1 +1 @@ |
| -A |
| \ No newline at end of file |
| +A |
| `[1:], |
| Edits: []diff.TextEdit{{Span: newSpan(1, 1), NewText: "\n"}}, |
| LineEdits: []diff.TextEdit{{Span: newSpan(0, 1), NewText: "A\n"}}, |
| }, { |
| Name: "delete_front", |
| In: "A\nB\nC\nA\nB\nB\nA\n", |
| Out: "C\nB\nA\nB\nA\nC\n", |
| Unified: UnifiedPrefix + ` |
| @@ -1,7 +1,6 @@ |
| -A |
| -B |
| C |
| +B |
| A |
| B |
| -B |
| A |
| +C |
| `[1:], |
| Edits: []diff.TextEdit{ |
| {Span: newSpan(0, 4), NewText: ""}, |
| {Span: newSpan(6, 6), NewText: "B\n"}, |
| {Span: newSpan(10, 12), NewText: ""}, |
| {Span: newSpan(14, 14), NewText: "C\n"}, |
| }, |
| NoDiff: true, // diff algorithm produces different delete/insert pattern |
| }, |
| { |
| Name: "replace_last_line", |
| In: "A\nB\n", |
| Out: "A\nC\n\n", |
| Unified: UnifiedPrefix + ` |
| @@ -1,2 +1,3 @@ |
| A |
| -B |
| +C |
| + |
| `[1:], |
| Edits: []diff.TextEdit{{Span: newSpan(2, 3), NewText: "C\n"}}, |
| LineEdits: []diff.TextEdit{{Span: newSpan(2, 4), NewText: "C\n\n"}}, |
| }, |
| { |
| Name: "multiple_replace", |
| In: "A\nB\nC\nD\nE\nF\nG\n", |
| Out: "A\nH\nI\nJ\nE\nF\nK\n", |
| Unified: UnifiedPrefix + ` |
| @@ -1,7 +1,7 @@ |
| A |
| -B |
| -C |
| -D |
| +H |
| +I |
| +J |
| E |
| F |
| -G |
| +K |
| `[1:], |
| Edits: []diff.TextEdit{ |
| {Span: newSpan(2, 8), NewText: "H\nI\nJ\n"}, |
| {Span: newSpan(12, 14), NewText: "K\n"}, |
| }, |
| NoDiff: true, // diff algorithm produces different delete/insert pattern |
| }, |
| } |
| |
| func init() { |
| // expand all the spans to full versions |
| // we need them all to have their line number and column |
| for _, tc := range TestCases { |
| tf := span.NewTokenFile("", []byte(tc.In)) |
| for i := range tc.Edits { |
| tc.Edits[i].Span, _ = tc.Edits[i].Span.WithAll(tf) |
| } |
| for i := range tc.LineEdits { |
| tc.LineEdits[i].Span, _ = tc.LineEdits[i].Span.WithAll(tf) |
| } |
| } |
| } |
| |
| func DiffTest(t *testing.T, compute diff.ComputeEdits) { |
| t.Helper() |
| for _, test := range TestCases { |
| t.Run(test.Name, func(t *testing.T) { |
| t.Helper() |
| edits, err := compute(span.URIFromPath("/"+test.Name), test.In, test.Out) |
| if err != nil { |
| t.Fatal(err) |
| } |
| got := diff.ApplyEdits(test.In, edits) |
| unified := fmt.Sprint(diff.ToUnified(FileA, FileB, test.In, edits)) |
| if got != test.Out { |
| t.Errorf("got patched:\n%v\nfrom diff:\n%v\nexpected:\n%v", got, unified, test.Out) |
| } |
| if !test.NoDiff && unified != test.Unified { |
| t.Errorf("got diff:\n%v\nexpected:\n%v", unified, test.Unified) |
| } |
| }) |
| } |
| } |
| |
| func newSpan(start, end int) span.Span { |
| return span.New("", span.NewPoint(0, 0, start), span.NewPoint(0, 0, end)) |
| } |