]> git.ipfire.org Git - thirdparty/gcc.git/blob - libgo/go/exp/types/check_test.go
libgo: Update to current sources.
[thirdparty/gcc.git] / libgo / go / exp / types / 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 types
24
25 import (
26 // "fmt"
27 "go/ast"
28 "go/parser"
29 "go/scanner"
30 "go/token"
31 "io/ioutil"
32 // "os"
33 "regexp"
34 "testing"
35 )
36
37 // The test filenames do not end in .go so that they are invisible
38 // to gofmt since they contain comments that must not change their
39 // positions relative to surrounding tokens.
40
41 var tests = []struct {
42 name string
43 files []string
44 }{
45 {"test0", []string{"testdata/test0.src"}},
46 }
47
48 var fset = token.NewFileSet()
49
50 func getFile(filename string) (file *token.File) {
51 fset.Iterate(func(f *token.File) bool {
52 if f.Name() == filename {
53 file = f
54 return false // end iteration
55 }
56 return true
57 })
58 return file
59 }
60
61 func getPos(filename string, offset int) token.Pos {
62 if f := getFile(filename); f != nil {
63 return f.Pos(offset)
64 }
65 return token.NoPos
66 }
67
68 func parseFiles(t *testing.T, testname string, filenames []string) (map[string]*ast.File, error) {
69 files := make(map[string]*ast.File)
70 var errors scanner.ErrorList
71 for _, filename := range filenames {
72 if _, exists := files[filename]; exists {
73 t.Fatalf("%s: duplicate file %s", testname, filename)
74 }
75 file, err := parser.ParseFile(fset, filename, nil, parser.DeclarationErrors)
76 if file == nil {
77 t.Fatalf("%s: could not parse file %s", testname, filename)
78 }
79 files[filename] = file
80 if err != nil {
81 // if the parser returns a non-scanner.ErrorList error
82 // the file couldn't be read in the first place and
83 // file == nil; in that case we shouldn't reach here
84 errors = append(errors, err.(scanner.ErrorList)...)
85 }
86
87 }
88 return files, errors
89 }
90
91 // ERROR comments must be of the form /* ERROR "rx" */ and rx is
92 // a regular expression that matches the expected error message.
93 //
94 var errRx = regexp.MustCompile(`^/\* *ERROR *"([^"]*)" *\*/$`)
95
96 // expectedErrors collects the regular expressions of ERROR comments found
97 // in files and returns them as a map of error positions to error messages.
98 //
99 func expectedErrors(t *testing.T, testname string, files map[string]*ast.File) map[token.Pos]string {
100 errors := make(map[token.Pos]string)
101 for filename := range files {
102 src, err := ioutil.ReadFile(filename)
103 if err != nil {
104 t.Fatalf("%s: could not read %s", testname, filename)
105 }
106
107 var s scanner.Scanner
108 // file was parsed already - do not add it again to the file
109 // set otherwise the position information returned here will
110 // not match the position information collected by the parser
111 s.Init(getFile(filename), src, nil, scanner.ScanComments)
112 var prev token.Pos // position of last non-comment, non-semicolon token
113
114 scanFile:
115 for {
116 pos, tok, lit := s.Scan()
117 switch tok {
118 case token.EOF:
119 break scanFile
120 case token.COMMENT:
121 s := errRx.FindStringSubmatch(lit)
122 if len(s) == 2 {
123 errors[prev] = string(s[1])
124 }
125 case token.SEMICOLON:
126 // ignore automatically inserted semicolon
127 if lit == "\n" {
128 break
129 }
130 fallthrough
131 default:
132 prev = pos
133 }
134 }
135 }
136 return errors
137 }
138
139 func eliminate(t *testing.T, expected map[token.Pos]string, errors error) {
140 if errors == nil {
141 return
142 }
143 for _, error := range errors.(scanner.ErrorList) {
144 // error.Pos is a token.Position, but we want
145 // a token.Pos so we can do a map lookup
146 pos := getPos(error.Pos.Filename, error.Pos.Offset)
147 if msg, found := expected[pos]; found {
148 // we expect a message at pos; check if it matches
149 rx, err := regexp.Compile(msg)
150 if err != nil {
151 t.Errorf("%s: %v", error.Pos, err)
152 continue
153 }
154 if match := rx.MatchString(error.Msg); !match {
155 t.Errorf("%s: %q does not match %q", error.Pos, error.Msg, msg)
156 continue
157 }
158 // we have a match - eliminate this error
159 delete(expected, pos)
160 } else {
161 // To keep in mind when analyzing failed test output:
162 // If the same error position occurs multiple times in errors,
163 // this message will be triggered (because the first error at
164 // the position removes this position from the expected errors).
165 t.Errorf("%s: no (multiple?) error expected, but found: %s", error.Pos, error.Msg)
166 }
167 }
168 }
169
170 /*
171
172 This test doesn't work with gccgo--it can't read gccgo imports.
173
174 func check(t *testing.T, testname string, testfiles []string) {
175 // TODO(gri) Eventually all these different phases should be
176 // subsumed into a single function call that takes
177 // a set of files and creates a fully resolved and
178 // type-checked AST.
179
180 files, err := parseFiles(t, testname, testfiles)
181
182 // we are expecting the following errors
183 // (collect these after parsing the files so that
184 // they are found in the file set)
185 errors := expectedErrors(t, testname, files)
186
187 // verify errors returned by the parser
188 eliminate(t, errors, err)
189
190 // verify errors returned after resolving identifiers
191 pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
192 eliminate(t, errors, err)
193
194 // verify errors returned by the typechecker
195 _, err = Check(fset, pkg)
196 eliminate(t, errors, err)
197
198 // there should be no expected errors left
199 if len(errors) > 0 {
200 t.Errorf("%s: %d errors not reported:", testname, len(errors))
201 for pos, msg := range errors {
202 t.Errorf("%s: %s\n", fset.Position(pos), msg)
203 }
204 }
205 }
206
207 func TestCheck(t *testing.T) {
208 // For easy debugging w/o changing the testing code,
209 // if there is a local test file, only test that file.
210 const testfile = "testdata/test.go"
211 if fi, err := os.Stat(testfile); err == nil && !fi.IsDir() {
212 fmt.Printf("WARNING: Testing only %s (remove it to run all tests)\n", testfile)
213 check(t, testfile, []string{testfile})
214 return
215 }
216
217 // Otherwise, run all the tests.
218 for _, test := range tests {
219 check(t, test.name, test.files)
220 }
221 }
222
223 */