1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package difftest supplies a set of tests that will operate on any
6// implementation of a diff algorithm as exposed by
7// "golang.org/x/tools/internal/lsp/diff"
8package difftest
9
10import (
11	"fmt"
12	"testing"
13
14	"golang.org/x/tools/internal/lsp/diff"
15	"golang.org/x/tools/internal/span"
16)
17
18const (
19	FileA         = "from"
20	FileB         = "to"
21	UnifiedPrefix = "--- " + FileA + "\n+++ " + FileB + "\n"
22)
23
24var TestCases = []struct {
25	Name, In, Out, Unified string
26	Edits, LineEdits       []diff.TextEdit
27	NoDiff                 bool
28}{{
29	Name: "empty",
30	In:   "",
31	Out:  "",
32}, {
33	Name: "no_diff",
34	In:   "gargantuan\n",
35	Out:  "gargantuan\n",
36}, {
37	Name: "replace_all",
38	In:   "fruit\n",
39	Out:  "cheese\n",
40	Unified: UnifiedPrefix + `
41@@ -1 +1 @@
42-fruit
43+cheese
44`[1:],
45	Edits:     []diff.TextEdit{{Span: newSpan(0, 5), NewText: "cheese"}},
46	LineEdits: []diff.TextEdit{{Span: newSpan(0, 6), NewText: "cheese\n"}},
47}, {
48	Name: "insert_rune",
49	In:   "gord\n",
50	Out:  "gourd\n",
51	Unified: UnifiedPrefix + `
52@@ -1 +1 @@
53-gord
54+gourd
55`[1:],
56	Edits:     []diff.TextEdit{{Span: newSpan(2, 2), NewText: "u"}},
57	LineEdits: []diff.TextEdit{{Span: newSpan(0, 5), NewText: "gourd\n"}},
58}, {
59	Name: "delete_rune",
60	In:   "groat\n",
61	Out:  "goat\n",
62	Unified: UnifiedPrefix + `
63@@ -1 +1 @@
64-groat
65+goat
66`[1:],
67	Edits:     []diff.TextEdit{{Span: newSpan(1, 2), NewText: ""}},
68	LineEdits: []diff.TextEdit{{Span: newSpan(0, 6), NewText: "goat\n"}},
69}, {
70	Name: "replace_rune",
71	In:   "loud\n",
72	Out:  "lord\n",
73	Unified: UnifiedPrefix + `
74@@ -1 +1 @@
75-loud
76+lord
77`[1:],
78	Edits:     []diff.TextEdit{{Span: newSpan(2, 3), NewText: "r"}},
79	LineEdits: []diff.TextEdit{{Span: newSpan(0, 5), NewText: "lord\n"}},
80}, {
81	Name: "replace_partials",
82	In:   "blanket\n",
83	Out:  "bunker\n",
84	Unified: UnifiedPrefix + `
85@@ -1 +1 @@
86-blanket
87+bunker
88`[1:],
89	Edits: []diff.TextEdit{
90		{Span: newSpan(1, 3), NewText: "u"},
91		{Span: newSpan(6, 7), NewText: "r"},
92	},
93	LineEdits: []diff.TextEdit{{Span: newSpan(0, 8), NewText: "bunker\n"}},
94}, {
95	Name: "insert_line",
96	In:   "1: one\n3: three\n",
97	Out:  "1: one\n2: two\n3: three\n",
98	Unified: UnifiedPrefix + `
99@@ -1,2 +1,3 @@
100 1: one
101+2: two
102 3: three
103`[1:],
104	Edits: []diff.TextEdit{{Span: newSpan(7, 7), NewText: "2: two\n"}},
105}, {
106	Name: "replace_no_newline",
107	In:   "A",
108	Out:  "B",
109	Unified: UnifiedPrefix + `
110@@ -1 +1 @@
111-A
112\ No newline at end of file
113+B
114\ No newline at end of file
115`[1:],
116	Edits: []diff.TextEdit{{Span: newSpan(0, 1), NewText: "B"}},
117}, {
118	Name: "add_end",
119	In:   "A",
120	Out:  "AB",
121	Unified: UnifiedPrefix + `
122@@ -1 +1 @@
123-A
124\ No newline at end of file
125+AB
126\ No newline at end of file
127`[1:],
128	Edits:     []diff.TextEdit{{Span: newSpan(1, 1), NewText: "B"}},
129	LineEdits: []diff.TextEdit{{Span: newSpan(0, 1), NewText: "AB"}},
130}, {
131	Name: "add_newline",
132	In:   "A",
133	Out:  "A\n",
134	Unified: UnifiedPrefix + `
135@@ -1 +1 @@
136-A
137\ No newline at end of file
138+A
139`[1:],
140	Edits:     []diff.TextEdit{{Span: newSpan(1, 1), NewText: "\n"}},
141	LineEdits: []diff.TextEdit{{Span: newSpan(0, 1), NewText: "A\n"}},
142}, {
143	Name: "delete_front",
144	In:   "A\nB\nC\nA\nB\nB\nA\n",
145	Out:  "C\nB\nA\nB\nA\nC\n",
146	Unified: UnifiedPrefix + `
147@@ -1,7 +1,6 @@
148-A
149-B
150 C
151+B
152 A
153 B
154-B
155 A
156+C
157`[1:],
158	Edits: []diff.TextEdit{
159		{Span: newSpan(0, 4), NewText: ""},
160		{Span: newSpan(6, 6), NewText: "B\n"},
161		{Span: newSpan(10, 12), NewText: ""},
162		{Span: newSpan(14, 14), NewText: "C\n"},
163	},
164	NoDiff: true, // diff algorithm produces different delete/insert pattern
165},
166	{
167		Name: "replace_last_line",
168		In:   "A\nB\n",
169		Out:  "A\nC\n\n",
170		Unified: UnifiedPrefix + `
171@@ -1,2 +1,3 @@
172 A
173-B
174+C
175+
176`[1:],
177		Edits:     []diff.TextEdit{{Span: newSpan(2, 3), NewText: "C\n"}},
178		LineEdits: []diff.TextEdit{{Span: newSpan(2, 4), NewText: "C\n\n"}},
179	},
180	{
181		Name: "mulitple_replace",
182		In:   "A\nB\nC\nD\nE\nF\nG\n",
183		Out:  "A\nH\nI\nJ\nE\nF\nK\n",
184		Unified: UnifiedPrefix + `
185@@ -1,7 +1,7 @@
186 A
187-B
188-C
189-D
190+H
191+I
192+J
193 E
194 F
195-G
196+K
197`[1:],
198		Edits: []diff.TextEdit{
199			{Span: newSpan(2, 8), NewText: "H\nI\nJ\n"},
200			{Span: newSpan(12, 14), NewText: "K\n"},
201		},
202		NoDiff: true, // diff algorithm produces different delete/insert pattern
203	},
204}
205
206func init() {
207	// expand all the spans to full versions
208	// we need them all to have their line number and column
209	for _, tc := range TestCases {
210		c := span.NewContentConverter("", []byte(tc.In))
211		for i := range tc.Edits {
212			tc.Edits[i].Span, _ = tc.Edits[i].Span.WithAll(c)
213		}
214		for i := range tc.LineEdits {
215			tc.LineEdits[i].Span, _ = tc.LineEdits[i].Span.WithAll(c)
216		}
217	}
218}
219
220func DiffTest(t *testing.T, compute diff.ComputeEdits) {
221	t.Helper()
222	for _, test := range TestCases {
223		t.Run(test.Name, func(t *testing.T) {
224			t.Helper()
225			edits := compute(span.URIFromPath("/"+test.Name), test.In, test.Out)
226			got := diff.ApplyEdits(test.In, edits)
227			unified := fmt.Sprint(diff.ToUnified(FileA, FileB, test.In, edits))
228			if got != test.Out {
229				t.Errorf("got patched:\n%v\nfrom diff:\n%v\nexpected:\n%v", got, unified, test.Out)
230			}
231			if !test.NoDiff && unified != test.Unified {
232				t.Errorf("got diff:\n%v\nexpected:\n%v", unified, test.Unified)
233			}
234		})
235	}
236}
237
238func newSpan(start, end int) span.Span {
239	return span.New("", span.NewPoint(0, 0, start), span.NewPoint(0, 0, end))
240}
241