Skip to content

Commit 8a357af

Browse files
committed
all: add testing for compiler error messages
This is needed for some improvements I'm going to make next. This commit also refactors error handling slightly to make it more easily testable, this should hopefully not result in any actual changes in behavior.
1 parent 7ac1ca0 commit 8a357af

File tree

6 files changed

+141
-19
lines changed

6 files changed

+141
-19
lines changed

errors_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"testing"
10+
"time"
11+
12+
"github.com/tinygo-org/tinygo/compileopts"
13+
)
14+
15+
// Test the error messages of the TinyGo compiler.
16+
func TestErrors(t *testing.T) {
17+
for _, name := range []string{
18+
"cgo",
19+
"syntax",
20+
"types",
21+
} {
22+
t.Run(name, func(t *testing.T) {
23+
testErrorMessages(t, "./testdata/errors/"+name+".go")
24+
})
25+
}
26+
}
27+
28+
func testErrorMessages(t *testing.T, filename string) {
29+
// Parse expected error messages.
30+
expected := readErrorMessages(t, filename)
31+
32+
// Try to build a binary (this should fail with an error).
33+
tmpdir := t.TempDir()
34+
err := Build(filename, tmpdir+"/out", &compileopts.Options{
35+
Target: "wasip1",
36+
Semaphore: sema,
37+
InterpTimeout: 180 * time.Second,
38+
Debug: true,
39+
VerifyIR: true,
40+
Opt: "z",
41+
})
42+
if err == nil {
43+
t.Fatal("expected to get a compiler error")
44+
}
45+
46+
// Get the full ./testdata/errors directory.
47+
wd, absErr := filepath.Abs("testdata/errors")
48+
if absErr != nil {
49+
t.Fatal(absErr)
50+
}
51+
52+
// Write error message out as plain text.
53+
var buf bytes.Buffer
54+
printCompilerError(err, func(v ...interface{}) {
55+
fmt.Fprintln(&buf, v...)
56+
}, wd)
57+
actual := strings.TrimRight(buf.String(), "\n")
58+
59+
// Check whether the error is as expected.
60+
if actual != expected {
61+
t.Errorf("expected error:\n%s\ngot:\n%s", indentText(expected, "> "), indentText(actual, "> "))
62+
}
63+
}
64+
65+
// Indent the given text with a given indentation string.
66+
func indentText(text, indent string) string {
67+
return indent + strings.ReplaceAll(text, "\n", "\n"+indent)
68+
}
69+
70+
// Read "// ERROR:" prefixed messages from the given file.
71+
func readErrorMessages(t *testing.T, file string) string {
72+
data, err := os.ReadFile(file)
73+
if err != nil {
74+
t.Fatal("could not read input file:", err)
75+
}
76+
77+
var errors []string
78+
for _, line := range strings.Split(string(data), "\n") {
79+
if strings.HasPrefix(line, "// ERROR: ") {
80+
errors = append(errors, strings.TrimRight(line[len("// ERROR: "):], "\r\n"))
81+
}
82+
}
83+
return strings.Join(errors, "\n")
84+
}

main.go

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,10 +1293,9 @@ func usage(command string) {
12931293

12941294
// try to make the path relative to the current working directory. If any error
12951295
// occurs, this error is ignored and the absolute path is returned instead.
1296-
func tryToMakePathRelative(dir string) string {
1297-
wd, err := os.Getwd()
1298-
if err != nil {
1299-
return dir
1296+
func tryToMakePathRelative(dir, wd string) string {
1297+
if wd == "" {
1298+
return dir // working directory not found
13001299
}
13011300
relpath, err := filepath.Rel(wd, dir)
13021301
if err != nil {
@@ -1307,28 +1306,25 @@ func tryToMakePathRelative(dir string) string {
13071306

13081307
// printCompilerError prints compiler errors using the provided logger function
13091308
// (similar to fmt.Println).
1310-
//
1311-
// There is one exception: interp errors may print to stderr unconditionally due
1312-
// to limitations in the LLVM bindings.
1313-
func printCompilerError(logln func(...interface{}), err error) {
1309+
func printCompilerError(err error, logln func(...interface{}), wd string) {
13141310
switch err := err.(type) {
13151311
case types.Error:
1316-
printCompilerError(logln, scanner.Error{
1312+
printCompilerError(scanner.Error{
13171313
Pos: err.Fset.Position(err.Pos),
13181314
Msg: err.Msg,
1319-
})
1315+
}, logln, wd)
13201316
case scanner.Error:
13211317
if !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("GOROOT"), "src")) && !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("TINYGOROOT"), "src")) {
13221318
// This file is not from the standard library (either the GOROOT or
13231319
// the TINYGOROOT). Make the path relative, for easier reading.
13241320
// Ignore any errors in the process (falling back to the absolute
13251321
// path).
1326-
err.Pos.Filename = tryToMakePathRelative(err.Pos.Filename)
1322+
err.Pos.Filename = tryToMakePathRelative(err.Pos.Filename, wd)
13271323
}
13281324
logln(err)
13291325
case scanner.ErrorList:
13301326
for _, scannerErr := range err {
1331-
printCompilerError(logln, *scannerErr)
1327+
printCompilerError(*scannerErr, logln, wd)
13321328
}
13331329
case *interp.Error:
13341330
logln("#", err.ImportPath)
@@ -1346,7 +1342,7 @@ func printCompilerError(logln func(...interface{}), err error) {
13461342
case loader.Errors:
13471343
logln("#", err.Pkg.ImportPath)
13481344
for _, err := range err.Errs {
1349-
printCompilerError(logln, err)
1345+
printCompilerError(err, logln, wd)
13501346
}
13511347
case loader.Error:
13521348
logln(err.Err.Error())
@@ -1356,7 +1352,7 @@ func printCompilerError(logln func(...interface{}), err error) {
13561352
}
13571353
case *builder.MultiError:
13581354
for _, err := range err.Errs {
1359-
printCompilerError(logln, err)
1355+
printCompilerError(err, logln, wd)
13601356
}
13611357
default:
13621358
logln("error:", err)
@@ -1365,9 +1361,13 @@ func printCompilerError(logln func(...interface{}), err error) {
13651361

13661362
func handleCompilerError(err error) {
13671363
if err != nil {
1368-
printCompilerError(func(args ...interface{}) {
1364+
wd, getwdErr := os.Getwd()
1365+
if getwdErr != nil {
1366+
wd = ""
1367+
}
1368+
printCompilerError(err, func(args ...interface{}) {
13691369
fmt.Fprintln(os.Stderr, args...)
1370-
}, err)
1370+
}, wd)
13711371
os.Exit(1)
13721372
}
13731373
}
@@ -1769,9 +1769,13 @@ func main() {
17691769
stderr := (*testStderr)(buf)
17701770
passed, err := Test(pkgName, stdout, stderr, options, outpath)
17711771
if err != nil {
1772-
printCompilerError(func(args ...interface{}) {
1772+
wd, getwdErr := os.Getwd()
1773+
if getwdErr != nil {
1774+
wd = ""
1775+
}
1776+
printCompilerError(err, func(args ...interface{}) {
17731777
fmt.Fprintln(stderr, args...)
1774-
}, err)
1778+
}, wd)
17751779
}
17761780
if !passed {
17771781
select {

main_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
380380
return cmd.Run()
381381
})
382382
if err != nil {
383-
printCompilerError(t.Log, err)
383+
printCompilerError(err, t.Log, "")
384384
t.Fail()
385385
return
386386
}

testdata/errors/cgo.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package main
2+
3+
// #error hello
4+
// )))
5+
import "C"
6+
7+
func main() {
8+
}
9+
10+
// TODO: this error should be relative to the current directory (so cgo.go
11+
// instead of testdata/errors/cgo.go).
12+
13+
// ERROR: # command-line-arguments
14+
// ERROR: testdata/errors/cgo.go:3:5: error: hello
15+
// ERROR: testdata/errors/cgo.go:4:4: error: expected identifier or '('

testdata/errors/syntax.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package main
2+
3+
func main(var) { // syntax error
4+
}
5+
6+
// ERROR: # command-line-arguments
7+
// ERROR: syntax.go:3:11: expected ')', found 'var'

testdata/errors/types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package main
2+
3+
func main() {
4+
var a int
5+
a = "foobar"
6+
nonexisting()
7+
}
8+
9+
// ERROR: # command-line-arguments
10+
// ERROR: types.go:5:6: cannot use "foobar" (untyped string constant) as int value in assignment
11+
// ERROR: types.go:6:2: undefined: nonexisting
12+
// ERROR: types.go:4:6: a declared and not used

0 commit comments

Comments
 (0)