github.com/zebozhuang/go@v0.0.0-20200207033046-f8a98f6f5c5d/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 "sync" 11 "testing" 12 ) 13 14 func checkPos(t *testing.T, msg string, got, want Position) { 15 if got.Filename != want.Filename { 16 t.Errorf("%s: got filename = %q; want %q", msg, got.Filename, want.Filename) 17 } 18 if got.Offset != want.Offset { 19 t.Errorf("%s: got offset = %d; want %d", msg, got.Offset, want.Offset) 20 } 21 if got.Line != want.Line { 22 t.Errorf("%s: got line = %d; want %d", msg, got.Line, want.Line) 23 } 24 if got.Column != want.Column { 25 t.Errorf("%s: got column = %d; want %d", msg, got.Column, want.Column) 26 } 27 } 28 29 func TestNoPos(t *testing.T) { 30 if NoPos.IsValid() { 31 t.Errorf("NoPos should not be valid") 32 } 33 var fset *FileSet 34 checkPos(t, "nil NoPos", fset.Position(NoPos), Position{}) 35 fset = NewFileSet() 36 checkPos(t, "fset NoPos", fset.Position(NoPos), Position{}) 37 } 38 39 var tests = []struct { 40 filename string 41 source []byte // may be nil 42 size int 43 lines []int 44 }{ 45 {"a", []byte{}, 0, []int{}}, 46 {"b", []byte("01234"), 5, []int{0}}, 47 {"c", []byte("\n\n\n\n\n\n\n\n\n"), 9, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}}, 48 {"d", nil, 100, []int{0, 5, 10, 20, 30, 70, 71, 72, 80, 85, 90, 99}}, 49 {"e", nil, 777, []int{0, 80, 100, 120, 130, 180, 267, 455, 500, 567, 620}}, 50 {"f", []byte("package p\n\nimport \"fmt\""), 23, []int{0, 10, 11}}, 51 {"g", []byte("package p\n\nimport \"fmt\"\n"), 24, []int{0, 10, 11}}, 52 {"h", []byte("package p\n\nimport \"fmt\"\n "), 25, []int{0, 10, 11, 24}}, 53 } 54 55 func linecol(lines []int, offs int) (int, int) { 56 prevLineOffs := 0 57 for line, lineOffs := range lines { 58 if offs < lineOffs { 59 return line, offs - prevLineOffs + 1 60 } 61 prevLineOffs = lineOffs 62 } 63 return len(lines), offs - prevLineOffs + 1 64 } 65 66 func verifyPositions(t *testing.T, fset *FileSet, f *File, lines []int) { 67 for offs := 0; offs < f.Size(); offs++ { 68 p := f.Pos(offs) 69 offs2 := f.Offset(p) 70 if offs2 != offs { 71 t.Errorf("%s, Offset: got offset %d; want %d", f.Name(), offs2, offs) 72 } 73 line, col := linecol(lines, offs) 74 msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p) 75 checkPos(t, msg, f.Position(f.Pos(offs)), Position{f.Name(), offs, line, col}) 76 checkPos(t, msg, fset.Position(p), Position{f.Name(), offs, line, col}) 77 } 78 } 79 80 func makeTestSource(size int, lines []int) []byte { 81 src := make([]byte, size) 82 for _, offs := range lines { 83 if offs > 0 { 84 src[offs-1] = '\n' 85 } 86 } 87 return src 88 } 89 90 func TestPositions(t *testing.T) { 91 const delta = 7 // a non-zero base offset increment 92 fset := NewFileSet() 93 for _, test := range tests { 94 // verify consistency of test case 95 if test.source != nil && len(test.source) != test.size { 96 t.Errorf("%s: inconsistent test case: got file size %d; want %d", test.filename, len(test.source), test.size) 97 } 98 99 // add file and verify name and size 100 f := fset.AddFile(test.filename, fset.Base()+delta, test.size) 101 if f.Name() != test.filename { 102 t.Errorf("got filename %q; want %q", f.Name(), test.filename) 103 } 104 if f.Size() != test.size { 105 t.Errorf("%s: got file size %d; want %d", f.Name(), f.Size(), test.size) 106 } 107 if fset.File(f.Pos(0)) != f { 108 t.Errorf("%s: f.Pos(0) was not found in f", f.Name()) 109 } 110 111 // add lines individually and verify all positions 112 for i, offset := range test.lines { 113 f.AddLine(offset) 114 if f.LineCount() != i+1 { 115 t.Errorf("%s, AddLine: got line count %d; want %d", f.Name(), f.LineCount(), i+1) 116 } 117 // adding the same offset again should be ignored 118 f.AddLine(offset) 119 if f.LineCount() != i+1 { 120 t.Errorf("%s, AddLine: got unchanged line count %d; want %d", f.Name(), f.LineCount(), i+1) 121 } 122 verifyPositions(t, fset, f, test.lines[0:i+1]) 123 } 124 125 // add lines with SetLines and verify all positions 126 if ok := f.SetLines(test.lines); !ok { 127 t.Errorf("%s: SetLines failed", f.Name()) 128 } 129 if f.LineCount() != len(test.lines) { 130 t.Errorf("%s, SetLines: got line count %d; want %d", f.Name(), f.LineCount(), len(test.lines)) 131 } 132 verifyPositions(t, fset, f, test.lines) 133 134 // add lines with SetLinesForContent and verify all positions 135 src := test.source 136 if src == nil { 137 // no test source available - create one from scratch 138 src = makeTestSource(test.size, test.lines) 139 } 140 f.SetLinesForContent(src) 141 if f.LineCount() != len(test.lines) { 142 t.Errorf("%s, SetLinesForContent: got line count %d; want %d", f.Name(), f.LineCount(), len(test.lines)) 143 } 144 verifyPositions(t, fset, f, test.lines) 145 } 146 } 147 148 func TestLineInfo(t *testing.T) { 149 fset := NewFileSet() 150 f := fset.AddFile("foo", fset.Base(), 500) 151 lines := []int{0, 42, 77, 100, 210, 220, 277, 300, 333, 401} 152 // add lines individually and provide alternative line information 153 for _, offs := range lines { 154 f.AddLine(offs) 155 f.AddLineInfo(offs, "bar", 42) 156 } 157 // verify positions for all offsets 158 for offs := 0; offs <= f.Size(); offs++ { 159 p := f.Pos(offs) 160 _, col := linecol(lines, offs) 161 msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p) 162 checkPos(t, msg, f.Position(f.Pos(offs)), Position{"bar", offs, 42, col}) 163 checkPos(t, msg, fset.Position(p), Position{"bar", offs, 42, col}) 164 } 165 } 166 167 func TestFiles(t *testing.T) { 168 fset := NewFileSet() 169 for i, test := range tests { 170 base := fset.Base() 171 if i%2 == 1 { 172 // Setting a negative base is equivalent to 173 // fset.Base(), so test some of each. 174 base = -1 175 } 176 fset.AddFile(test.filename, base, test.size) 177 j := 0 178 fset.Iterate(func(f *File) bool { 179 if f.Name() != tests[j].filename { 180 t.Errorf("got filename = %s; want %s", f.Name(), tests[j].filename) 181 } 182 j++ 183 return true 184 }) 185 if j != i+1 { 186 t.Errorf("got %d files; want %d", j, i+1) 187 } 188 } 189 } 190 191 // FileSet.File should return nil if Pos is past the end of the FileSet. 192 func TestFileSetPastEnd(t *testing.T) { 193 fset := NewFileSet() 194 for _, test := range tests { 195 fset.AddFile(test.filename, fset.Base(), test.size) 196 } 197 if f := fset.File(Pos(fset.Base())); f != nil { 198 t.Errorf("got %v, want nil", f) 199 } 200 } 201 202 func TestFileSetCacheUnlikely(t *testing.T) { 203 fset := NewFileSet() 204 offsets := make(map[string]int) 205 for _, test := range tests { 206 offsets[test.filename] = fset.Base() 207 fset.AddFile(test.filename, fset.Base(), test.size) 208 } 209 for file, pos := range offsets { 210 f := fset.File(Pos(pos)) 211 if f.Name() != file { 212 t.Errorf("got %q at position %d, want %q", f.Name(), pos, file) 213 } 214 } 215 } 216 217 // issue 4345. Test that concurrent use of FileSet.Pos does not trigger a 218 // race in the FileSet position cache. 219 func TestFileSetRace(t *testing.T) { 220 fset := NewFileSet() 221 for i := 0; i < 100; i++ { 222 fset.AddFile(fmt.Sprintf("file-%d", i), fset.Base(), 1031) 223 } 224 max := int32(fset.Base()) 225 var stop sync.WaitGroup 226 r := rand.New(rand.NewSource(7)) 227 for i := 0; i < 2; i++ { 228 r := rand.New(rand.NewSource(r.Int63())) 229 stop.Add(1) 230 go func() { 231 for i := 0; i < 1000; i++ { 232 fset.Position(Pos(r.Int31n(max))) 233 } 234 stop.Done() 235 }() 236 } 237 stop.Wait() 238 } 239 240 // issue 16548. Test that concurrent use of File.AddLine and FileSet.PositionFor 241 // does not trigger a race in the FileSet position cache. 242 func TestFileSetRace2(t *testing.T) { 243 const N = 1e3 244 var ( 245 fset = NewFileSet() 246 file = fset.AddFile("", -1, N) 247 ch = make(chan int, 2) 248 ) 249 250 go func() { 251 for i := 0; i < N; i++ { 252 file.AddLine(i) 253 } 254 ch <- 1 255 }() 256 257 go func() { 258 pos := file.Pos(0) 259 for i := 0; i < N; i++ { 260 fset.PositionFor(pos, false) 261 } 262 ch <- 1 263 }() 264 265 <-ch 266 <-ch 267 } 268 269 func TestPositionFor(t *testing.T) { 270 src := []byte(` 271 foo 272 b 273 ar 274 //line :100 275 foobar 276 //line bar:3 277 done 278 `) 279 280 const filename = "foo" 281 fset := NewFileSet() 282 f := fset.AddFile(filename, fset.Base(), len(src)) 283 f.SetLinesForContent(src) 284 285 // verify position info 286 for i, offs := range f.lines { 287 got1 := f.PositionFor(f.Pos(offs), false) 288 got2 := f.PositionFor(f.Pos(offs), true) 289 got3 := f.Position(f.Pos(offs)) 290 want := Position{filename, offs, i + 1, 1} 291 checkPos(t, "1. PositionFor unadjusted", got1, want) 292 checkPos(t, "1. PositionFor adjusted", got2, want) 293 checkPos(t, "1. Position", got3, want) 294 } 295 296 // manually add //line info on lines l1, l2 297 const l1, l2 = 5, 7 298 f.AddLineInfo(f.lines[l1-1], "", 100) 299 f.AddLineInfo(f.lines[l2-1], "bar", 3) 300 301 // unadjusted position info must remain unchanged 302 for i, offs := range f.lines { 303 got1 := f.PositionFor(f.Pos(offs), false) 304 want := Position{filename, offs, i + 1, 1} 305 checkPos(t, "2. PositionFor unadjusted", got1, want) 306 } 307 308 // adjusted position info should have changed 309 for i, offs := range f.lines { 310 got2 := f.PositionFor(f.Pos(offs), true) 311 got3 := f.Position(f.Pos(offs)) 312 want := Position{filename, offs, i + 1, 1} 313 // manually compute wanted filename and line 314 line := want.Line 315 if i+1 >= l1 { 316 want.Filename = "" 317 want.Line = line - l1 + 100 318 } 319 if i+1 >= l2 { 320 want.Filename = "bar" 321 want.Line = line - l2 + 3 322 } 323 checkPos(t, "3. PositionFor adjusted", got2, want) 324 checkPos(t, "3. Position", got3, want) 325 } 326 }