github.com/AndrienkoAleksandr/go@v0.0.19/src/go/token/position_test.go (about) 1 // Copyright 2010 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 token 6 7 import ( 8 "fmt" 9 "math/rand" 10 "reflect" 11 "sync" 12 "testing" 13 ) 14 15 func checkPos(t *testing.T, msg string, got, want Position) { 16 if got.Filename != want.Filename { 17 t.Errorf("%s: got filename = %q; want %q", msg, got.Filename, want.Filename) 18 } 19 if got.Offset != want.Offset { 20 t.Errorf("%s: got offset = %d; want %d", msg, got.Offset, want.Offset) 21 } 22 if got.Line != want.Line { 23 t.Errorf("%s: got line = %d; want %d", msg, got.Line, want.Line) 24 } 25 if got.Column != want.Column { 26 t.Errorf("%s: got column = %d; want %d", msg, got.Column, want.Column) 27 } 28 } 29 30 func TestNoPos(t *testing.T) { 31 if NoPos.IsValid() { 32 t.Errorf("NoPos should not be valid") 33 } 34 var fset *FileSet 35 checkPos(t, "nil NoPos", fset.Position(NoPos), Position{}) 36 fset = NewFileSet() 37 checkPos(t, "fset NoPos", fset.Position(NoPos), Position{}) 38 } 39 40 var tests = []struct { 41 filename string 42 source []byte // may be nil 43 size int 44 lines []int 45 }{ 46 {"a", []byte{}, 0, []int{}}, 47 {"b", []byte("01234"), 5, []int{0}}, 48 {"c", []byte("\n\n\n\n\n\n\n\n\n"), 9, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}}, 49 {"d", nil, 100, []int{0, 5, 10, 20, 30, 70, 71, 72, 80, 85, 90, 99}}, 50 {"e", nil, 777, []int{0, 80, 100, 120, 130, 180, 267, 455, 500, 567, 620}}, 51 {"f", []byte("package p\n\nimport \"fmt\""), 23, []int{0, 10, 11}}, 52 {"g", []byte("package p\n\nimport \"fmt\"\n"), 24, []int{0, 10, 11}}, 53 {"h", []byte("package p\n\nimport \"fmt\"\n "), 25, []int{0, 10, 11, 24}}, 54 } 55 56 func linecol(lines []int, offs int) (int, int) { 57 prevLineOffs := 0 58 for line, lineOffs := range lines { 59 if offs < lineOffs { 60 return line, offs - prevLineOffs + 1 61 } 62 prevLineOffs = lineOffs 63 } 64 return len(lines), offs - prevLineOffs + 1 65 } 66 67 func verifyPositions(t *testing.T, fset *FileSet, f *File, lines []int) { 68 for offs := 0; offs < f.Size(); offs++ { 69 p := f.Pos(offs) 70 offs2 := f.Offset(p) 71 if offs2 != offs { 72 t.Errorf("%s, Offset: got offset %d; want %d", f.Name(), offs2, offs) 73 } 74 line, col := linecol(lines, offs) 75 msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p) 76 checkPos(t, msg, f.Position(f.Pos(offs)), Position{f.Name(), offs, line, col}) 77 checkPos(t, msg, fset.Position(p), Position{f.Name(), offs, line, col}) 78 } 79 } 80 81 func makeTestSource(size int, lines []int) []byte { 82 src := make([]byte, size) 83 for _, offs := range lines { 84 if offs > 0 { 85 src[offs-1] = '\n' 86 } 87 } 88 return src 89 } 90 91 func TestPositions(t *testing.T) { 92 const delta = 7 // a non-zero base offset increment 93 fset := NewFileSet() 94 for _, test := range tests { 95 // verify consistency of test case 96 if test.source != nil && len(test.source) != test.size { 97 t.Errorf("%s: inconsistent test case: got file size %d; want %d", test.filename, len(test.source), test.size) 98 } 99 100 // add file and verify name and size 101 f := fset.AddFile(test.filename, fset.Base()+delta, test.size) 102 if f.Name() != test.filename { 103 t.Errorf("got filename %q; want %q", f.Name(), test.filename) 104 } 105 if f.Size() != test.size { 106 t.Errorf("%s: got file size %d; want %d", f.Name(), f.Size(), test.size) 107 } 108 if fset.File(f.Pos(0)) != f { 109 t.Errorf("%s: f.Pos(0) was not found in f", f.Name()) 110 } 111 112 // add lines individually and verify all positions 113 for i, offset := range test.lines { 114 f.AddLine(offset) 115 if f.LineCount() != i+1 { 116 t.Errorf("%s, AddLine: got line count %d; want %d", f.Name(), f.LineCount(), i+1) 117 } 118 // adding the same offset again should be ignored 119 f.AddLine(offset) 120 if f.LineCount() != i+1 { 121 t.Errorf("%s, AddLine: got unchanged line count %d; want %d", f.Name(), f.LineCount(), i+1) 122 } 123 verifyPositions(t, fset, f, test.lines[0:i+1]) 124 } 125 126 // add lines with SetLines and verify all positions 127 if ok := f.SetLines(test.lines); !ok { 128 t.Errorf("%s: SetLines failed", f.Name()) 129 } 130 if f.LineCount() != len(test.lines) { 131 t.Errorf("%s, SetLines: got line count %d; want %d", f.Name(), f.LineCount(), len(test.lines)) 132 } 133 if !reflect.DeepEqual(f.Lines(), test.lines) { 134 t.Errorf("%s, Lines after SetLines(v): got %v; want %v", f.Name(), f.Lines(), test.lines) 135 } 136 verifyPositions(t, fset, f, test.lines) 137 138 // add lines with SetLinesForContent and verify all positions 139 src := test.source 140 if src == nil { 141 // no test source available - create one from scratch 142 src = makeTestSource(test.size, test.lines) 143 } 144 f.SetLinesForContent(src) 145 if f.LineCount() != len(test.lines) { 146 t.Errorf("%s, SetLinesForContent: got line count %d; want %d", f.Name(), f.LineCount(), len(test.lines)) 147 } 148 verifyPositions(t, fset, f, test.lines) 149 } 150 } 151 152 func TestLineInfo(t *testing.T) { 153 fset := NewFileSet() 154 f := fset.AddFile("foo", fset.Base(), 500) 155 lines := []int{0, 42, 77, 100, 210, 220, 277, 300, 333, 401} 156 // add lines individually and provide alternative line information 157 for _, offs := range lines { 158 f.AddLine(offs) 159 f.AddLineInfo(offs, "bar", 42) 160 } 161 // verify positions for all offsets 162 for offs := 0; offs <= f.Size(); offs++ { 163 p := f.Pos(offs) 164 _, col := linecol(lines, offs) 165 msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p) 166 checkPos(t, msg, f.Position(f.Pos(offs)), Position{"bar", offs, 42, col}) 167 checkPos(t, msg, fset.Position(p), Position{"bar", offs, 42, col}) 168 } 169 } 170 171 func TestFiles(t *testing.T) { 172 fset := NewFileSet() 173 for i, test := range tests { 174 base := fset.Base() 175 if i%2 == 1 { 176 // Setting a negative base is equivalent to 177 // fset.Base(), so test some of each. 178 base = -1 179 } 180 fset.AddFile(test.filename, base, test.size) 181 j := 0 182 fset.Iterate(func(f *File) bool { 183 if f.Name() != tests[j].filename { 184 t.Errorf("got filename = %s; want %s", f.Name(), tests[j].filename) 185 } 186 j++ 187 return true 188 }) 189 if j != i+1 { 190 t.Errorf("got %d files; want %d", j, i+1) 191 } 192 } 193 } 194 195 // FileSet.File should return nil if Pos is past the end of the FileSet. 196 func TestFileSetPastEnd(t *testing.T) { 197 fset := NewFileSet() 198 for _, test := range tests { 199 fset.AddFile(test.filename, fset.Base(), test.size) 200 } 201 if f := fset.File(Pos(fset.Base())); f != nil { 202 t.Errorf("got %v, want nil", f) 203 } 204 } 205 206 func TestFileSetCacheUnlikely(t *testing.T) { 207 fset := NewFileSet() 208 offsets := make(map[string]int) 209 for _, test := range tests { 210 offsets[test.filename] = fset.Base() 211 fset.AddFile(test.filename, fset.Base(), test.size) 212 } 213 for file, pos := range offsets { 214 f := fset.File(Pos(pos)) 215 if f.Name() != file { 216 t.Errorf("got %q at position %d, want %q", f.Name(), pos, file) 217 } 218 } 219 } 220 221 // issue 4345. Test that concurrent use of FileSet.Pos does not trigger a 222 // race in the FileSet position cache. 223 func TestFileSetRace(t *testing.T) { 224 fset := NewFileSet() 225 for i := 0; i < 100; i++ { 226 fset.AddFile(fmt.Sprintf("file-%d", i), fset.Base(), 1031) 227 } 228 max := int32(fset.Base()) 229 var stop sync.WaitGroup 230 r := rand.New(rand.NewSource(7)) 231 for i := 0; i < 2; i++ { 232 r := rand.New(rand.NewSource(r.Int63())) 233 stop.Add(1) 234 go func() { 235 for i := 0; i < 1000; i++ { 236 fset.Position(Pos(r.Int31n(max))) 237 } 238 stop.Done() 239 }() 240 } 241 stop.Wait() 242 } 243 244 // issue 16548. Test that concurrent use of File.AddLine and FileSet.PositionFor 245 // does not trigger a race in the FileSet position cache. 246 func TestFileSetRace2(t *testing.T) { 247 const N = 1e3 248 var ( 249 fset = NewFileSet() 250 file = fset.AddFile("", -1, N) 251 ch = make(chan int, 2) 252 ) 253 254 go func() { 255 for i := 0; i < N; i++ { 256 file.AddLine(i) 257 } 258 ch <- 1 259 }() 260 261 go func() { 262 pos := file.Pos(0) 263 for i := 0; i < N; i++ { 264 fset.PositionFor(pos, false) 265 } 266 ch <- 1 267 }() 268 269 <-ch 270 <-ch 271 } 272 273 func TestPositionFor(t *testing.T) { 274 src := []byte(` 275 foo 276 b 277 ar 278 //line :100 279 foobar 280 //line bar:3 281 done 282 `) 283 284 const filename = "foo" 285 fset := NewFileSet() 286 f := fset.AddFile(filename, fset.Base(), len(src)) 287 f.SetLinesForContent(src) 288 289 // verify position info 290 for i, offs := range f.lines { 291 got1 := f.PositionFor(f.Pos(offs), false) 292 got2 := f.PositionFor(f.Pos(offs), true) 293 got3 := f.Position(f.Pos(offs)) 294 want := Position{filename, offs, i + 1, 1} 295 checkPos(t, "1. PositionFor unadjusted", got1, want) 296 checkPos(t, "1. PositionFor adjusted", got2, want) 297 checkPos(t, "1. Position", got3, want) 298 } 299 300 // manually add //line info on lines l1, l2 301 const l1, l2 = 5, 7 302 f.AddLineInfo(f.lines[l1-1], "", 100) 303 f.AddLineInfo(f.lines[l2-1], "bar", 3) 304 305 // unadjusted position info must remain unchanged 306 for i, offs := range f.lines { 307 got1 := f.PositionFor(f.Pos(offs), false) 308 want := Position{filename, offs, i + 1, 1} 309 checkPos(t, "2. PositionFor unadjusted", got1, want) 310 } 311 312 // adjusted position info should have changed 313 for i, offs := range f.lines { 314 got2 := f.PositionFor(f.Pos(offs), true) 315 got3 := f.Position(f.Pos(offs)) 316 want := Position{filename, offs, i + 1, 1} 317 // manually compute wanted filename and line 318 line := want.Line 319 if i+1 >= l1 { 320 want.Filename = "" 321 want.Line = line - l1 + 100 322 } 323 if i+1 >= l2 { 324 want.Filename = "bar" 325 want.Line = line - l2 + 3 326 } 327 checkPos(t, "3. PositionFor adjusted", got2, want) 328 checkPos(t, "3. Position", got3, want) 329 } 330 } 331 332 func TestLineStart(t *testing.T) { 333 const src = "one\ntwo\nthree\n" 334 fset := NewFileSet() 335 f := fset.AddFile("input", -1, len(src)) 336 f.SetLinesForContent([]byte(src)) 337 338 for line := 1; line <= 3; line++ { 339 pos := f.LineStart(line) 340 position := fset.Position(pos) 341 if position.Line != line || position.Column != 1 { 342 t.Errorf("LineStart(%d) returned wrong pos %d: %s", line, pos, position) 343 } 344 } 345 } 346 347 func TestRemoveFile(t *testing.T) { 348 contentA := []byte("this\nis\nfileA") 349 contentB := []byte("this\nis\nfileB") 350 fset := NewFileSet() 351 a := fset.AddFile("fileA", -1, len(contentA)) 352 a.SetLinesForContent(contentA) 353 b := fset.AddFile("fileB", -1, len(contentB)) 354 b.SetLinesForContent(contentB) 355 356 checkPos := func(pos Pos, want string) { 357 if got := fset.Position(pos).String(); got != want { 358 t.Errorf("Position(%d) = %s, want %s", pos, got, want) 359 } 360 } 361 checkNumFiles := func(want int) { 362 got := 0 363 fset.Iterate(func(*File) bool { got++; return true }) 364 if got != want { 365 t.Errorf("Iterate called %d times, want %d", got, want) 366 } 367 } 368 369 apos3 := a.Pos(3) 370 bpos3 := b.Pos(3) 371 checkPos(apos3, "fileA:1:4") 372 checkPos(bpos3, "fileB:1:4") 373 checkNumFiles(2) 374 375 // After removal, queries on fileA fail. 376 fset.RemoveFile(a) 377 checkPos(apos3, "-") 378 checkPos(bpos3, "fileB:1:4") 379 checkNumFiles(1) 380 381 // idempotent / no effect 382 fset.RemoveFile(a) 383 checkPos(apos3, "-") 384 checkPos(bpos3, "fileB:1:4") 385 checkNumFiles(1) 386 } 387 388 func TestFileAddLineColumnInfo(t *testing.T) { 389 const ( 390 filename = "test.go" 391 filesize = 100 392 ) 393 394 tests := []struct { 395 name string 396 infos []lineInfo 397 want []lineInfo 398 }{ 399 { 400 name: "normal", 401 infos: []lineInfo{ 402 {Offset: 10, Filename: filename, Line: 2, Column: 1}, 403 {Offset: 50, Filename: filename, Line: 3, Column: 1}, 404 {Offset: 80, Filename: filename, Line: 4, Column: 2}, 405 }, 406 want: []lineInfo{ 407 {Offset: 10, Filename: filename, Line: 2, Column: 1}, 408 {Offset: 50, Filename: filename, Line: 3, Column: 1}, 409 {Offset: 80, Filename: filename, Line: 4, Column: 2}, 410 }, 411 }, 412 { 413 name: "offset1 == file size", 414 infos: []lineInfo{ 415 {Offset: filesize, Filename: filename, Line: 2, Column: 1}, 416 }, 417 want: nil, 418 }, 419 { 420 name: "offset1 > file size", 421 infos: []lineInfo{ 422 {Offset: filesize + 1, Filename: filename, Line: 2, Column: 1}, 423 }, 424 want: nil, 425 }, 426 { 427 name: "offset2 == file size", 428 infos: []lineInfo{ 429 {Offset: 10, Filename: filename, Line: 2, Column: 1}, 430 {Offset: filesize, Filename: filename, Line: 3, Column: 1}, 431 }, 432 want: []lineInfo{ 433 {Offset: 10, Filename: filename, Line: 2, Column: 1}, 434 }, 435 }, 436 { 437 name: "offset2 > file size", 438 infos: []lineInfo{ 439 {Offset: 10, Filename: filename, Line: 2, Column: 1}, 440 {Offset: filesize + 1, Filename: filename, Line: 3, Column: 1}, 441 }, 442 want: []lineInfo{ 443 {Offset: 10, Filename: filename, Line: 2, Column: 1}, 444 }, 445 }, 446 { 447 name: "offset2 == offset1", 448 infos: []lineInfo{ 449 {Offset: 10, Filename: filename, Line: 2, Column: 1}, 450 {Offset: 10, Filename: filename, Line: 3, Column: 1}, 451 }, 452 want: []lineInfo{ 453 {Offset: 10, Filename: filename, Line: 2, Column: 1}, 454 }, 455 }, 456 { 457 name: "offset2 < offset1", 458 infos: []lineInfo{ 459 {Offset: 10, Filename: filename, Line: 2, Column: 1}, 460 {Offset: 9, Filename: filename, Line: 3, Column: 1}, 461 }, 462 want: []lineInfo{ 463 {Offset: 10, Filename: filename, Line: 2, Column: 1}, 464 }, 465 }, 466 } 467 468 for _, test := range tests { 469 t.Run(test.name, func(t *testing.T) { 470 fs := NewFileSet() 471 f := fs.AddFile(filename, -1, filesize) 472 for _, info := range test.infos { 473 f.AddLineColumnInfo(info.Offset, info.Filename, info.Line, info.Column) 474 } 475 if !reflect.DeepEqual(f.infos, test.want) { 476 t.Errorf("\ngot %+v, \nwant %+v", f.infos, test.want) 477 } 478 }) 479 } 480 }