summaryrefslogtreecommitdiff
path: root/libgo/go/go/typechecker/typechecker_test.go
blob: 33f4a6223ff41a62dc8d8618fb042590bc2ece94 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file implements a simple typechecker test harness. Packages found
// in the testDir directory are typechecked. Error messages reported by
// the typechecker are compared against the error messages expected for
// the test files.
//
// Expected errors are indicated in the test files by putting a comment
// of the form /* ERROR "rx" */ immediately following an offending token.
// The harness will verify that an error matching the regular expression
// rx is reported at that source position. Consecutive comments may be
// used to indicate multiple errors for the same token position.
//
// For instance, the following test file indicates that a "not declared"
// error should be reported for the undeclared variable x:
//
//	package P0
//	func f() {
//		_ = x /* ERROR "not declared" */ + 1
//	}
// 
// If the -pkg flag is set, only packages with package names matching
// the regular expression provided via the flag value are tested.

package typechecker

import (
	"flag"
	"fmt"
	"go/ast"
	"go/parser"
	"go/scanner"
	"go/token"
	"io/ioutil"
	"os"
	"regexp"
	"sort"
	"strings"
	"testing"
)


const testDir = "./testdata" // location of test packages

var fset = token.NewFileSet()

var (
	pkgPat = flag.String("pkg", ".*", "regular expression to select test packages by package name")
	trace  = flag.Bool("trace", false, "print package names")
)


// ERROR comments must be of the form /* ERROR "rx" */ and rx is
// a regular expression that matches the expected error message.
var errRx = regexp.MustCompile(`^/\* *ERROR *"([^"]*)" *\*/$`)

// expectedErrors collects the regular expressions of ERROR comments
// found in the package files of pkg and returns them in sorted order
// (by filename and position).
func expectedErrors(t *testing.T, pkg *ast.Package) (list scanner.ErrorList) {
	// scan all package files
	for filename := range pkg.Files {
		src, err := ioutil.ReadFile(filename)
		if err != nil {
			t.Fatalf("expectedErrors(%s): %v", pkg.Name, err)
		}

		var s scanner.Scanner
		file := fset.AddFile(filename, fset.Base(), len(src))
		s.Init(file, src, nil, scanner.ScanComments)
		var prev token.Pos // position of last non-comment token
	loop:
		for {
			pos, tok, lit := s.Scan()
			switch tok {
			case token.EOF:
				break loop
			case token.COMMENT:
				s := errRx.FindSubmatch(lit)
				if len(s) == 2 {
					list = append(list, &scanner.Error{fset.Position(prev), string(s[1])})
				}
			default:
				prev = pos
			}
		}
	}
	sort.Sort(list) // multiple files may not be sorted
	return
}


func testFilter(f *os.FileInfo) bool {
	return strings.HasSuffix(f.Name, ".go") && f.Name[0] != '.'
}


func checkError(t *testing.T, expected, found *scanner.Error) {
	rx, err := regexp.Compile(expected.Msg)
	if err != nil {
		t.Errorf("%s: %v", expected.Pos, err)
		return
	}

	match := rx.MatchString(found.Msg)

	if expected.Pos.Offset != found.Pos.Offset {
		if match {
			t.Errorf("%s: expected error should have been at %s", expected.Pos, found.Pos)
		} else {
			t.Errorf("%s: error matching %q expected", expected.Pos, expected.Msg)
			return
		}
	}

	if !match {
		t.Errorf("%s: %q does not match %q", expected.Pos, expected.Msg, found.Msg)
	}
}


func TestTypeCheck(t *testing.T) {
	flag.Parse()
	pkgRx, err := regexp.Compile(*pkgPat)
	if err != nil {
		t.Fatalf("illegal flag value %q: %s", *pkgPat, err)
	}

	pkgs, err := parser.ParseDir(fset, testDir, testFilter, 0)
	if err != nil {
		scanner.PrintError(os.Stderr, err)
		t.Fatalf("packages in %s contain syntax errors", testDir)
	}

	for _, pkg := range pkgs {
		if !pkgRx.MatchString(pkg.Name) {
			continue // only test selected packages
		}

		if *trace {
			fmt.Println(pkg.Name)
		}

		xlist := expectedErrors(t, pkg)
		err := CheckPackage(fset, pkg, nil)
		if err != nil {
			if elist, ok := err.(scanner.ErrorList); ok {
				// verify that errors match
				for i := 0; i < len(xlist) && i < len(elist); i++ {
					checkError(t, xlist[i], elist[i])
				}
				// the correct number or errors must have been found
				if len(xlist) != len(elist) {
					fmt.Fprintf(os.Stderr, "%s\n", pkg.Name)
					scanner.PrintError(os.Stderr, elist)
					fmt.Fprintln(os.Stderr)
					t.Errorf("TypeCheck(%s): %d errors expected but %d reported", pkg.Name, len(xlist), len(elist))
				}
			} else {
				t.Errorf("TypeCheck(%s): %v", pkg.Name, err)
			}
		} else if len(xlist) > 0 {
			t.Errorf("TypeCheck(%s): %d errors expected but 0 reported", pkg.Name, len(xlist))
		}
	}
}