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