-
Notifications
You must be signed in to change notification settings - Fork 2
/
tinygo_test.go
235 lines (225 loc) · 6.23 KB
/
tinygo_test.go
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package board_test
import (
"bytes"
"flag"
"go/ast"
"go/parser"
"go/token"
"os/exec"
"testing"
)
var boards = []string{
// Please keep this list sorted!
"badger2040",
"gameboy-advance",
"gopher-badge",
"mch2022",
"pinetime",
"pybadge",
"pyportal",
"simulator",
"thumby",
}
func isXtensa(board string) bool {
return board == "mch2022"
}
var flagXtensa = flag.Bool("xtensa", false, "test Xtensa based boards")
// These method names should match the ones in testdata/smoketest.go, so that no
// method goes unchecked!
var definedGlobals = map[string][]string{
"Power": []string{
"Configure",
"Status",
},
"Sensors": []string{
"Configure",
"Update",
"Acceleration",
"Steps",
"Temperature",
},
"Display": []string{
"Configure",
"PPI",
"ConfigureTouch",
"MaxBrightness",
"SetBrightness",
"WaitForVBlank",
},
"Buttons": []string{
"Configure",
"ReadInput",
"NextEvent",
},
}
func TestBoards(t *testing.T) {
for _, board := range boards {
board := board
t.Run(board, func(t *testing.T) {
if isXtensa(board) && !*flagXtensa {
t.Skip("skipping Xtensa board:", board)
}
t.Parallel()
outbuf := &bytes.Buffer{}
var cmd *exec.Cmd
if board == "simulator" {
cmd = exec.Command("go", "build", "-o="+t.TempDir()+"/output", "./testdata/smoketest.go")
} else {
cmd = exec.Command("tinygo", "build", "-o="+t.TempDir()+"/output", "-target="+board, "./testdata/smoketest.go")
}
cmd.Stderr = outbuf
cmd.Stdout = outbuf
err := cmd.Run()
if err != nil {
t.Errorf("failed to compile smoke test: %s\n%s", err, outbuf.String())
}
})
}
}
// Test for exported names: all of them have to adhere to a strict API so that
// the API for all boards is the same.
func TestExported(t *testing.T) {
for _, board := range boards {
board := board
t.Run(board, func(t *testing.T) {
// Parse the Go file into an AST.
filename := "board-" + board + ".go"
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution)
if err != nil {
t.Errorf("could not open/parse %s: %v", filename, err)
}
// Collect method names for (typically unexported) named types.
// Also set some defaults that aren't defined in board files (but in
// common.go, probably).
methodNames := map[string][]string{
"baseSensors": definedGlobals["Sensors"],
"dummyBattery": definedGlobals["Power"],
"noButtons": definedGlobals["Buttons"],
}
for _, decl := range f.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok {
if decl.Name.IsExported() && decl.Recv != nil && len(decl.Recv.List) > 0 {
recvType := decl.Recv.List[0].Type
name := extractTypeName(recvType)
methodNames[name] = append(methodNames[name], decl.Name.Name)
}
}
}
// Check all exported types, variables, etc.
for _, decl := range f.Decls {
pos := fset.Position(decl.Pos())
switch decl := decl.(type) {
case *ast.FuncDecl:
if !decl.Name.IsExported() {
continue
}
if decl.Recv != nil && len(decl.Recv.List) > 0 {
// Method name, this is checked when checking named
// types.
continue
}
t.Errorf("%s: unexpected exported function %s", pos, decl.Name.Name)
case *ast.GenDecl:
switch decl.Tok {
case token.IMPORT:
// imports don't export anything
case token.CONST:
for _, spec := range decl.Specs {
pos := fset.Position(spec.Pos())
switch spec := spec.(type) {
case *ast.ValueSpec:
for _, name := range spec.Names {
if !name.IsExported() {
continue
}
if name.Name != "Name" {
// "Name" is the only allowed constant.
t.Errorf("%s: unexpected constant: %s", pos, name.Name)
}
}
default:
t.Errorf("%s: unexpected spec: %#v", pos, spec)
}
}
case token.VAR:
// Variables are things like Power, Display, etc that
// are typically defined using unexported named types.
// We need to check that they don't define any unexpected methods.
for _, spec := range decl.Specs {
pos := fset.Position(spec.Pos())
switch spec := spec.(type) {
case *ast.ValueSpec:
for _, name := range spec.Names {
if !name.IsExported() {
continue
}
if _, ok := definedGlobals[name.Name]; !ok {
t.Errorf("%s: unexpected variable: %s", pos, name.Name)
continue
}
if len(spec.Values) != 1 {
t.Errorf("%s: expected a single value for board.%s", pos, name.Name)
continue
}
typeName := extractTypeName(spec.Values[0])
if _, ok := methodNames[typeName]; !ok {
t.Errorf("%s: could not find methods for type %#v", pos, typeName)
continue
}
for _, typeMethod := range methodNames[typeName] {
found := false
for _, expectedMethod := range definedGlobals[name.Name] {
if typeMethod == expectedMethod {
found = true
}
}
if !found {
t.Errorf("%s: unexpected method %s on board.%s", pos, typeMethod, name)
}
}
}
default:
t.Errorf("%s: unexpected spec: %#v", pos, spec)
}
}
case token.TYPE:
// Boards shouldn't define any new types.
for _, spec := range decl.Specs {
pos := fset.Position(spec.Pos())
switch spec := spec.(type) {
case *ast.TypeSpec:
if !spec.Name.IsExported() {
continue
}
t.Errorf("%s: unexpected type: %s", pos, spec.Name)
default:
t.Errorf("%s: unexpected spec: %#v", pos, spec)
}
}
default:
t.Errorf("%s: unexpected declaration: %#v", pos, decl)
}
default:
t.Logf("%s: unexpected declaration: %#v", pos, decl)
}
}
})
}
}
// Extract the named type from the given AST expression (resolving things like
// *ast.StarExpr).
func extractTypeName(x ast.Expr) string {
switch value := x.(type) {
case *ast.Ident:
return value.Name
case *ast.CompositeLit:
return extractTypeName(value.Type)
case *ast.StarExpr:
return extractTypeName(value.X)
case *ast.UnaryExpr:
return extractTypeName(value.X)
default:
return "<unknown>"
}
}