kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/util/span/span_test.go (about) 1 /* 2 * Copyright 2018 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package span 18 19 import ( 20 "fmt" 21 "strconv" 22 "testing" 23 24 "github.com/google/go-cmp/cmp/cmpopts" 25 "kythe.io/kythe/go/test/testutil" 26 "kythe.io/kythe/go/util/compare" 27 28 "google.golang.org/protobuf/proto" 29 30 cpb "kythe.io/kythe/proto/common_go_proto" 31 ) 32 33 func TestNormalizerPoint(t *testing.T) { 34 const text = `line 1 35 line 2 36 last line without newline` 37 38 tests := []struct{ p, expected *cpb.Point }{ 39 { 40 &cpb.Point{}, 41 &cpb.Point{ByteOffset: 0, LineNumber: 1, ColumnOffset: 0}, 42 }, 43 { 44 &cpb.Point{LineNumber: 1}, 45 &cpb.Point{ByteOffset: 0, LineNumber: 1, ColumnOffset: 0}, 46 }, 47 { 48 &cpb.Point{ByteOffset: 1}, 49 &cpb.Point{ByteOffset: 1, LineNumber: 1, ColumnOffset: 1}, 50 }, 51 { 52 &cpb.Point{ByteOffset: 6}, 53 &cpb.Point{ByteOffset: 6, LineNumber: 1, ColumnOffset: 6}, 54 }, 55 { 56 &cpb.Point{LineNumber: 1, ColumnOffset: 6}, 57 &cpb.Point{ByteOffset: 6, LineNumber: 1, ColumnOffset: 6}, 58 }, 59 { 60 &cpb.Point{ByteOffset: 7}, 61 &cpb.Point{ByteOffset: 7, LineNumber: 2, ColumnOffset: 0}, 62 }, 63 { 64 &cpb.Point{LineNumber: 2}, 65 &cpb.Point{ByteOffset: 7, LineNumber: 2, ColumnOffset: 0}, 66 }, 67 { 68 &cpb.Point{ByteOffset: 10}, 69 &cpb.Point{ByteOffset: 10, LineNumber: 2, ColumnOffset: 3}, 70 }, 71 { 72 &cpb.Point{ByteOffset: 13}, 73 &cpb.Point{ByteOffset: 13, LineNumber: 2, ColumnOffset: 6}, 74 }, 75 { 76 &cpb.Point{LineNumber: 2, ColumnOffset: 6}, 77 &cpb.Point{ByteOffset: 13, LineNumber: 2, ColumnOffset: 6}, 78 }, 79 { 80 &cpb.Point{LineNumber: 2, ColumnOffset: 7}, // past end of column 81 &cpb.Point{ByteOffset: 13, LineNumber: 2, ColumnOffset: 6}, 82 }, 83 { 84 &cpb.Point{LineNumber: 3}, 85 &cpb.Point{ByteOffset: 14, LineNumber: 3, ColumnOffset: 0}, 86 }, 87 { 88 &cpb.Point{LineNumber: 3, ColumnOffset: 5}, 89 &cpb.Point{ByteOffset: 19, LineNumber: 3, ColumnOffset: 5}, 90 }, 91 { 92 &cpb.Point{ByteOffset: 39}, 93 &cpb.Point{ByteOffset: 39, LineNumber: 3, ColumnOffset: 25}, 94 }, 95 { 96 &cpb.Point{ByteOffset: 40}, // past end of text 97 &cpb.Point{ByteOffset: 39, LineNumber: 3, ColumnOffset: 25}, 98 }, 99 { 100 &cpb.Point{LineNumber: 5, ColumnOffset: 5}, // past end of text 101 &cpb.Point{ByteOffset: 39, LineNumber: 3, ColumnOffset: 25}, 102 }, 103 { 104 &cpb.Point{ByteOffset: -1}, // before start of text 105 &cpb.Point{ByteOffset: 0, LineNumber: 1, ColumnOffset: 0}, 106 }, 107 { 108 &cpb.Point{LineNumber: -1, ColumnOffset: 5}, // before start of text 109 &cpb.Point{LineNumber: 1}, 110 }, 111 } 112 113 n := NewNormalizer([]byte(text)) 114 for _, test := range tests { 115 if p := n.Point(test.p); !proto.Equal(p, test.expected) { 116 t.Errorf("n.Point({%v}): expected {%v}; found {%v}", test.p, test.expected, p) 117 } 118 } 119 } 120 121 func TestPatcher(t *testing.T) { 122 tests := []struct { 123 oldText, newText string 124 125 oldSpans []*span 126 newSpans []*span 127 }{{ 128 oldText: "this is some text", 129 newText: "this is some changed text", 130 oldSpans: []*span{{0, 5}, {13, 17}, {13, 13}, {8, 17}}, 131 newSpans: []*span{{0, 5}, {21, 25}, {21, 21}, nil}, 132 }, { 133 oldText: "line one\nline two\nline three\nline four\n", 134 newText: "line one\nline three\nline two\nline four\n", 135 oldSpans: []*span{{0, 10}, {10, 14}, {16, 19}, {20, 24}, {25, 30}, {29, 38}}, 136 newSpans: []*span{{0, 10}, {10, 14}, nil, {22, 26}, nil, {29, 38}}, 137 }, { 138 oldText: "line three\n", 139 newText: "line one\ntwo\nthree\n", 140 oldSpans: []*span{ 141 {0, 4}, 142 {5, 5}, 143 {5, 10}, 144 }, 145 newSpans: []*span{ 146 {0, 4}, 147 {13, 13}, 148 {13, 18}, 149 }, 150 }} 151 152 for i, test := range tests { 153 t.Run(strconv.Itoa(i), func(t *testing.T) { 154 p, err := NewPatcher([]byte(test.oldText), []byte(test.newText)) 155 if err != nil { 156 t.Fatal(err) 157 } 158 if len(test.oldSpans) != len(test.newSpans) { 159 t.Fatalf("Invalid test: {%v}", test) 160 } 161 162 for i, s := range test.oldSpans { 163 start, end, exists := p.Patch(s.Start, s.End) 164 165 if ns := test.newSpans[i]; ns == nil && exists { 166 t.Errorf("Expected span not to exist in new text; received (%d, %d]", start, end) 167 } else if ns != nil && !exists { 168 t.Errorf("Expected span %v to exist in new text as %v; did not exist", s, ns) 169 } else if ns != nil && exists && (start != ns.Start || end != ns.End) { 170 t.Errorf("Expected %v; received (%d, %d]", ns, start, end) 171 } 172 } 173 }) 174 } 175 } 176 177 func p(bo, ln, co int32) *cpb.Point { 178 return &cpb.Point{ByteOffset: bo, LineNumber: ln, ColumnOffset: co} 179 } 180 181 func sp(start, end *cpb.Point) *cpb.Span { return &cpb.Span{Start: start, End: end} } 182 183 func TestPatchSpan(t *testing.T) { 184 tests := []struct { 185 oldText, newText string 186 187 oldSpans []*cpb.Span 188 newSpans []*cpb.Span 189 }{{ 190 oldText: "this is some text\nsecond line\n", 191 newText: "this is some text\nsecond line\n", 192 193 oldSpans: []*cpb.Span{ 194 sp(p(0, 1, 0), p(4, 1, 4)), 195 sp(p(13, 1, 13), p(17, 1, 17)), 196 }, 197 newSpans: []*cpb.Span{ 198 sp(p(0, 1, 0), p(4, 1, 4)), 199 sp(p(13, 1, 13), p(17, 1, 17)), 200 }, 201 }, { 202 oldText: "this is some text", 203 newText: "this is some changed text", 204 205 oldSpans: []*cpb.Span{ 206 sp(p(0, 1, 0), p(4, 1, 4)), 207 sp(p(13, 1, 13), p(17, 1, 17)), 208 nil, 209 }, 210 newSpans: []*cpb.Span{ 211 sp(p(0, 1, 0), p(4, 1, 4)), 212 sp(p(21, 1, 21), p(25, 1, 25)), 213 nil, 214 }, 215 }, { 216 oldText: "line one\nline two\nline three\nline four\n", 217 newText: "line one\nline three\nline two\npre line four\n", 218 219 oldSpans: []*cpb.Span{ 220 sp(p(0, 1, 0), p(5, 1, 5)), 221 sp(p(10, 2, 0), p(14, 2, 4)), 222 sp(p(15, 2, 6), p(18, 2, 11)), 223 sp(p(30, 4, 0), p(34, 4, 4)), 224 }, 225 newSpans: []*cpb.Span{ 226 sp(p(0, 1, 0), p(5, 1, 5)), 227 sp(p(10, 2, 0), p(14, 2, 4)), 228 nil, 229 sp(p(34, 4, 4), p(38, 4, 8)), 230 }, 231 }, { 232 oldText: "line one\nmoved\n", 233 newText: "line one\ninsert\nmoved\n", 234 235 oldSpans: []*cpb.Span{ 236 sp(p(5, 1, 5), p(8, 1, 8)), 237 sp(p(10, 2, 0), p(15, 2, 5)), 238 }, 239 newSpans: []*cpb.Span{ 240 sp(p(5, 1, 5), p(8, 1, 8)), 241 sp(p(17, 3, 0), p(22, 3, 5)), 242 }, 243 }, { 244 oldText: "line\ntwo\nthree\n", 245 newText: "line one\nline two\nthee\n", 246 247 oldSpans: []*cpb.Span{ 248 sp(p(0, 1, 0), p(0, 1, 0)), 249 sp(p(0, 1, 0), p(4, 1, 4)), 250 sp(p(5, 2, 0), p(8, 2, 3)), 251 sp(p(9, 3, 0), p(14, 3, 5)), 252 }, 253 newSpans: []*cpb.Span{ 254 sp(p(0, 1, 0), p(0, 1, 0)), 255 sp(p(0, 1, 0), p(4, 1, 4)), 256 sp(p(14, 2, 5), p(17, 2, 8)), 257 nil, 258 }, 259 }, { 260 oldText: "line three\n", 261 newText: "line one\ntwo\nthree\n", 262 263 oldSpans: []*cpb.Span{ 264 sp(p(0, 1, 0), p(4, 1, 4)), 265 sp(p(5, 1, 5), p(5, 1, 5)), 266 sp(p(5, 1, 5), p(10, 1, 10)), 267 }, 268 newSpans: []*cpb.Span{ 269 sp(p(0, 1, 0), p(4, 1, 4)), 270 sp(p(13, 3, 0), p(13, 3, 0)), 271 sp(p(13, 3, 0), p(18, 3, 5)), 272 }, 273 }, { 274 oldText: "line three\nfour\n", 275 newText: "line one\ntwo\nthree\nfour\n", 276 277 oldSpans: []*cpb.Span{ 278 sp(p(0, 1, 0), p(4, 1, 4)), 279 sp(p(5, 1, 5), p(10, 1, 10)), 280 sp(p(11, 2, 0), p(15, 2, 4)), 281 }, 282 newSpans: []*cpb.Span{ 283 sp(p(0, 1, 0), p(4, 1, 4)), 284 sp(p(13, 3, 0), p(18, 3, 5)), 285 sp(p(19, 4, 0), p(23, 4, 4)), 286 }, 287 }, { 288 oldText: "線\n動\n", 289 newText: "線\n入\n動\n", 290 291 oldSpans: []*cpb.Span{ 292 sp(p(0, 1, 0), p(3, 1, 3)), 293 sp(p(0, 1, 0), p(7, 2, 3)), 294 sp(p(4, 2, 0), p(7, 2, 3)), 295 }, 296 newSpans: []*cpb.Span{ 297 sp(p(0, 1, 0), p(3, 1, 3)), 298 nil, 299 sp(p(8, 3, 0), p(11, 3, 3)), 300 }, 301 }} 302 303 for i, test := range tests { 304 t.Run(strconv.Itoa(i), func(t *testing.T) { 305 if len(test.oldSpans) == 0 || len(test.oldSpans) != len(test.newSpans) { 306 t.Fatalf("Invalid test: {%v}", test) 307 } 308 309 p, err := NewPatcher([]byte(test.oldText), []byte(test.newText)) 310 if err != nil { 311 t.Fatal(err) 312 } 313 314 for i, s := range test.oldSpans { 315 t.Run(strconv.Itoa(i), func(t *testing.T) { 316 found, exists := p.PatchSpan(s) 317 318 if ns := test.newSpans[i]; s == nil && ns == nil { 319 if found != nil || !exists { 320 t.Errorf("Expected nil span to exist as nil span in new text; received %s %v", found, exists) 321 } 322 } else if ns == nil && exists { 323 t.Errorf("Expected span not to exist in new text; received %s", found) 324 } else if ns != nil && !exists { 325 t.Errorf("Expected span %s to exist in new text as %s; did not exist", s, ns) 326 } else if ns != nil && exists && !proto.Equal(found, ns) { 327 t.Errorf("(-expected; +found)\n%s", compare.ProtoDiff(ns, found)) 328 } 329 }) 330 } 331 }) 332 } 333 } 334 335 func TestMarshal(t *testing.T) { 336 expected := &Patcher{ 337 []diff{ 338 {Length: 2, Type: ins, Newlines: 1, FirstNewline: 2, LastNewline: 5}, 339 {Length: 10, Type: del, Newlines: 5, FirstNewline: 0, LastNewline: 9}, 340 {Length: 3, Type: eq, Newlines: 0, FirstNewline: -1, LastNewline: -1}, 341 {Length: 5, Type: ins, Newlines: 1, FirstNewline: 2, LastNewline: 2}, 342 }, 343 } 344 345 rec, err := expected.Marshal() 346 testutil.Fatalf(t, "Marshal: %v", err) 347 348 found, err := Unmarshal(rec) 349 testutil.Fatalf(t, "Unmarshal: %v", err) 350 351 // Check that the unmarshalled Patcher has correct prefix sums 352 ignoreTypeField := cmpopts.IgnoreFields(offsetTracker{}, "Type") 353 oldT := offsetTracker{Type: del} 354 newT := offsetTracker{Type: ins} 355 for _, d := range found.spans { 356 if diff := compare.ProtoDiff(oldT, d.oldPrefix, ignoreTypeField); diff != "" { 357 t.Fatalf("Old prefix sum incorrect (-expected; +found)\n%s", diff) 358 } 359 if diff := compare.ProtoDiff(newT, d.newPrefix, ignoreTypeField); diff != "" { 360 t.Fatalf("New prefix sum incorrect (-expected; +found)\n%s", diff) 361 } 362 363 oldT.Update(d) 364 newT.Update(d) 365 } 366 367 if diff := compare.ProtoDiff(expected.spans, found.spans, cmpopts.IgnoreUnexported(diff{})); diff != "" { 368 t.Errorf("(-expected; +found)\n%s", diff) 369 } 370 } 371 372 func TestLargeDiff(t *testing.T) { 373 var before, after string 374 for i := 0; i < 60; i++ { 375 before += fmt.Sprintf("%d\n", i) 376 after += fmt.Sprintf("%d\n", i/2) 377 } 378 379 patcher, err := NewPatcher([]byte(before), []byte(after)) 380 testutil.Fatalf(t, "NewPatcher: %v", err) 381 382 s := sp(p(26, 12, 1), p(28, 12, 3)) 383 expected := sp(p(55, 25, 1), p(57, 25, 3)) 384 385 found, exists := patcher.PatchSpan(s) 386 if !exists { 387 t.Fatal("Patched span does not exist") 388 } 389 390 if diff := compare.ProtoDiff(expected, found); diff != "" { 391 t.Errorf("(-expected; +found)\n%s", diff) 392 } 393 } 394 395 type span struct{ Start, End int32 } 396 397 func (s span) String() string { return fmt.Sprintf("(%d, %d]", s.Start, s.End) }