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 "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
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
93 fset := NewFileSet()
94 for _, test := range tests {
95
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
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
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
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
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 !slices.Equal(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
139 src := test.source
140 if src == nil {
141
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
157 for _, offs := range lines {
158 f.AddLine(offs)
159 f.AddLineInfo(offs, "bar", 42)
160 }
161
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
177
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
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
222
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
245
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
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
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
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
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
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
376 fset.RemoveFile(a)
377 checkPos(apos3, "-")
378 checkPos(bpos3, "fileB:1:4")
379 checkNumFiles(1)
380
381
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 !slices.Equal(f.infos, test.want) {
476 t.Errorf("\ngot %+v, \nwant %+v", f.infos, test.want)
477 }
478 })
479 }
480 }
481
482 func TestIssue57490(t *testing.T) {
483
484 if debug {
485 defer func() {
486 if recover() == nil {
487 t.Errorf("got no panic")
488 }
489 }()
490 }
491
492 const fsize = 5
493 fset := NewFileSet()
494 base := fset.Base()
495 f := fset.AddFile("f", base, fsize)
496
497
498 if got := f.Offset(NoPos); got != 0 {
499 t.Errorf("offset = %d, want %d", got, 0)
500 }
501 if got := f.Offset(Pos(-1)); got != 0 {
502 t.Errorf("offset = %d, want %d", got, 0)
503 }
504 if got := f.Offset(Pos(base + fsize + 1)); got != fsize {
505 t.Errorf("offset = %d, want %d", got, fsize)
506 }
507
508
509 if got := f.Pos(-1); got != Pos(base) {
510 t.Errorf("pos = %d, want %d", got, base)
511 }
512 if got := f.Pos(fsize + 1); got != Pos(base+fsize) {
513 t.Errorf("pos = %d, want %d", got, base+fsize)
514 }
515
516
517 want := fmt.Sprintf("%s:1:1", f.Name())
518 if got := f.Position(Pos(-1)).String(); got != want {
519 t.Errorf("position = %s, want %s", got, want)
520 }
521 want = fmt.Sprintf("%s:1:%d", f.Name(), fsize+1)
522 if got := f.Position(Pos(fsize + 1)).String(); got != want {
523 t.Errorf("position = %s, want %s", got, want)
524 }
525
526
527 const xsize = fsize + 5
528 for offset := -xsize; offset < xsize; offset++ {
529 want1 := f.Offset(Pos(f.base + offset))
530 if got := f.Offset(f.Pos(offset)); got != want1 {
531 t.Errorf("offset = %d, want %d", got, want1)
532 }
533
534 want2 := f.Pos(offset)
535 if got := f.Pos(f.Offset(want2)); got != want2 {
536 t.Errorf("pos = %d, want %d", got, want2)
537 }
538 }
539 }
540
View as plain text