Source file
src/go/token/position_test.go
1
2
3
4
5 package token
6
7 import (
8 "fmt"
9 "math/rand"
10 "slices"
11 "strings"
12 "sync"
13 "testing"
14 )
15
16 func checkPos(t *testing.T, msg string, got, want Position) {
17 if got.Filename != want.Filename {
18 t.Errorf("%s: got filename = %q; want %q", msg, got.Filename, want.Filename)
19 }
20 if got.Offset != want.Offset {
21 t.Errorf("%s: got offset = %d; want %d", msg, got.Offset, want.Offset)
22 }
23 if got.Line != want.Line {
24 t.Errorf("%s: got line = %d; want %d", msg, got.Line, want.Line)
25 }
26 if got.Column != want.Column {
27 t.Errorf("%s: got column = %d; want %d", msg, got.Column, want.Column)
28 }
29 }
30
31 func TestNoPos(t *testing.T) {
32 if NoPos.IsValid() {
33 t.Errorf("NoPos should not be valid")
34 }
35 var fset *FileSet
36 checkPos(t, "nil NoPos", fset.Position(NoPos), Position{})
37 fset = NewFileSet()
38 checkPos(t, "fset NoPos", fset.Position(NoPos), Position{})
39 }
40
41 var tests = []struct {
42 filename string
43 source []byte
44 size int
45 lines []int
46 }{
47 {"a", []byte{}, 0, []int{}},
48 {"b", []byte("01234"), 5, []int{0}},
49 {"c", []byte("\n\n\n\n\n\n\n\n\n"), 9, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}},
50 {"d", nil, 100, []int{0, 5, 10, 20, 30, 70, 71, 72, 80, 85, 90, 99}},
51 {"e", nil, 777, []int{0, 80, 100, 120, 130, 180, 267, 455, 500, 567, 620}},
52 {"f", []byte("package p\n\nimport \"fmt\""), 23, []int{0, 10, 11}},
53 {"g", []byte("package p\n\nimport \"fmt\"\n"), 24, []int{0, 10, 11}},
54 {"h", []byte("package p\n\nimport \"fmt\"\n "), 25, []int{0, 10, 11, 24}},
55 }
56
57 func linecol(lines []int, offs int) (int, int) {
58 prevLineOffs := 0
59 for line, lineOffs := range lines {
60 if offs < lineOffs {
61 return line, offs - prevLineOffs + 1
62 }
63 prevLineOffs = lineOffs
64 }
65 return len(lines), offs - prevLineOffs + 1
66 }
67
68 func verifyPositions(t *testing.T, fset *FileSet, f *File, lines []int) {
69 for offs := 0; offs < f.Size(); offs++ {
70 p := f.Pos(offs)
71 offs2 := f.Offset(p)
72 if offs2 != offs {
73 t.Errorf("%s, Offset: got offset %d; want %d", f.Name(), offs2, offs)
74 }
75 line, col := linecol(lines, offs)
76 msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p)
77 checkPos(t, msg, f.Position(f.Pos(offs)), Position{f.Name(), offs, line, col})
78 checkPos(t, msg, fset.Position(p), Position{f.Name(), offs, line, col})
79 }
80 }
81
82 func makeTestSource(size int, lines []int) []byte {
83 src := make([]byte, size)
84 for _, offs := range lines {
85 if offs > 0 {
86 src[offs-1] = '\n'
87 }
88 }
89 return src
90 }
91
92 func TestPositions(t *testing.T) {
93 const delta = 7
94 fset := NewFileSet()
95 for _, test := range tests {
96
97 if test.source != nil && len(test.source) != test.size {
98 t.Errorf("%s: inconsistent test case: got file size %d; want %d", test.filename, len(test.source), test.size)
99 }
100
101
102 f := fset.AddFile(test.filename, fset.Base()+delta, test.size)
103 if f.Name() != test.filename {
104 t.Errorf("got filename %q; want %q", f.Name(), test.filename)
105 }
106 if f.Size() != test.size {
107 t.Errorf("%s: got file size %d; want %d", f.Name(), f.Size(), test.size)
108 }
109 if fset.File(f.Pos(0)) != f {
110 t.Errorf("%s: f.Pos(0) was not found in f", f.Name())
111 }
112
113
114 for i, offset := range test.lines {
115 f.AddLine(offset)
116 if f.LineCount() != i+1 {
117 t.Errorf("%s, AddLine: got line count %d; want %d", f.Name(), f.LineCount(), i+1)
118 }
119
120 f.AddLine(offset)
121 if f.LineCount() != i+1 {
122 t.Errorf("%s, AddLine: got unchanged line count %d; want %d", f.Name(), f.LineCount(), i+1)
123 }
124 verifyPositions(t, fset, f, test.lines[0:i+1])
125 }
126
127
128 if ok := f.SetLines(test.lines); !ok {
129 t.Errorf("%s: SetLines failed", f.Name())
130 }
131 if f.LineCount() != len(test.lines) {
132 t.Errorf("%s, SetLines: got line count %d; want %d", f.Name(), f.LineCount(), len(test.lines))
133 }
134 if !slices.Equal(f.Lines(), test.lines) {
135 t.Errorf("%s, Lines after SetLines(v): got %v; want %v", f.Name(), f.Lines(), test.lines)
136 }
137 verifyPositions(t, fset, f, test.lines)
138
139
140 src := test.source
141 if src == nil {
142
143 src = makeTestSource(test.size, test.lines)
144 }
145 f.SetLinesForContent(src)
146 if f.LineCount() != len(test.lines) {
147 t.Errorf("%s, SetLinesForContent: got line count %d; want %d", f.Name(), f.LineCount(), len(test.lines))
148 }
149 verifyPositions(t, fset, f, test.lines)
150 }
151 }
152
153 func TestLineInfo(t *testing.T) {
154 fset := NewFileSet()
155 f := fset.AddFile("foo", fset.Base(), 500)
156 lines := []int{0, 42, 77, 100, 210, 220, 277, 300, 333, 401}
157
158 for _, offs := range lines {
159 f.AddLine(offs)
160 f.AddLineInfo(offs, "bar", 42)
161 }
162
163 for offs := 0; offs <= f.Size(); offs++ {
164 p := f.Pos(offs)
165 _, col := linecol(lines, offs)
166 msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p)
167 checkPos(t, msg, f.Position(f.Pos(offs)), Position{"bar", offs, 42, col})
168 checkPos(t, msg, fset.Position(p), Position{"bar", offs, 42, col})
169 }
170 }
171
172 func TestFiles(t *testing.T) {
173 fset := NewFileSet()
174 for i, test := range tests {
175 base := fset.Base()
176 if i%2 == 1 {
177
178
179 base = -1
180 }
181 fset.AddFile(test.filename, base, test.size)
182 j := 0
183 fset.Iterate(func(f *File) bool {
184 if f.Name() != tests[j].filename {
185 t.Errorf("got filename = %s; want %s", f.Name(), tests[j].filename)
186 }
187 j++
188 return true
189 })
190 if j != i+1 {
191 t.Errorf("got %d files; want %d", j, i+1)
192 }
193 }
194 }
195
196
197 func TestFileSetPastEnd(t *testing.T) {
198 fset := NewFileSet()
199 for _, test := range tests {
200 fset.AddFile(test.filename, fset.Base(), test.size)
201 }
202 if f := fset.File(Pos(fset.Base())); f != nil {
203 t.Errorf("got %v, want nil", f)
204 }
205 }
206
207 func TestFileSetCacheUnlikely(t *testing.T) {
208 fset := NewFileSet()
209 offsets := make(map[string]int)
210 for _, test := range tests {
211 offsets[test.filename] = fset.Base()
212 fset.AddFile(test.filename, fset.Base(), test.size)
213 }
214 for file, pos := range offsets {
215 f := fset.File(Pos(pos))
216 if f.Name() != file {
217 t.Errorf("got %q at position %d, want %q", f.Name(), pos, file)
218 }
219 }
220 }
221
222
223
224 func TestFileSetRace(t *testing.T) {
225 fset := NewFileSet()
226 for i := 0; i < 100; i++ {
227 fset.AddFile(fmt.Sprintf("file-%d", i), fset.Base(), 1031)
228 }
229 max := int32(fset.Base())
230 var stop sync.WaitGroup
231 r := rand.New(rand.NewSource(7))
232 for i := 0; i < 2; i++ {
233 r := rand.New(rand.NewSource(r.Int63()))
234 stop.Add(1)
235 go func() {
236 for i := 0; i < 1000; i++ {
237 fset.Position(Pos(r.Int31n(max)))
238 }
239 stop.Done()
240 }()
241 }
242 stop.Wait()
243 }
244
245
246
247 func TestFileSetRace2(t *testing.T) {
248 const N = 1e3
249 var (
250 fset = NewFileSet()
251 file = fset.AddFile("", -1, N)
252 ch = make(chan int, 2)
253 )
254
255 go func() {
256 for i := 0; i < N; i++ {
257 file.AddLine(i)
258 }
259 ch <- 1
260 }()
261
262 go func() {
263 pos := file.Pos(0)
264 for i := 0; i < N; i++ {
265 fset.PositionFor(pos, false)
266 }
267 ch <- 1
268 }()
269
270 <-ch
271 <-ch
272 }
273
274 func TestPositionFor(t *testing.T) {
275 src := []byte(`
276 foo
277 b
278 ar
279 //line :100
280 foobar
281 //line bar:3
282 done
283 `)
284
285 const filename = "foo"
286 fset := NewFileSet()
287 f := fset.AddFile(filename, fset.Base(), len(src))
288 f.SetLinesForContent(src)
289
290
291 for i, offs := range f.lines {
292 got1 := f.PositionFor(f.Pos(offs), false)
293 got2 := f.PositionFor(f.Pos(offs), true)
294 got3 := f.Position(f.Pos(offs))
295 want := Position{filename, offs, i + 1, 1}
296 checkPos(t, "1. PositionFor unadjusted", got1, want)
297 checkPos(t, "1. PositionFor adjusted", got2, want)
298 checkPos(t, "1. Position", got3, want)
299 }
300
301
302 const l1, l2 = 5, 7
303 f.AddLineInfo(f.lines[l1-1], "", 100)
304 f.AddLineInfo(f.lines[l2-1], "bar", 3)
305
306
307 for i, offs := range f.lines {
308 got1 := f.PositionFor(f.Pos(offs), false)
309 want := Position{filename, offs, i + 1, 1}
310 checkPos(t, "2. PositionFor unadjusted", got1, want)
311 }
312
313
314 for i, offs := range f.lines {
315 got2 := f.PositionFor(f.Pos(offs), true)
316 got3 := f.Position(f.Pos(offs))
317 want := Position{filename, offs, i + 1, 1}
318
319 line := want.Line
320 if i+1 >= l1 {
321 want.Filename = ""
322 want.Line = line - l1 + 100
323 }
324 if i+1 >= l2 {
325 want.Filename = "bar"
326 want.Line = line - l2 + 3
327 }
328 checkPos(t, "3. PositionFor adjusted", got2, want)
329 checkPos(t, "3. Position", got3, want)
330 }
331 }
332
333 func TestLineStart(t *testing.T) {
334 const src = "one\ntwo\nthree\n"
335 fset := NewFileSet()
336 f := fset.AddFile("input", -1, len(src))
337 f.SetLinesForContent([]byte(src))
338
339 for line := 1; line <= 3; line++ {
340 pos := f.LineStart(line)
341 position := fset.Position(pos)
342 if position.Line != line || position.Column != 1 {
343 t.Errorf("LineStart(%d) returned wrong pos %d: %s", line, pos, position)
344 }
345 }
346 }
347
348 func TestRemoveFile(t *testing.T) {
349 contentA := []byte("this\nis\nfileA")
350 contentB := []byte("this\nis\nfileB")
351 fset := NewFileSet()
352 a := fset.AddFile("fileA", -1, len(contentA))
353 a.SetLinesForContent(contentA)
354 b := fset.AddFile("fileB", -1, len(contentB))
355 b.SetLinesForContent(contentB)
356
357 checkPos := func(pos Pos, want string) {
358 if got := fset.Position(pos).String(); got != want {
359 t.Errorf("Position(%d) = %s, want %s", pos, got, want)
360 }
361 }
362 checkNumFiles := func(want int) {
363 got := 0
364 fset.Iterate(func(*File) bool { got++; return true })
365 if got != want {
366 t.Errorf("Iterate called %d times, want %d", got, want)
367 }
368 }
369
370 apos3 := a.Pos(3)
371 bpos3 := b.Pos(3)
372 checkPos(apos3, "fileA:1:4")
373 checkPos(bpos3, "fileB:1:4")
374 checkNumFiles(2)
375
376
377 fset.RemoveFile(a)
378 checkPos(apos3, "-")
379 checkPos(bpos3, "fileB:1:4")
380 checkNumFiles(1)
381
382
383 fset.RemoveFile(a)
384 checkPos(apos3, "-")
385 checkPos(bpos3, "fileB:1:4")
386 checkNumFiles(1)
387 }
388
389 func TestFileAddLineColumnInfo(t *testing.T) {
390 const (
391 filename = "test.go"
392 filesize = 100
393 )
394
395 tests := []struct {
396 name string
397 infos []lineInfo
398 want []lineInfo
399 }{
400 {
401 name: "normal",
402 infos: []lineInfo{
403 {Offset: 10, Filename: filename, Line: 2, Column: 1},
404 {Offset: 50, Filename: filename, Line: 3, Column: 1},
405 {Offset: 80, Filename: filename, Line: 4, Column: 2},
406 },
407 want: []lineInfo{
408 {Offset: 10, Filename: filename, Line: 2, Column: 1},
409 {Offset: 50, Filename: filename, Line: 3, Column: 1},
410 {Offset: 80, Filename: filename, Line: 4, Column: 2},
411 },
412 },
413 {
414 name: "offset1 == file size",
415 infos: []lineInfo{
416 {Offset: filesize, Filename: filename, Line: 2, Column: 1},
417 },
418 want: nil,
419 },
420 {
421 name: "offset1 > file size",
422 infos: []lineInfo{
423 {Offset: filesize + 1, Filename: filename, Line: 2, Column: 1},
424 },
425 want: nil,
426 },
427 {
428 name: "offset2 == file size",
429 infos: []lineInfo{
430 {Offset: 10, Filename: filename, Line: 2, Column: 1},
431 {Offset: filesize, Filename: filename, Line: 3, Column: 1},
432 },
433 want: []lineInfo{
434 {Offset: 10, Filename: filename, Line: 2, Column: 1},
435 },
436 },
437 {
438 name: "offset2 > file size",
439 infos: []lineInfo{
440 {Offset: 10, Filename: filename, Line: 2, Column: 1},
441 {Offset: filesize + 1, Filename: filename, Line: 3, Column: 1},
442 },
443 want: []lineInfo{
444 {Offset: 10, Filename: filename, Line: 2, Column: 1},
445 },
446 },
447 {
448 name: "offset2 == offset1",
449 infos: []lineInfo{
450 {Offset: 10, Filename: filename, Line: 2, Column: 1},
451 {Offset: 10, Filename: filename, Line: 3, Column: 1},
452 },
453 want: []lineInfo{
454 {Offset: 10, Filename: filename, Line: 2, Column: 1},
455 },
456 },
457 {
458 name: "offset2 < offset1",
459 infos: []lineInfo{
460 {Offset: 10, Filename: filename, Line: 2, Column: 1},
461 {Offset: 9, Filename: filename, Line: 3, Column: 1},
462 },
463 want: []lineInfo{
464 {Offset: 10, Filename: filename, Line: 2, Column: 1},
465 },
466 },
467 }
468
469 for _, test := range tests {
470 t.Run(test.name, func(t *testing.T) {
471 fs := NewFileSet()
472 f := fs.AddFile(filename, -1, filesize)
473 for _, info := range test.infos {
474 f.AddLineColumnInfo(info.Offset, info.Filename, info.Line, info.Column)
475 }
476 if !slices.Equal(f.infos, test.want) {
477 t.Errorf("\ngot %+v, \nwant %+v", f.infos, test.want)
478 }
479 })
480 }
481 }
482
483 func TestIssue57490(t *testing.T) {
484
485 if debug {
486 defer func() {
487 if recover() == nil {
488 t.Errorf("got no panic")
489 }
490 }()
491 }
492
493 const fsize = 5
494 fset := NewFileSet()
495 base := fset.Base()
496 f := fset.AddFile("f", base, fsize)
497
498
499 if got := f.Offset(NoPos); got != 0 {
500 t.Errorf("offset = %d, want %d", got, 0)
501 }
502 if got := f.Offset(Pos(-1)); got != 0 {
503 t.Errorf("offset = %d, want %d", got, 0)
504 }
505 if got := f.Offset(Pos(base + fsize + 1)); got != fsize {
506 t.Errorf("offset = %d, want %d", got, fsize)
507 }
508
509
510 if got := f.Pos(-1); got != Pos(base) {
511 t.Errorf("pos = %d, want %d", got, base)
512 }
513 if got := f.Pos(fsize + 1); got != Pos(base+fsize) {
514 t.Errorf("pos = %d, want %d", got, base+fsize)
515 }
516
517
518 want := fmt.Sprintf("%s:1:1", f.Name())
519 if got := f.Position(Pos(-1)).String(); got != want {
520 t.Errorf("position = %s, want %s", got, want)
521 }
522 want = fmt.Sprintf("%s:1:%d", f.Name(), fsize+1)
523 if got := f.Position(Pos(fsize + 1)).String(); got != want {
524 t.Errorf("position = %s, want %s", got, want)
525 }
526
527
528 const xsize = fsize + 5
529 for offset := -xsize; offset < xsize; offset++ {
530 want1 := f.Offset(Pos(f.base + offset))
531 if got := f.Offset(f.Pos(offset)); got != want1 {
532 t.Errorf("offset = %d, want %d", got, want1)
533 }
534
535 want2 := f.Pos(offset)
536 if got := f.Pos(f.Offset(want2)); got != want2 {
537 t.Errorf("pos = %d, want %d", got, want2)
538 }
539 }
540 }
541
542 func TestFileSet_AddExistingFiles(t *testing.T) {
543 fset := NewFileSet()
544
545 check := func(descr, want string) {
546 t.Helper()
547 if got := fsetString(fset); got != want {
548 t.Errorf("%s: got %s, want %s", descr, got, want)
549 }
550 }
551
552 fileA := fset.AddFile("A", -1, 3)
553 fileB := fset.AddFile("B", -1, 5)
554 _ = fileB
555 check("after AddFile [AB]", "{A:1-4 B:5-10}")
556
557 fset.AddExistingFiles()
558 check("after AddExistingFiles []", "{A:1-4 B:5-10}")
559
560 fileC := NewFileSet().AddFile("C", 100, 5)
561 fileD := NewFileSet().AddFile("D", 200, 5)
562 fset.AddExistingFiles(fileC, fileA, fileD, fileC)
563 check("after AddExistingFiles [CADC]", "{A:1-4 B:5-10 C:100-105 D:200-205}")
564
565 fileE := fset.AddFile("E", -1, 3)
566 _ = fileE
567 check("after AddFile [E]", "{A:1-4 B:5-10 C:100-105 D:200-205 E:206-209}")
568 }
569
570 func fsetString(fset *FileSet) string {
571 var buf strings.Builder
572 buf.WriteRune('{')
573 sep := ""
574 fset.Iterate(func(f *File) bool {
575 fmt.Fprintf(&buf, "%s%s:%d-%d", sep, f.Name(), f.Base(), f.Base()+f.Size())
576 sep = " "
577 return true
578 })
579 buf.WriteRune('}')
580 return buf.String()
581 }
582
View as plain text