aboutsummaryrefslogtreecommitdiffhomepage
path: root/errors_test.go
blob: 79174197872363108d0a08b8ef03b73000e50b3b (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
package main

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"testing"
	"time"

	"github.com/tinygo-org/tinygo/compileopts"
)

// Test the error messages of the TinyGo compiler.
func TestErrors(t *testing.T) {
	for _, name := range []string{
		"cgo",
		"compiler",
		"interp",
		"loader-importcycle",
		"loader-invaliddep",
		"loader-invalidpackage",
		"loader-nopackage",
		"optimizer",
		"syntax",
		"types",
	} {
		t.Run(name, func(t *testing.T) {
			testErrorMessages(t, "./testdata/errors/"+name+".go")
		})
	}
}

func testErrorMessages(t *testing.T, filename string) {
	// Parse expected error messages.
	expected := readErrorMessages(t, filename)

	// Try to build a binary (this should fail with an error).
	tmpdir := t.TempDir()
	err := Build(filename, tmpdir+"/out", &compileopts.Options{
		Target:        "wasip1",
		Semaphore:     sema,
		InterpTimeout: 180 * time.Second,
		Debug:         true,
		VerifyIR:      true,
		Opt:           "z",
	})
	if err == nil {
		t.Fatal("expected to get a compiler error")
	}

	// Get the full ./testdata/errors directory.
	wd, absErr := filepath.Abs("testdata/errors")
	if absErr != nil {
		t.Fatal(absErr)
	}

	// Write error message out as plain text.
	var buf bytes.Buffer
	printCompilerError(err, func(v ...interface{}) {
		fmt.Fprintln(&buf, v...)
	}, wd)
	actual := strings.TrimRight(buf.String(), "\n")

	// Check whether the error is as expected.
	if !matchErrors(t, expected, actual) {
		t.Errorf("expected error:\n%s\ngot:\n%s", indentText(expected, "> "), indentText(actual, "> "))
	}
}

func matchErrors(t *testing.T, pattern, actual string) bool {
	patternLines := strings.Split(pattern, "\n")
	actualLines := strings.Split(actual, "\n")
	if len(patternLines) != len(actualLines) {
		return false
	}
	for i, patternLine := range patternLines {
		indices := regexp.MustCompile(`\{\{.*?\}\}`).FindAllStringIndex(patternLine, -1)
		patternParts := []string{"^"}
		lastStop := 0
		for _, startstop := range indices {
			start := startstop[0]
			stop := startstop[1]
			patternParts = append(patternParts,
				regexp.QuoteMeta(patternLine[lastStop:start]),
				patternLine[start+2:stop-2])
			lastStop = stop
		}
		patternParts = append(patternParts, regexp.QuoteMeta(patternLine[lastStop:]), "$")
		pattern := strings.Join(patternParts, "")
		re, err := regexp.Compile(pattern)
		if err != nil {
			t.Fatalf("could not compile regexp for %#v: %v", patternLine, err)
		}
		if !re.MatchString(actualLines[i]) {
			return false
		}
	}
	return true
}

// Indent the given text with a given indentation string.
func indentText(text, indent string) string {
	return indent + strings.ReplaceAll(text, "\n", "\n"+indent)
}

// Read "// ERROR:" prefixed messages from the given file.
func readErrorMessages(t *testing.T, file string) string {
	data, err := os.ReadFile(file)
	if err != nil {
		t.Fatal("could not read input file:", err)
	}

	var errors []string
	for _, line := range strings.Split(string(data), "\n") {
		if strings.HasPrefix(line, "// ERROR: ") {
			errors = append(errors, strings.TrimRight(line[len("// ERROR: "):], "\r\n"))
		}
	}
	return strings.Join(errors, "\n")
}