1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package types2_test
31
32 import (
33 "bytes"
34 "cmd/compile/internal/syntax"
35 "flag"
36 "fmt"
37 "go/build"
38 "go/build/constraint"
39 "internal/buildcfg"
40 "internal/testenv"
41 "os"
42 "path/filepath"
43 "reflect"
44 "regexp"
45 "runtime"
46 "slices"
47 "strconv"
48 "strings"
49 "testing"
50
51 . "cmd/compile/internal/types2"
52 )
53
54 var (
55 haltOnError = flag.Bool("halt", false, "halt on error")
56 verifyErrors = flag.Bool("verify", false, "verify errors (rather than list them) in TestManual")
57 )
58
59 func parseFiles(t *testing.T, filenames []string, srcs [][]byte, mode syntax.Mode) ([]*syntax.File, []error) {
60 var files []*syntax.File
61 var errlist []error
62 errh := func(err error) { errlist = append(errlist, err) }
63 for i, filename := range filenames {
64 base := syntax.NewFileBase(filename)
65 r := bytes.NewReader(srcs[i])
66 file, err := syntax.Parse(base, r, errh, nil, mode)
67 if file == nil {
68 t.Fatalf("%s: %s", filename, err)
69 }
70 files = append(files, file)
71 }
72 return files, errlist
73 }
74
75 func unpackError(err error) (syntax.Pos, string) {
76 switch err := err.(type) {
77 case syntax.Error:
78 return err.Pos, err.Msg
79 case Error:
80 return err.Pos, err.Msg
81 default:
82 return nopos, err.Error()
83 }
84 }
85
86
87 func absDiff(x, y uint) uint {
88 if x < y {
89 return y - x
90 }
91 return x - y
92 }
93
94
95
96
97 func parseFlags(src []byte, flags *flag.FlagSet) error {
98
99 const prefix = "//"
100 if !bytes.HasPrefix(src, []byte(prefix)) {
101 return nil
102 }
103 src = src[len(prefix):]
104 if i := bytes.Index(src, []byte("-")); i < 0 || len(bytes.TrimSpace(src[:i])) != 0 {
105 return nil
106 }
107 end := bytes.Index(src, []byte("\n"))
108 const maxLen = 256
109 if end < 0 || end > maxLen {
110 return fmt.Errorf("flags comment line too long")
111 }
112
113 return flags.Parse(strings.Fields(string(src[:end])))
114 }
115
116
117
118
119
120
121
122
123
124
125 func testFiles(t *testing.T, filenames []string, srcs [][]byte, colDelta uint, manual bool, opts ...func(*Config)) {
126 if len(filenames) == 0 {
127 t.Fatal("no source files")
128 }
129
130
131 files, errlist := parseFiles(t, filenames, srcs, 0)
132 pkgName := "<no package>"
133 if len(files) > 0 {
134 pkgName = files[0].PkgName.Value
135 }
136 listErrors := manual && !*verifyErrors
137 if listErrors && len(errlist) > 0 {
138 t.Errorf("--- %s:", pkgName)
139 for _, err := range errlist {
140 t.Error(err)
141 }
142 }
143
144
145 var conf Config
146 conf.Trace = manual && testing.Verbose()
147 conf.Importer = defaultImporter()
148 conf.Error = func(err error) {
149 if *haltOnError {
150 defer panic(err)
151 }
152 if listErrors {
153 t.Error(err)
154 return
155 }
156 errlist = append(errlist, err)
157 }
158
159
160 for _, opt := range opts {
161 opt(&conf)
162 }
163
164
165 var goexperiment string
166 flags := flag.NewFlagSet("", flag.PanicOnError)
167 flags.StringVar(&conf.GoVersion, "lang", "", "")
168 flags.StringVar(&goexperiment, "goexperiment", "", "")
169 flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
170 if err := parseFlags(srcs[0], flags); err != nil {
171 t.Fatal(err)
172 }
173
174 if goexperiment != "" {
175 revert := setGOEXPERIMENT(goexperiment)
176 defer revert()
177 }
178
179
180 info := Info{
181 Types: make(map[syntax.Expr]TypeAndValue),
182 Instances: make(map[*syntax.Name]Instance),
183 Defs: make(map[*syntax.Name]Object),
184 Uses: make(map[*syntax.Name]Object),
185 Implicits: make(map[syntax.Node]Object),
186 Selections: make(map[*syntax.SelectorExpr]*Selection),
187 Scopes: make(map[syntax.Node]*Scope),
188 FileVersions: make(map[*syntax.PosBase]string),
189 }
190
191
192 conf.Check(pkgName, files, &info)
193 if listErrors {
194 return
195 }
196
197
198 errmap := make(map[string]map[uint][]syntax.Error)
199 for i, filename := range filenames {
200 if m := syntax.CommentMap(bytes.NewReader(srcs[i]), regexp.MustCompile("^ ERRORx? ")); len(m) > 0 {
201 errmap[filename] = m
202 }
203 }
204
205
206 var indices []int
207 for _, err := range errlist {
208 gotPos, gotMsg := unpackError(err)
209
210
211 filename := gotPos.Base().Filename()
212 filemap := errmap[filename]
213 line := gotPos.Line()
214 var errList []syntax.Error
215 if filemap != nil {
216 errList = filemap[line]
217 }
218
219
220 indices = indices[:0]
221 for i, want := range errList {
222 pattern, substr := strings.CutPrefix(want.Msg, " ERROR ")
223 if !substr {
224 var found bool
225 pattern, found = strings.CutPrefix(want.Msg, " ERRORx ")
226 if !found {
227 panic("unreachable")
228 }
229 }
230 unquoted, err := strconv.Unquote(strings.TrimSpace(pattern))
231 if err != nil {
232 t.Errorf("%s:%d:%d: invalid ERROR pattern (cannot unquote %s)", filename, line, want.Pos.Col(), pattern)
233 continue
234 }
235 if substr {
236 if !strings.Contains(gotMsg, unquoted) {
237 continue
238 }
239 } else {
240 rx, err := regexp.Compile(unquoted)
241 if err != nil {
242 t.Errorf("%s:%d:%d: %v", filename, line, want.Pos.Col(), err)
243 continue
244 }
245 if !rx.MatchString(gotMsg) {
246 continue
247 }
248 }
249 indices = append(indices, i)
250 }
251 if len(indices) == 0 {
252 t.Errorf("%s: no error expected: %q", gotPos, gotMsg)
253 continue
254 }
255
256
257
258 index := -1
259 var delta uint
260 for _, i := range indices {
261 if d := absDiff(gotPos.Col(), errList[i].Pos.Col()); index < 0 || d < delta {
262 index, delta = i, d
263 }
264 }
265
266
267 if delta > colDelta {
268 t.Errorf("%s: got col = %d; want %d", gotPos, gotPos.Col(), errList[index].Pos.Col())
269 }
270
271
272 if n := len(errList) - 1; n > 0 {
273
274 copy(errList[index:], errList[index+1:])
275 filemap[line] = errList[:n]
276 } else {
277
278 delete(filemap, line)
279 }
280
281
282 if len(filemap) == 0 {
283 delete(errmap, filename)
284 }
285 }
286
287
288 if len(errmap) > 0 {
289 t.Errorf("--- %s: unreported errors:", pkgName)
290 for filename, filemap := range errmap {
291 for line, errList := range filemap {
292 for _, err := range errList {
293 t.Errorf("%s:%d:%d: %s", filename, line, err.Pos.Col(), err.Msg)
294 }
295 }
296 }
297 }
298 }
299
300
301
302 func boolFieldAddr(conf *Config, name string) *bool {
303 v := reflect.Indirect(reflect.ValueOf(conf))
304 return (*bool)(v.FieldByName(name).Addr().UnsafePointer())
305 }
306
307
308
309
310
311 func setGOEXPERIMENT(goexperiment string) func() {
312 exp, err := buildcfg.ParseGOEXPERIMENT(runtime.GOOS, runtime.GOARCH, goexperiment)
313 if err != nil {
314 panic(err)
315 }
316 old := buildcfg.Experiment
317 buildcfg.Experiment = *exp
318 return func() { buildcfg.Experiment = old }
319 }
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335 func TestManual(t *testing.T) {
336 testenv.MustHaveGoBuild(t)
337
338 filenames := flag.Args()
339 if len(filenames) == 0 {
340 filenames = []string{filepath.FromSlash("testdata/manual.go")}
341 }
342
343 info, err := os.Stat(filenames[0])
344 if err != nil {
345 t.Fatalf("TestManual: %v", err)
346 }
347
348 DefPredeclaredTestFuncs()
349 if info.IsDir() {
350 if len(filenames) > 1 {
351 t.Fatal("TestManual: must have only one directory argument")
352 }
353 testDir(t, filenames[0], 0, true)
354 } else {
355 testPkg(t, filenames, 0, true)
356 }
357 }
358
359 func TestLongConstants(t *testing.T) {
360 format := `package longconst; const _ = %s /* ERROR "constant overflow" */; const _ = %s // ERROR "excessively long constant"`
361 src := fmt.Sprintf(format, strings.Repeat("1", 9999), strings.Repeat("1", 10001))
362 testFiles(t, []string{"longconst.go"}, [][]byte{[]byte(src)}, 0, false)
363 }
364
365 func withSizes(sizes Sizes) func(*Config) {
366 return func(cfg *Config) {
367 cfg.Sizes = sizes
368 }
369 }
370
371
372
373
374 func TestIndexRepresentability(t *testing.T) {
375 const src = `package index; var s []byte; var _ = s[int64 /* ERRORx "int64\\(1\\) << 40 \\(.*\\) overflows int" */ (1) << 40]`
376 testFiles(t, []string{"index.go"}, [][]byte{[]byte(src)}, 0, false, withSizes(&StdSizes{4, 4}))
377 }
378
379 func TestIssue47243_TypedRHS(t *testing.T) {
380
381
382 const src = `package issue47243; var a uint64; var _ = a << uint64(4294967296)`
383 testFiles(t, []string{"p.go"}, [][]byte{[]byte(src)}, 0, false, withSizes(&StdSizes{4, 4}))
384 }
385
386 func TestCheck(t *testing.T) {
387 DefPredeclaredTestFuncs()
388 testDirFiles(t, "../../../../internal/types/testdata/check", 50, false)
389 }
390 func TestSpec(t *testing.T) { testDirFiles(t, "../../../../internal/types/testdata/spec", 20, false) }
391 func TestExamples(t *testing.T) {
392 testDirFiles(t, "../../../../internal/types/testdata/examples", 125, false)
393 }
394 func TestFixedbugs(t *testing.T) {
395 testDirFiles(t, "../../../../internal/types/testdata/fixedbugs", 100, false)
396 }
397 func TestLocal(t *testing.T) { testDirFiles(t, "testdata/local", 0, false) }
398
399 func testDirFiles(t *testing.T, dir string, colDelta uint, manual bool) {
400 testenv.MustHaveGoBuild(t)
401 dir = filepath.FromSlash(dir)
402
403 fis, err := os.ReadDir(dir)
404 if err != nil {
405 t.Error(err)
406 return
407 }
408
409 for _, fi := range fis {
410 path := filepath.Join(dir, fi.Name())
411
412
413 if fi.IsDir() {
414 testDir(t, path, colDelta, manual)
415 } else {
416 t.Run(filepath.Base(path), func(t *testing.T) {
417 testPkg(t, []string{path}, colDelta, manual)
418 })
419 }
420 }
421 }
422
423 func testDir(t *testing.T, dir string, colDelta uint, manual bool) {
424 fis, err := os.ReadDir(dir)
425 if err != nil {
426 t.Error(err)
427 return
428 }
429
430 var filenames []string
431 for _, fi := range fis {
432 filenames = append(filenames, filepath.Join(dir, fi.Name()))
433 }
434
435 t.Run(filepath.Base(dir), func(t *testing.T) {
436 testPkg(t, filenames, colDelta, manual)
437 })
438 }
439
440 func testPkg(t *testing.T, filenames []string, colDelta uint, manual bool) {
441 fs := filenames[:0]
442 srcs := make([][]byte, 0, len(filenames))
443 for _, filename := range filenames {
444 src, err := os.ReadFile(filename)
445 if err != nil {
446 t.Fatalf("could not read %s: %v", filename, err)
447 }
448 if !shouldTest(src) {
449 continue
450 }
451 fs = append(fs, filename)
452 srcs = append(srcs, src)
453 }
454 if len(fs) == 0 {
455 t.Skip("all files skipped by build tags")
456 }
457 testFiles(t, fs, srcs, colDelta, manual)
458 }
459
460
461
462 func shouldTest(src []byte) bool {
463 match := func(tag string) bool {
464
465 if slices.Contains(build.Default.ReleaseTags, tag) {
466 return true
467 }
468 return tag == runtime.GOOS || tag == runtime.GOARCH
469 }
470 for line := range strings.SplitSeq(string(src), "\n") {
471 if strings.HasPrefix(line, "package ") {
472 break
473 }
474 if expr, err := constraint.Parse(line); err == nil {
475 return expr.Eval(match)
476 }
477 }
478 return true
479 }
480
View as plain text