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