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