]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/types2/check_test.go
cmd/compile/internal/types2: list errors by default in TestManual
[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         verifyErrors = flag.Bool("verify", false, "verify errors (rather than list them) in TestManual")
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, manual 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         listErrors := manual && !*verifyErrors
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 = manual && testing.Verbose()
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 // TestManual is for manual testing of input files, provided as a list
243 // of arguments after the test arguments (and a separating "--"). For
244 // instance, to check the files foo.go and bar.go, use:
245 //
246 //      go test -run Manual -- foo.go bar.go
247 //
248 // Provide the -verify flag to verify errors against ERROR comments in
249 // the input files rather than having a list of errors reported.
250 // The accepted Go language version can be controlled with the -lang flag.
251 func TestManual(t *testing.T) {
252         filenames := flag.Args()
253         if len(filenames) == 0 {
254                 return
255         }
256         testenv.MustHaveGoBuild(t)
257         DefPredeclaredTestFuncs()
258         checkFiles(t, filenames, *goVersion, 0, true)
259 }
260
261 // TODO(gri) go/types has extra TestLongConstants and TestIndexRepresentability tests
262
263 func TestCheck(t *testing.T)     { DefPredeclaredTestFuncs(); testDir(t, "check", 75) } // TODO(gri) narrow column tolerance
264 func TestExamples(t *testing.T)  { testDir(t, "examples", 0) }
265 func TestFixedbugs(t *testing.T) { testDir(t, "fixedbugs", 0) }
266
267 func testDir(t *testing.T, dir string, colDelta uint) {
268         testenv.MustHaveGoBuild(t)
269
270         dir = filepath.Join("testdata", dir)
271         fis, err := os.ReadDir(dir)
272         if err != nil {
273                 t.Error(err)
274                 return
275         }
276
277         for _, fi := range fis {
278                 path := filepath.Join(dir, fi.Name())
279
280                 // if fi is a directory, its files make up a single package
281                 var filenames []string
282                 if fi.IsDir() {
283                         fis, err := os.ReadDir(path)
284                         if err != nil {
285                                 t.Error(err)
286                                 continue
287                         }
288                         for _, fi := range fis {
289                                 filenames = append(filenames, filepath.Join(path, fi.Name()))
290                         }
291                 } else {
292                         filenames = []string{path}
293                 }
294
295                 t.Run(filepath.Base(path), func(t *testing.T) {
296                         checkFiles(t, filenames, *goVersion, colDelta, false)
297                 })
298         }
299 }