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