]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/types2/check_test.go
cmd/compile/internal/types2: generalize assignability to generic types
[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         "cmd/compile/internal/syntax"
27         "flag"
28         "internal/testenv"
29         "os"
30         "path/filepath"
31         "regexp"
32         "sort"
33         "strings"
34         "testing"
35
36         . "cmd/compile/internal/types2"
37 )
38
39 var (
40         haltOnError  = flag.Bool("halt", false, "halt on error")
41         verifyErrors = flag.Bool("verify", false, "verify errors (rather than list them) in TestManual")
42         goVersion    = flag.String("lang", "", "Go language version (e.g. \"go1.12\")")
43 )
44
45 func parseFiles(t *testing.T, filenames []string, mode syntax.Mode) ([]*syntax.File, []error) {
46         var files []*syntax.File
47         var errlist []error
48         errh := func(err error) { errlist = append(errlist, err) }
49         for _, filename := range filenames {
50                 file, err := syntax.ParseFile(filename, errh, nil, mode)
51                 if file == nil {
52                         t.Fatalf("%s: %s", filename, err)
53                 }
54                 files = append(files, file)
55         }
56         return files, errlist
57 }
58
59 func unpackError(err error) syntax.Error {
60         switch err := err.(type) {
61         case syntax.Error:
62                 return err
63         case Error:
64                 return syntax.Error{Pos: err.Pos, Msg: err.Msg}
65         default:
66                 return syntax.Error{Msg: err.Error()}
67         }
68 }
69
70 // delta returns the absolute difference between x and y.
71 func delta(x, y uint) uint {
72         switch {
73         case x < y:
74                 return y - x
75         case x > y:
76                 return x - y
77         default:
78                 return 0
79         }
80 }
81
82 // goVersionRx matches a Go version string using '_', e.g. "go1_12".
83 var goVersionRx = regexp.MustCompile(`^go[1-9][0-9]*_(0|[1-9][0-9]*)$`)
84
85 // asGoVersion returns a regular Go language version string
86 // if s is a Go version string using '_' rather than '.' to
87 // separate the major and minor version numbers (e.g. "go1_12").
88 // Otherwise it returns the empty string.
89 func asGoVersion(s string) string {
90         if goVersionRx.MatchString(s) {
91                 return strings.Replace(s, "_", ".", 1)
92         }
93         return ""
94 }
95
96 func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) {
97         if len(filenames) == 0 {
98                 t.Fatal("no source files")
99         }
100
101         var mode syntax.Mode
102         if strings.HasSuffix(filenames[0], ".go2") || manual {
103                 mode |= syntax.AllowGenerics
104         }
105         // parse files and collect parser errors
106         files, errlist := parseFiles(t, filenames, mode)
107
108         pkgName := "<no package>"
109         if len(files) > 0 {
110                 pkgName = files[0].PkgName.Value
111         }
112
113         // if no Go version is given, consider the package name
114         goVersion := *goVersion
115         if goVersion == "" {
116                 goVersion = asGoVersion(pkgName)
117         }
118
119         listErrors := manual && !*verifyErrors
120         if listErrors && len(errlist) > 0 {
121                 t.Errorf("--- %s:", pkgName)
122                 for _, err := range errlist {
123                         t.Error(err)
124                 }
125         }
126
127         // typecheck and collect typechecker errors
128         var conf Config
129         conf.GoVersion = goVersion
130         // special case for importC.src
131         if len(filenames) == 1 && strings.HasSuffix(filenames[0], "importC.src") {
132                 conf.FakeImportC = true
133         }
134         conf.Trace = manual && testing.Verbose()
135         conf.Importer = defaultImporter()
136         conf.Error = func(err error) {
137                 if *haltOnError {
138                         defer panic(err)
139                 }
140                 if listErrors {
141                         t.Error(err)
142                         return
143                 }
144                 errlist = append(errlist, err)
145         }
146         conf.Check(pkgName, files, nil)
147
148         if listErrors {
149                 return
150         }
151
152         // sort errlist in source order
153         sort.Slice(errlist, func(i, j int) bool {
154                 pi := unpackError(errlist[i]).Pos
155                 pj := unpackError(errlist[j]).Pos
156                 return pi.Cmp(pj) < 0
157         })
158
159         // collect expected errors
160         errmap := make(map[string]map[uint][]syntax.Error)
161         for _, filename := range filenames {
162                 f, err := os.Open(filename)
163                 if err != nil {
164                         t.Error(err)
165                         continue
166                 }
167                 if m := syntax.ErrorMap(f); len(m) > 0 {
168                         errmap[filename] = m
169                 }
170                 f.Close()
171         }
172
173         // match against found errors
174         for _, err := range errlist {
175                 got := unpackError(err)
176
177                 // find list of errors for the respective error line
178                 filename := got.Pos.Base().Filename()
179                 filemap := errmap[filename]
180                 line := got.Pos.Line()
181                 var list []syntax.Error
182                 if filemap != nil {
183                         list = filemap[line]
184                 }
185                 // list may be nil
186
187                 // one of errors in list should match the current error
188                 index := -1 // list index of matching message, if any
189                 for i, want := range list {
190                         rx, err := regexp.Compile(want.Msg)
191                         if err != nil {
192                                 t.Errorf("%s:%d:%d: %v", filename, line, want.Pos.Col(), err)
193                                 continue
194                         }
195                         if rx.MatchString(got.Msg) {
196                                 index = i
197                                 break
198                         }
199                 }
200                 if index < 0 {
201                         t.Errorf("%s: no error expected: %q", got.Pos, got.Msg)
202                         continue
203                 }
204
205                 // column position must be within expected colDelta
206                 want := list[index]
207                 if delta(got.Pos.Col(), want.Pos.Col()) > colDelta {
208                         t.Errorf("%s: got col = %d; want %d", got.Pos, got.Pos.Col(), want.Pos.Col())
209                 }
210
211                 // eliminate from list
212                 if n := len(list) - 1; n > 0 {
213                         // not the last entry - slide entries down (don't reorder)
214                         copy(list[index:], list[index+1:])
215                         filemap[line] = list[:n]
216                 } else {
217                         // last entry - remove list from filemap
218                         delete(filemap, line)
219                 }
220
221                 // if filemap is empty, eliminate from errmap
222                 if len(filemap) == 0 {
223                         delete(errmap, filename)
224                 }
225         }
226
227         // there should be no expected errors left
228         if len(errmap) > 0 {
229                 t.Errorf("--- %s: unreported errors:", pkgName)
230                 for filename, filemap := range errmap {
231                         for line, list := range filemap {
232                                 for _, err := range list {
233                                         t.Errorf("%s:%d:%d: %s", filename, line, err.Pos.Col(), err.Msg)
234                                 }
235                         }
236                 }
237         }
238 }
239
240 // TestManual is for manual testing of a package - either provided
241 // as a list of filenames belonging to the package, or a directory
242 // name containing the package files - after the test arguments
243 // (and a separating "--"). For instance, to test the package made
244 // of the files foo.go and bar.go, use:
245 //
246 //      go test -run Manual -- foo.go bar.go
247 //
248 // If no source arguments are provided, the file testdata/manual.go2
249 // is used instead.
250 // Provide the -verify flag to verify errors against ERROR comments
251 // in the input files rather than having a list of errors reported.
252 // The accepted Go language version can be controlled with the -lang
253 // flag.
254 func TestManual(t *testing.T) {
255         testenv.MustHaveGoBuild(t)
256
257         filenames := flag.Args()
258         if len(filenames) == 0 {
259                 filenames = []string{filepath.FromSlash("testdata/manual.go2")}
260         }
261
262         info, err := os.Stat(filenames[0])
263         if err != nil {
264                 t.Fatalf("TestManual: %v", err)
265         }
266
267         DefPredeclaredTestFuncs()
268         if info.IsDir() {
269                 if len(filenames) > 1 {
270                         t.Fatal("TestManual: must have only one directory argument")
271                 }
272                 testDir(t, filenames[0], 0, true)
273         } else {
274                 testFiles(t, filenames, 0, true)
275         }
276 }
277
278 // TODO(gri) go/types has extra TestLongConstants and TestIndexRepresentability tests
279
280 func TestCheck(t *testing.T)     { DefPredeclaredTestFuncs(); testDirFiles(t, "testdata/check", 75, false) } // TODO(gri) narrow column tolerance
281 func TestSpec(t *testing.T)      { DefPredeclaredTestFuncs(); testDirFiles(t, "testdata/spec", 0, false) }
282 func TestExamples(t *testing.T)  { testDirFiles(t, "testdata/examples", 0, false) }
283 func TestFixedbugs(t *testing.T) { testDirFiles(t, "testdata/fixedbugs", 0, false) }
284
285 func testDirFiles(t *testing.T, dir string, colDelta uint, manual bool) {
286         testenv.MustHaveGoBuild(t)
287         dir = filepath.FromSlash(dir)
288
289         fis, err := os.ReadDir(dir)
290         if err != nil {
291                 t.Error(err)
292                 return
293         }
294
295         for _, fi := range fis {
296                 path := filepath.Join(dir, fi.Name())
297
298                 // If fi is a directory, its files make up a single package.
299                 if fi.IsDir() {
300                         testDir(t, path, colDelta, manual)
301                 } else {
302                         t.Run(filepath.Base(path), func(t *testing.T) {
303                                 testFiles(t, []string{path}, colDelta, manual)
304                         })
305                 }
306         }
307 }
308
309 func testDir(t *testing.T, dir string, colDelta uint, manual bool) {
310         fis, err := os.ReadDir(dir)
311         if err != nil {
312                 t.Error(err)
313                 return
314         }
315
316         var filenames []string
317         for _, fi := range fis {
318                 filenames = append(filenames, filepath.Join(dir, fi.Name()))
319         }
320
321         t.Run(filepath.Base(dir), func(t *testing.T) {
322                 testFiles(t, filenames, colDelta, manual)
323         })
324 }