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.
Этот коммит содержится в:
родитель
617e2791ef
коммит
78acbdf0d9
6 изменённых файлов: 139 добавлений и 69 удалений
35
Makefile
35
Makefile
|
@ -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
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")
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче