]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/types2/check_test.go
go/types, types2: remove global goVersion flag (cleanup)
[gostls13.git] / src / cmd / compile / internal / types2 / check_test.go
1 // Copyright 2011 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 // This file implements a typechecker test harness. The packages specified
6 // in tests are typechecked. Error messages reported by the typechecker are
7 // compared against the error messages expected in the test files.
8 //
9 // Expected errors are indicated in the test files by putting a comment
10 // of the form /* ERROR "rx" */ immediately following an offending token.
11 // The harness will verify that an error matching the regular expression
12 // rx is reported at that source position. Consecutive comments may be
13 // used to indicate multiple errors for the same token position.
14 //
15 // For instance, the following test file indicates that a "not declared"
16 // error should be reported for the undeclared variable x:
17 //
18 //      package p
19 //      func f() {
20 //              _ = x /* ERROR "not declared" */ + 1
21 //      }
22
23 package types2_test
24
25 import (
26         "bytes"
27         "cmd/compile/internal/syntax"
28         "flag"
29         "fmt"
30         "internal/testenv"
31         "os"
32         "path/filepath"
33         "regexp"
34         "sort"
35         "strings"
36         "testing"
37
38         . "cmd/compile/internal/types2"
39 )
40
41 var (
42         haltOnError  = flag.Bool("halt", false, "halt on error")
43         verifyErrors = flag.Bool("verify", false, "verify errors (rather than list them) in TestManual")
44 )
45
46 func parseFiles(t *testing.T, filenames []string, mode syntax.Mode) ([]*syntax.File, []error) {
47         var files []*syntax.File
48         var errlist []error
49         errh := func(err error) { errlist = append(errlist, err) }
50         for _, filename := range filenames {
51                 file, err := syntax.ParseFile(filename, errh, nil, mode)
52                 if file == nil {
53                         t.Fatalf("%s: %s", filename, err)
54                 }
55                 files = append(files, file)
56         }
57         return files, errlist
58 }
59
60 func unpackError(err error) syntax.Error {
61         switch err := err.(type) {
62         case syntax.Error:
63                 return err
64         case Error:
65                 return syntax.Error{Pos: err.Pos, Msg: err.Msg}
66         default:
67                 return syntax.Error{Msg: err.Error()}
68         }
69 }
70
71 // delta returns the absolute difference between x and y.
72 func delta(x, y uint) uint {
73         switch {
74         case x < y:
75                 return y - x
76         case x > y:
77                 return x - y
78         default:
79                 return 0
80         }
81 }
82
83 // Note: parseFlags is identical to the version in go/types which is
84 //       why it has a src argument even though here it is always nil.
85
86 // parseFlags parses flags from the first line of the given source
87 // (from src if present, or by reading from the file) if the line
88 // starts with "//" (line comment) followed by "-" (possibly with
89 // spaces between). Otherwise the line is ignored.
90 func parseFlags(filename string, src []byte, flags *flag.FlagSet) error {
91         // If there is no src, read from the file.
92         const maxLen = 256
93         if len(src) == 0 {
94                 f, err := os.Open(filename)
95                 if err != nil {
96                         return err
97                 }
98
99                 var buf [maxLen]byte
100                 n, err := f.Read(buf[:])
101                 if err != nil {
102                         return err
103                 }
104                 src = buf[:n]
105         }
106
107         // we must have a line comment that starts with a "-"
108         const prefix = "//"
109         if !bytes.HasPrefix(src, []byte(prefix)) {
110                 return nil // first line is not a line comment
111         }
112         src = src[len(prefix):]
113         if i := bytes.Index(src, []byte("-")); i < 0 || len(bytes.TrimSpace(src[:i])) != 0 {
114                 return nil // comment doesn't start with a "-"
115         }
116         end := bytes.Index(src, []byte("\n"))
117         if end < 0 || end > maxLen {
118                 return fmt.Errorf("flags comment line too long")
119         }
120
121         return flags.Parse(strings.Fields(string(src[:end])))
122 }
123
124 func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) {
125         if len(filenames) == 0 {
126                 t.Fatal("no source files")
127         }
128
129         var conf Config
130         flags := flag.NewFlagSet("", flag.PanicOnError)
131         flags.StringVar(&conf.GoVersion, "lang", "", "")
132         flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
133         if err := parseFlags(filenames[0], nil, flags); err != nil {
134                 t.Fatal(err)
135         }
136
137         files, errlist := parseFiles(t, filenames, 0)
138
139         pkgName := "<no package>"
140         if len(files) > 0 {
141                 pkgName = files[0].PkgName.Value
142         }
143
144         listErrors := manual && !*verifyErrors
145         if listErrors && len(errlist) > 0 {
146                 t.Errorf("--- %s:", pkgName)
147                 for _, err := range errlist {
148                         t.Error(err)
149                 }
150         }
151
152         // typecheck and collect typechecker errors
153         conf.Trace = manual && testing.Verbose()
154         conf.Importer = defaultImporter()
155         conf.Error = func(err error) {
156                 if *haltOnError {
157                         defer panic(err)
158                 }
159                 if listErrors {
160                         t.Error(err)
161                         return
162                 }
163                 errlist = append(errlist, err)
164         }
165         conf.Check(pkgName, files, nil)
166
167         if listErrors {
168                 return
169         }
170
171         // sort errlist in source order
172         sort.Slice(errlist, func(i, j int) bool {
173                 pi := unpackError(errlist[i]).Pos
174                 pj := unpackError(errlist[j]).Pos
175                 return pi.Cmp(pj) < 0
176         })
177
178         // collect expected errors
179         errmap := make(map[string]map[uint][]syntax.Error)
180         for _, filename := range filenames {
181                 f, err := os.Open(filename)
182                 if err != nil {
183                         t.Error(err)
184                         continue
185                 }
186                 if m := syntax.ErrorMap(f); len(m) > 0 {
187                         errmap[filename] = m
188                 }
189                 f.Close()
190         }
191
192         // match against found errors
193         for _, err := range errlist {
194                 got := unpackError(err)
195
196                 // find list of errors for the respective error line
197                 filename := got.Pos.Base().Filename()
198                 filemap := errmap[filename]
199                 line := got.Pos.Line()
200                 var list []syntax.Error
201                 if filemap != nil {
202                         list = filemap[line]
203                 }
204                 // list may be nil
205
206                 // one of errors in list should match the current error
207                 index := -1 // list index of matching message, if any
208                 for i, want := range list {
209                         rx, err := regexp.Compile(want.Msg)
210                         if err != nil {
211                                 t.Errorf("%s:%d:%d: %v", filename, line, want.Pos.Col(), err)
212                                 continue
213                         }
214                         if rx.MatchString(got.Msg) {
215                                 index = i
216                                 break
217                         }
218                 }
219                 if index < 0 {
220                         t.Errorf("%s: no error expected: %q", got.Pos, got.Msg)
221                         continue
222                 }
223
224                 // column position must be within expected colDelta
225                 want := list[index]
226                 if delta(got.Pos.Col(), want.Pos.Col()) > colDelta {
227                         t.Errorf("%s: got col = %d; want %d", got.Pos, got.Pos.Col(), want.Pos.Col())
228                 }
229
230                 // eliminate from list
231                 if n := len(list) - 1; n > 0 {
232                         // not the last entry - slide entries down (don't reorder)
233                         copy(list[index:], list[index+1:])
234                         filemap[line] = list[:n]
235                 } else {
236                         // last entry - remove list from filemap
237                         delete(filemap, line)
238                 }
239
240                 // if filemap is empty, eliminate from errmap
241                 if len(filemap) == 0 {
242                         delete(errmap, filename)
243                 }
244         }
245
246         // there should be no expected errors left
247         if len(errmap) > 0 {
248                 t.Errorf("--- %s: unreported errors:", pkgName)
249                 for filename, filemap := range errmap {
250                         for line, list := range filemap {
251                                 for _, err := range list {
252                                         t.Errorf("%s:%d:%d: %s", filename, line, err.Pos.Col(), err.Msg)
253                                 }
254                         }
255                 }
256         }
257 }
258
259 // TestManual is for manual testing of a package - either provided
260 // as a list of filenames belonging to the package, or a directory
261 // name containing the package files - after the test arguments
262 // (and a separating "--"). For instance, to test the package made
263 // of the files foo.go and bar.go, use:
264 //
265 //      go test -run Manual -- foo.go bar.go
266 //
267 // If no source arguments are provided, the file testdata/manual.go
268 // is used instead.
269 // Provide the -verify flag to verify errors against ERROR comments
270 // in the input files rather than having a list of errors reported.
271 // The accepted Go language version can be controlled with the -lang
272 // flag.
273 func TestManual(t *testing.T) {
274         testenv.MustHaveGoBuild(t)
275
276         filenames := flag.Args()
277         if len(filenames) == 0 {
278                 filenames = []string{filepath.FromSlash("testdata/manual.go")}
279         }
280
281         info, err := os.Stat(filenames[0])
282         if err != nil {
283                 t.Fatalf("TestManual: %v", err)
284         }
285
286         DefPredeclaredTestFuncs()
287         if info.IsDir() {
288                 if len(filenames) > 1 {
289                         t.Fatal("TestManual: must have only one directory argument")
290                 }
291                 testDir(t, filenames[0], 0, true)
292         } else {
293                 testFiles(t, filenames, 0, true)
294         }
295 }
296
297 // TODO(gri) go/types has extra TestLongConstants and TestIndexRepresentability tests
298
299 func TestCheck(t *testing.T) {
300         DefPredeclaredTestFuncs()
301         testDirFiles(t, "../../../../internal/types/testdata/check", 50, false) // TODO(gri) narrow column tolerance
302 }
303 func TestSpec(t *testing.T) { testDirFiles(t, "../../../../internal/types/testdata/spec", 0, false) }
304 func TestExamples(t *testing.T) {
305         testDirFiles(t, "../../../../internal/types/testdata/examples", 45, false)
306 } // TODO(gri) narrow column tolerance
307 func TestFixedbugs(t *testing.T) {
308         testDirFiles(t, "../../../../internal/types/testdata/fixedbugs", 100, false)
309 }                            // TODO(gri) narrow column tolerance
310 func TestLocal(t *testing.T) { testDirFiles(t, "testdata/local", 0, false) }
311
312 func testDirFiles(t *testing.T, dir string, colDelta uint, manual bool) {
313         testenv.MustHaveGoBuild(t)
314         dir = filepath.FromSlash(dir)
315
316         fis, err := os.ReadDir(dir)
317         if err != nil {
318                 t.Error(err)
319                 return
320         }
321
322         for _, fi := range fis {
323                 path := filepath.Join(dir, fi.Name())
324
325                 // If fi is a directory, its files make up a single package.
326                 if fi.IsDir() {
327                         testDir(t, path, colDelta, manual)
328                 } else {
329                         t.Run(filepath.Base(path), func(t *testing.T) {
330                                 testFiles(t, []string{path}, colDelta, manual)
331                         })
332                 }
333         }
334 }
335
336 func testDir(t *testing.T, dir string, colDelta uint, manual bool) {
337         fis, err := os.ReadDir(dir)
338         if err != nil {
339                 t.Error(err)
340                 return
341         }
342
343         var filenames []string
344         for _, fi := range fis {
345                 filenames = append(filenames, filepath.Join(dir, fi.Name()))
346         }
347
348         t.Run(filepath.Base(dir), func(t *testing.T) {
349                 testFiles(t, filenames, colDelta, manual)
350         })
351 }