Skip to content

Commit 78acbdf

Browse files
aykevldeadprogram
authored andcommitted
main: match go test output
This commit makes the output of `tinygo test` similar to that of `go test`. It changes the following things in the process: * Running multiple tests in a single command is now possible. They aren't paralellized yet. * Packages with no test files won't crash TinyGo, instead it logs it in the same way the Go toolchain does.
1 parent 617e279 commit 78acbdf

File tree

6 files changed

+139
-69
lines changed

6 files changed

+139
-69
lines changed

Makefile

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -182,25 +182,28 @@ tinygo:
182182
test: wasi-libc
183183
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
184184

185+
TEST_PACKAGES = \
186+
container/heap \
187+
container/list \
188+
container/ring \
189+
crypto/des \
190+
encoding \
191+
encoding/ascii85 \
192+
encoding/base32 \
193+
encoding/hex \
194+
hash/adler32 \
195+
hash/fnv \
196+
hash/crc64 \
197+
math \
198+
math/cmplx \
199+
text/scanner \
200+
unicode/utf8 \
201+
185202
# Test known-working standard library packages.
186-
# TODO: do this in one command, parallelize, and only show failing tests (no
187-
# implied -v flag).
203+
# TODO: parallelize, and only show failing tests (no implied -v flag).
188204
.PHONY: tinygo-test
189205
tinygo-test:
190-
$(TINYGO) test container/heap
191-
$(TINYGO) test container/list
192-
$(TINYGO) test container/ring
193-
$(TINYGO) test crypto/des
194-
$(TINYGO) test encoding/ascii85
195-
$(TINYGO) test encoding/base32
196-
$(TINYGO) test encoding/hex
197-
$(TINYGO) test hash/adler32
198-
$(TINYGO) test hash/fnv
199-
$(TINYGO) test hash/crc64
200-
$(TINYGO) test math
201-
$(TINYGO) test math/cmplx
202-
$(TINYGO) test text/scanner
203-
$(TINYGO) test unicode/utf8
206+
$(TINYGO) test $(TEST_PACKAGES)
204207

205208
.PHONY: smoketest
206209
smoketest:

builder/build.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ type BuildResult struct {
3939
// The directory of the main package. This is useful for testing as the test
4040
// binary must be run in the directory of the tested package.
4141
MainDir string
42+
43+
// ImportPath is the import path of the main package. This is useful for
44+
// correctly printing test results: the import path isn't always the same as
45+
// the path listed on the command line.
46+
ImportPath string
4247
}
4348

4449
// packageAction is the struct that is serialized to JSON and hashed, to work as
@@ -638,8 +643,9 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
638643
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
639644
}
640645
return action(BuildResult{
641-
Binary: tmppath,
642-
MainDir: lprogram.MainPkg().Dir,
646+
Binary: tmppath,
647+
MainDir: lprogram.MainPkg().Dir,
648+
ImportPath: lprogram.MainPkg().ImportPath,
643649
})
644650
}
645651

loader/errors.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,13 @@ type Error struct {
2323
func (e Error) Error() string {
2424
return e.Err.Error()
2525
}
26+
27+
// Error returned when loading a *Program for a test binary but no test files
28+
// are present.
29+
type NoTestFilesError struct {
30+
ImportPath string
31+
}
32+
33+
func (e NoTestFilesError) Error() string {
34+
return "no test files"
35+
}

loader/loader.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@ func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, t
210210
p.Packages[pkg.ImportPath] = pkg
211211
}
212212

213+
if config.TestConfig.CompileTestBinary && !strings.HasSuffix(p.sorted[len(p.sorted)-1].ImportPath, ".test") {
214+
// Trying to compile a test binary but there are no test files in this
215+
// package.
216+
return p, NoTestFilesError{p.sorted[len(p.sorted)-1].ImportPath}
217+
}
218+
213219
return p, nil
214220
}
215221

main.go

Lines changed: 92 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,17 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
136136
})
137137
}
138138

139-
// Test runs the tests in the given package.
140-
func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, outpath string) error {
139+
// Test runs the tests in the given package. Returns whether the test passed and
140+
// possibly an error if the test failed to run.
141+
func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, outpath string) (bool, error) {
141142
options.TestConfig.CompileTestBinary = true
142143
config, err := builder.NewConfig(options)
143144
if err != nil {
144-
return err
145+
return false, err
145146
}
146147

147-
return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
148+
var passed bool
149+
err = builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
148150
if testCompileOnly || outpath != "" {
149151
// Write test binary to the specified file name.
150152
if outpath == "" {
@@ -158,48 +160,78 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
158160
// Do not run the test.
159161
return nil
160162
}
161-
if len(config.Target.Emulator) == 0 {
162-
// Run directly.
163-
cmd := executeCommand(config.Options, result.Binary)
164-
cmd.Stdout = os.Stdout
165-
cmd.Stderr = os.Stderr
166-
cmd.Dir = result.MainDir
167-
err := cmd.Run()
168-
if err != nil {
169-
// Propagate the exit code
170-
if err, ok := err.(*exec.ExitError); ok {
171-
os.Exit(err.ExitCode())
172-
}
173-
return &commandError{"failed to run compiled binary", result.Binary, err}
174-
}
175-
return nil
163+
164+
// Run the test.
165+
start := time.Now()
166+
var err error
167+
passed, err = runPackageTest(config, result)
168+
if err != nil {
169+
return err
170+
}
171+
duration := time.Since(start)
172+
173+
// Print the result.
174+
importPath := strings.TrimSuffix(result.ImportPath, ".test")
175+
if passed {
176+
fmt.Printf("ok \t%s\t%.3fs\n", importPath, duration.Seconds())
176177
} else {
177-
// Run in an emulator.
178-
args := append(config.Target.Emulator[1:], result.Binary)
179-
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
180-
buf := &bytes.Buffer{}
181-
w := io.MultiWriter(os.Stdout, buf)
182-
cmd.Stdout = w
183-
cmd.Stderr = os.Stderr
184-
err := cmd.Run()
185-
if err != nil {
186-
if err, ok := err.(*exec.ExitError); !ok || !err.Exited() {
187-
// Workaround for QEMU which always exits with an error.
188-
return &commandError{"failed to run emulator with", result.Binary, err}
189-
}
178+
fmt.Printf("FAIL\t%s\t%.3fs\n", importPath, duration.Seconds())
179+
}
180+
return nil
181+
})
182+
if err, ok := err.(loader.NoTestFilesError); ok {
183+
fmt.Printf("? \t%s\t[no test files]\n", err.ImportPath)
184+
// Pretend the test passed - it at least didn't fail.
185+
return true, nil
186+
}
187+
return passed, err
188+
}
189+
190+
// runPackageTest runs a test binary that was previously built. The return
191+
// values are whether the test passed and any errors encountered while trying to
192+
// run the binary.
193+
func runPackageTest(config *compileopts.Config, result builder.BuildResult) (bool, error) {
194+
if len(config.Target.Emulator) == 0 {
195+
// Run directly.
196+
cmd := executeCommand(config.Options, result.Binary)
197+
cmd.Stdout = os.Stdout
198+
cmd.Stderr = os.Stderr
199+
cmd.Dir = result.MainDir
200+
err := cmd.Run()
201+
if err != nil {
202+
if _, ok := err.(*exec.ExitError); ok {
203+
// Binary exited with a non-zero exit code, which means the test
204+
// failed.
205+
return false, nil
190206
}
191-
testOutput := string(buf.Bytes())
192-
if testOutput == "PASS\n" || strings.HasSuffix(testOutput, "\nPASS\n") {
193-
// Test passed.
194-
return nil
195-
} else {
196-
// Test failed, either by ending with the word "FAIL" or with a
197-
// panic of some sort.
198-
os.Exit(1)
199-
return nil // unreachable
207+
return false, &commandError{"failed to run compiled binary", result.Binary, err}
208+
}
209+
return true, nil
210+
} else {
211+
// Run in an emulator.
212+
args := append(config.Target.Emulator[1:], result.Binary)
213+
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
214+
buf := &bytes.Buffer{}
215+
w := io.MultiWriter(os.Stdout, buf)
216+
cmd.Stdout = w
217+
cmd.Stderr = os.Stderr
218+
err := cmd.Run()
219+
if err != nil {
220+
if err, ok := err.(*exec.ExitError); !ok || !err.Exited() {
221+
// Workaround for QEMU which always exits with an error.
222+
return false, &commandError{"failed to run emulator with", result.Binary, err}
200223
}
201224
}
202-
})
225+
testOutput := string(buf.Bytes())
226+
if testOutput == "PASS\n" || strings.HasSuffix(testOutput, "\nPASS\n") {
227+
// Test passed.
228+
return true, nil
229+
} else {
230+
// Test failed, either by ending with the word "FAIL" or with a
231+
// panic of some sort.
232+
return false, nil
233+
}
234+
}
203235
}
204236

205237
// Flash builds and flashes the built binary to the given serial port.
@@ -1072,16 +1104,26 @@ func main() {
10721104
err := Run(pkgName, options)
10731105
handleCompilerError(err)
10741106
case "test":
1075-
pkgName := "."
1076-
if flag.NArg() == 1 {
1077-
pkgName = filepath.ToSlash(flag.Arg(0))
1078-
} else if flag.NArg() > 1 {
1079-
fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified")
1080-
usage()
1107+
var pkgNames []string
1108+
for i := 0; i < flag.NArg(); i++ {
1109+
pkgNames = append(pkgNames, filepath.ToSlash(flag.Arg(i)))
1110+
}
1111+
if len(pkgNames) == 0 {
1112+
pkgNames = []string{"."}
1113+
}
1114+
allTestsPassed := true
1115+
for _, pkgName := range pkgNames {
1116+
// TODO: parallelize building the test binaries
1117+
passed, err := Test(pkgName, options, *testCompileOnlyFlag, outpath)
1118+
handleCompilerError(err)
1119+
if !passed {
1120+
allTestsPassed = false
1121+
}
1122+
}
1123+
if !allTestsPassed {
1124+
fmt.Println("FAIL")
10811125
os.Exit(1)
10821126
}
1083-
err := Test(pkgName, options, *testCompileOnlyFlag, outpath)
1084-
handleCompilerError(err)
10851127
case "targets":
10861128
dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets")
10871129
entries, err := ioutil.ReadDir(dir)

src/testing/testing.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ type M struct {
201201

202202
// Run the test suite.
203203
func (m *M) Run() int {
204+
if len(m.Tests) == 0 {
205+
fmt.Fprintln(os.Stderr, "testing: warning: no tests to run")
206+
}
207+
204208
failures := 0
205209
for _, test := range m.Tests {
206210
t := &T{
@@ -226,7 +230,6 @@ func (m *M) Run() int {
226230
}
227231

228232
if failures > 0 {
229-
fmt.Printf("exit status %d\n", failures)
230233
fmt.Println("FAIL")
231234
} else {
232235
fmt.Println("PASS")

0 commit comments

Comments
 (0)