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.
Этот коммит содержится в:
Ayke van Laethem 2021-05-06 18:54:59 +02:00 коммит произвёл Ron Evans
родитель 617e2791ef
коммит 78acbdf0d9
6 изменённых файлов: 139 добавлений и 69 удалений

Просмотреть файл

@ -182,25 +182,28 @@ tinygo:
test: wasi-libc test: wasi-libc
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 . 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 .
TEST_PACKAGES = \
container/heap \
container/list \
container/ring \
crypto/des \
encoding \
encoding/ascii85 \
encoding/base32 \
encoding/hex \
hash/adler32 \
hash/fnv \
hash/crc64 \
math \
math/cmplx \
text/scanner \
unicode/utf8 \
# Test known-working standard library packages. # Test known-working standard library packages.
# TODO: do this in one command, parallelize, and only show failing tests (no # TODO: parallelize, and only show failing tests (no implied -v flag).
# implied -v flag).
.PHONY: tinygo-test .PHONY: tinygo-test
tinygo-test: tinygo-test:
$(TINYGO) test container/heap $(TINYGO) test $(TEST_PACKAGES)
$(TINYGO) test container/list
$(TINYGO) test container/ring
$(TINYGO) test crypto/des
$(TINYGO) test encoding/ascii85
$(TINYGO) test encoding/base32
$(TINYGO) test encoding/hex
$(TINYGO) test hash/adler32
$(TINYGO) test hash/fnv
$(TINYGO) test hash/crc64
$(TINYGO) test math
$(TINYGO) test math/cmplx
$(TINYGO) test text/scanner
$(TINYGO) test unicode/utf8
.PHONY: smoketest .PHONY: smoketest
smoketest: smoketest:

Просмотреть файл

@ -39,6 +39,11 @@ type BuildResult struct {
// The directory of the main package. This is useful for testing as the test // The directory of the main package. This is useful for testing as the test
// binary must be run in the directory of the tested package. // binary must be run in the directory of the tested package.
MainDir string MainDir string
// ImportPath is the import path of the main package. This is useful for
// correctly printing test results: the import path isn't always the same as
// the path listed on the command line.
ImportPath string
} }
// packageAction is the struct that is serialized to JSON and hashed, to work as // 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
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat) return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
} }
return action(BuildResult{ return action(BuildResult{
Binary: tmppath, Binary: tmppath,
MainDir: lprogram.MainPkg().Dir, MainDir: lprogram.MainPkg().Dir,
ImportPath: lprogram.MainPkg().ImportPath,
}) })
} }

Просмотреть файл

@ -23,3 +23,13 @@ type Error struct {
func (e Error) Error() string { func (e Error) Error() string {
return e.Err.Error() return e.Err.Error()
} }
// Error returned when loading a *Program for a test binary but no test files
// are present.
type NoTestFilesError struct {
ImportPath string
}
func (e NoTestFilesError) Error() string {
return "no test files"
}

Просмотреть файл

@ -210,6 +210,12 @@ func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, t
p.Packages[pkg.ImportPath] = pkg p.Packages[pkg.ImportPath] = pkg
} }
if config.TestConfig.CompileTestBinary && !strings.HasSuffix(p.sorted[len(p.sorted)-1].ImportPath, ".test") {
// Trying to compile a test binary but there are no test files in this
// package.
return p, NoTestFilesError{p.sorted[len(p.sorted)-1].ImportPath}
}
return p, nil return p, nil
} }

142
main.go
Просмотреть файл

@ -136,15 +136,17 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
}) })
} }
// Test runs the tests in the given package. // Test runs the tests in the given package. Returns whether the test passed and
func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, outpath string) error { // possibly an error if the test failed to run.
func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, outpath string) (bool, error) {
options.TestConfig.CompileTestBinary = true options.TestConfig.CompileTestBinary = true
config, err := builder.NewConfig(options) config, err := builder.NewConfig(options)
if err != nil { if err != nil {
return err return false, err
} }
return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error { var passed bool
err = builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
if testCompileOnly || outpath != "" { if testCompileOnly || outpath != "" {
// Write test binary to the specified file name. // Write test binary to the specified file name.
if outpath == "" { if outpath == "" {
@ -158,48 +160,78 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
// Do not run the test. // Do not run the test.
return nil return nil
} }
if len(config.Target.Emulator) == 0 {
// Run directly. // Run the test.
cmd := executeCommand(config.Options, result.Binary) start := time.Now()
cmd.Stdout = os.Stdout var err error
cmd.Stderr = os.Stderr passed, err = runPackageTest(config, result)
cmd.Dir = result.MainDir if err != nil {
err := cmd.Run() return err
if err != nil { }
// Propagate the exit code duration := time.Since(start)
if err, ok := err.(*exec.ExitError); ok {
os.Exit(err.ExitCode()) // Print the result.
} importPath := strings.TrimSuffix(result.ImportPath, ".test")
return &commandError{"failed to run compiled binary", result.Binary, err} if passed {
} fmt.Printf("ok \t%s\t%.3fs\n", importPath, duration.Seconds())
return nil
} else { } else {
// Run in an emulator. fmt.Printf("FAIL\t%s\t%.3fs\n", importPath, duration.Seconds())
args := append(config.Target.Emulator[1:], result.Binary) }
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...) return nil
buf := &bytes.Buffer{} })
w := io.MultiWriter(os.Stdout, buf) if err, ok := err.(loader.NoTestFilesError); ok {
cmd.Stdout = w fmt.Printf("? \t%s\t[no test files]\n", err.ImportPath)
cmd.Stderr = os.Stderr // Pretend the test passed - it at least didn't fail.
err := cmd.Run() return true, nil
if err != nil { }
if err, ok := err.(*exec.ExitError); !ok || !err.Exited() { return passed, err
// Workaround for QEMU which always exits with an error. }
return &commandError{"failed to run emulator with", result.Binary, err}
} // runPackageTest runs a test binary that was previously built. The return
// values are whether the test passed and any errors encountered while trying to
// run the binary.
func runPackageTest(config *compileopts.Config, result builder.BuildResult) (bool, error) {
if len(config.Target.Emulator) == 0 {
// Run directly.
cmd := executeCommand(config.Options, result.Binary)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = result.MainDir
err := cmd.Run()
if err != nil {
if _, ok := err.(*exec.ExitError); ok {
// Binary exited with a non-zero exit code, which means the test
// failed.
return false, nil
} }
testOutput := string(buf.Bytes()) return false, &commandError{"failed to run compiled binary", result.Binary, err}
if testOutput == "PASS\n" || strings.HasSuffix(testOutput, "\nPASS\n") { }
// Test passed. return true, nil
return nil } else {
} else { // Run in an emulator.
// Test failed, either by ending with the word "FAIL" or with a args := append(config.Target.Emulator[1:], result.Binary)
// panic of some sort. cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
os.Exit(1) buf := &bytes.Buffer{}
return nil // unreachable w := io.MultiWriter(os.Stdout, buf)
cmd.Stdout = w
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if err, ok := err.(*exec.ExitError); !ok || !err.Exited() {
// Workaround for QEMU which always exits with an error.
return false, &commandError{"failed to run emulator with", result.Binary, err}
} }
} }
}) testOutput := string(buf.Bytes())
if testOutput == "PASS\n" || strings.HasSuffix(testOutput, "\nPASS\n") {
// Test passed.
return true, nil
} else {
// Test failed, either by ending with the word "FAIL" or with a
// panic of some sort.
return false, nil
}
}
} }
// Flash builds and flashes the built binary to the given serial port. // Flash builds and flashes the built binary to the given serial port.
@ -1072,16 +1104,26 @@ func main() {
err := Run(pkgName, options) err := Run(pkgName, options)
handleCompilerError(err) handleCompilerError(err)
case "test": case "test":
pkgName := "." var pkgNames []string
if flag.NArg() == 1 { for i := 0; i < flag.NArg(); i++ {
pkgName = filepath.ToSlash(flag.Arg(0)) pkgNames = append(pkgNames, filepath.ToSlash(flag.Arg(i)))
} else if flag.NArg() > 1 { }
fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified") if len(pkgNames) == 0 {
usage() pkgNames = []string{"."}
}
allTestsPassed := true
for _, pkgName := range pkgNames {
// TODO: parallelize building the test binaries
passed, err := Test(pkgName, options, *testCompileOnlyFlag, outpath)
handleCompilerError(err)
if !passed {
allTestsPassed = false
}
}
if !allTestsPassed {
fmt.Println("FAIL")
os.Exit(1) os.Exit(1)
} }
err := Test(pkgName, options, *testCompileOnlyFlag, outpath)
handleCompilerError(err)
case "targets": case "targets":
dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets") dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets")
entries, err := ioutil.ReadDir(dir) entries, err := ioutil.ReadDir(dir)

Просмотреть файл

@ -201,6 +201,10 @@ type M struct {
// Run the test suite. // Run the test suite.
func (m *M) Run() int { func (m *M) Run() int {
if len(m.Tests) == 0 {
fmt.Fprintln(os.Stderr, "testing: warning: no tests to run")
}
failures := 0 failures := 0
for _, test := range m.Tests { for _, test := range m.Tests {
t := &T{ t := &T{
@ -226,7 +230,6 @@ func (m *M) Run() int {
} }
if failures > 0 { if failures > 0 {
fmt.Printf("exit status %d\n", failures)
fmt.Println("FAIL") fmt.Println("FAIL")
} else { } else {
fmt.Println("PASS") fmt.Println("PASS")