aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorCarolyn Van Slyck <[email protected]>2019-06-18 03:23:59 -0700
committerRon Evans <[email protected]>2019-06-18 12:23:59 +0200
commit208e1719ad9a8e7ff94e8aac900909c37747a04f (patch)
tree0ec53ee94cc447f84e6bbfe51ef68ad428bf2bf4
parenta3d1f1a514aaaad6c2bfe6024ee48211cf6b7575 (diff)
downloadtinygo-208e1719ad9a8e7ff94e8aac900909c37747a04f.tar.gz
tinygo-208e1719ad9a8e7ff94e8aac900909c37747a04f.zip
Add test command to tinygo (#243)
* Add test command to tinygo
-rw-r--r--Makefile4
-rw-r--r--compiler/compiler.go12
-rw-r--r--loader/loader.go137
-rw-r--r--main.go33
-rw-r--r--src/reflect/value.go2
-rw-r--r--src/testing/doc.go6
-rw-r--r--src/testing/testing.go77
-rw-r--r--tests/tinygotest/main.go14
-rw-r--r--tests/tinygotest/main_test.go16
9 files changed, 288 insertions, 13 deletions
diff --git a/Makefile b/Makefile
index c65fa7d27..04342dd1e 100644
--- a/Makefile
+++ b/Makefile
@@ -38,6 +38,7 @@ fmt:
fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
+
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-stm32
gen-device-avr:
@@ -85,6 +86,9 @@ build/tinygo:
test:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
+tinygo-test:
+ cd tests/tinygotest && tinygo test
+
.PHONY: smoketest smoketest-no-avr
smoketest: smoketest-no-avr
tinygo build -size short -o test.elf -target=arduino examples/blinky1
diff --git a/compiler/compiler.go b/compiler/compiler.go
index 27ee530bc..93c3f496a 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -48,6 +48,12 @@ type Config struct {
TINYGOROOT string // GOROOT for TinyGo
GOPATH string // GOPATH, like `go env GOPATH`
BuildTags []string // build tags for TinyGo (empty means {Config.GOOS/Config.GOARCH})
+ TestConfig TestConfig
+}
+
+type TestConfig struct {
+ CompileTestBinary bool
+ // TODO: Filter the test functions to run, include verbose flag, etc
}
type Compiler struct {
@@ -214,7 +220,7 @@ func (c *Compiler) Compile(mainPath string) []error {
path = path[len(tinygoPath+"/src/"):]
}
switch path {
- case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync":
+ case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing":
return path
default:
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
@@ -241,6 +247,7 @@ func (c *Compiler) Compile(mainPath string) []error {
CFlags: c.CFlags,
ClangHeaders: c.ClangHeaders,
}
+
if strings.HasSuffix(mainPath, ".go") {
_, err = lprogram.ImportFile(mainPath)
if err != nil {
@@ -252,12 +259,13 @@ func (c *Compiler) Compile(mainPath string) []error {
return []error{err}
}
}
+
_, err = lprogram.Import("runtime", "")
if err != nil {
return []error{err}
}
- err = lprogram.Parse()
+ err = lprogram.Parse(c.TestConfig.CompileTestBinary)
if err != nil {
return []error{err}
}
diff --git a/loader/loader.go b/loader/loader.go
index cfa81e99f..1cf371565 100644
--- a/loader/loader.go
+++ b/loader/loader.go
@@ -1,6 +1,7 @@
package loader
import (
+ "bytes"
"errors"
"go/ast"
"go/build"
@@ -10,12 +11,15 @@ import (
"os"
"path/filepath"
"sort"
+ "strings"
+ "text/template"
"github.com/tinygo-org/tinygo/cgo"
)
// Program holds all packages and some metadata about the program as a whole.
type Program struct {
+ mainPkg string
Build *build.Context
OverlayBuild *build.Context
OverlayPath func(path string) string
@@ -64,6 +68,11 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
+
+ if p.mainPkg == "" {
+ p.mainPkg = buildPkg.ImportPath
+ }
+
return pkg, nil
}
@@ -93,6 +102,11 @@ func (p *Program) ImportFile(path string) (*Package, error) {
p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
+
+ if p.mainPkg == "" {
+ p.mainPkg = buildPkg.ImportPath
+ }
+
return pkg, nil
}
@@ -171,10 +185,12 @@ func (p *Program) sort() {
// The returned error may be an Errors error, which contains a list of errors.
//
// Idempotent.
-func (p *Program) Parse() error {
+func (p *Program) Parse(compileTestBinary bool) error {
+ includeTests := compileTestBinary
+
// Load all imports
for _, pkg := range p.Sorted() {
- err := pkg.importRecursively()
+ err := pkg.importRecursively(includeTests)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
if pkg.ImportPath != err.Packages[0] {
@@ -187,7 +203,14 @@ func (p *Program) Parse() error {
// Parse all packages.
for _, pkg := range p.Sorted() {
- err := pkg.Parse()
+ err := pkg.Parse(includeTests)
+ if err != nil {
+ return err
+ }
+ }
+
+ if compileTestBinary {
+ err := p.SwapTestMain()
if err != nil {
return err
}
@@ -204,6 +227,83 @@ func (p *Program) Parse() error {
return nil
}
+func (p *Program) SwapTestMain() error {
+ var tests []string
+
+ isTestFunc := func(f *ast.FuncDecl) bool {
+ // TODO: improve signature check
+ if strings.HasPrefix(f.Name.Name, "Test") && f.Name.Name != "TestMain" {
+ return true
+ }
+ return false
+ }
+ mainPkg := p.Packages[p.mainPkg]
+ for _, f := range mainPkg.Files {
+ for i, d := range f.Decls {
+ switch v := d.(type) {
+ case *ast.FuncDecl:
+ if isTestFunc(v) {
+ tests = append(tests, v.Name.Name)
+ }
+ if v.Name.Name == "main" {
+ // Remove main
+ if len(f.Decls) == 1 {
+ f.Decls = make([]ast.Decl, 0)
+ } else {
+ f.Decls[i] = f.Decls[len(f.Decls)-1]
+ f.Decls = f.Decls[:len(f.Decls)-1]
+ }
+ }
+ }
+ }
+ }
+
+ // TODO: Check if they defined a TestMain and call it instead of testing.TestMain
+ const mainBody = `package main
+
+import (
+ "testing"
+)
+
+func main () {
+ m := &testing.M{
+ Tests: []testing.TestToCall{
+{{range .TestFunctions}}
+ {Name: "{{.}}", Func: {{.}}},
+{{end}}
+ },
+ }
+
+ testing.TestMain(m)
+}
+`
+ tmpl := template.Must(template.New("testmain").Parse(mainBody))
+ b := bytes.Buffer{}
+ tmplData := struct {
+ TestFunctions []string
+ }{
+ TestFunctions: tests,
+ }
+
+ err := tmpl.Execute(&b, tmplData)
+ if err != nil {
+ return err
+ }
+ path := filepath.Join(p.mainPkg, "$testmain.go")
+
+ if p.fset == nil {
+ p.fset = token.NewFileSet()
+ }
+
+ newMain, err := parser.ParseFile(p.fset, path, b.Bytes(), parser.AllErrors)
+ if err != nil {
+ return err
+ }
+ mainPkg.Files = append(mainPkg.Files, newMain)
+
+ return nil
+}
+
// parseFile is a wrapper around parser.ParseFile.
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
if p.fset == nil {
@@ -228,7 +328,7 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
// Parse parses and typechecks this package.
//
// Idempotent.
-func (p *Package) Parse() error {
+func (p *Package) Parse(includeTests bool) error {
if len(p.Files) != 0 {
return nil
}
@@ -242,7 +342,7 @@ func (p *Package) Parse() error {
return nil
}
- files, err := p.parseFiles()
+ files, err := p.parseFiles(includeTests)
if err != nil {
return err
}
@@ -281,11 +381,21 @@ func (p *Package) Check() error {
}
// parseFiles parses the loaded list of files and returns this list.
-func (p *Package) parseFiles() ([]*ast.File, error) {
+func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
// TODO: do this concurrently.
var files []*ast.File
var fileErrs []error
- for _, file := range p.GoFiles {
+
+ var gofiles []string
+ if includeTests {
+ gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles))
+ gofiles = append(gofiles, p.GoFiles...)
+ gofiles = append(gofiles, p.TestGoFiles...)
+ } else {
+ gofiles = p.GoFiles
+ }
+
+ for _, file := range gofiles {
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
if err != nil {
fileErrs = append(fileErrs, err)
@@ -320,6 +430,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
if len(fileErrs) != 0 {
return nil, Errors{p, fileErrs}
}
+
return files, nil
}
@@ -340,9 +451,15 @@ func (p *Package) Import(to string) (*types.Package, error) {
// importRecursively() on the imported packages as well.
//
// Idempotent.
-func (p *Package) importRecursively() error {
+func (p *Package) importRecursively(includeTests bool) error {
p.Importing = true
- for _, to := range p.Package.Imports {
+
+ imports := p.Package.Imports
+ if includeTests {
+ imports = append(imports, p.Package.TestImports...)
+ }
+
+ for _, to := range imports {
if to == "C" {
// Do CGo processing in a later stage.
continue
@@ -360,7 +477,7 @@ func (p *Package) importRecursively() error {
if importedPkg.Importing {
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
}
- err = importedPkg.importRecursively()
+ err = importedPkg.importRecursively(false)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...)
diff --git a/main.go b/main.go
index 4eaa359cb..97ea47d36 100644
--- a/main.go
+++ b/main.go
@@ -54,6 +54,7 @@ type BuildConfig struct {
cFlags []string
ldFlags []string
wasmAbi string
+ testConfig compiler.TestConfig
}
// Helper function for Compiler object.
@@ -108,6 +109,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
GOROOT: goroot,
GOPATH: getGopath(),
BuildTags: tags,
+ TestConfig: config.testConfig,
}
c, err := compiler.NewCompiler(pkgName, compilerConfig)
if err != nil {
@@ -349,6 +351,30 @@ func Build(pkgName, outpath, target string, config *BuildConfig) error {
})
}
+func Test(pkgName, target string, config *BuildConfig) error {
+ spec, err := LoadTarget(target)
+ if err != nil {
+ return err
+ }
+
+ spec.BuildTags = append(spec.BuildTags, "test")
+ config.testConfig.CompileTestBinary = true
+ return Compile(pkgName, ".elf", spec, config, func(tmppath string) error {
+ cmd := exec.Command(tmppath)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err := cmd.Run()
+ if err != nil {
+ // Propagate the exit code
+ if err, ok := err.(*exec.ExitError); ok {
+ os.Exit(err.ExitCode())
+ }
+ return &commandError{"failed to run compiled binary", tmppath, err}
+ }
+ return nil
+ })
+}
+
func Flash(pkgName, target, port string, config *BuildConfig) error {
spec, err := LoadTarget(target)
if err != nil {
@@ -656,6 +682,13 @@ func main() {
}
err := Run(flag.Arg(0), *target, config)
handleCompilerError(err)
+ case "test":
+ pkgRoot := "."
+ if flag.NArg() == 1 {
+ pkgRoot = flag.Arg(0)
+ }
+ err := Test(pkgRoot, *target, config)
+ handleCompilerError(err)
case "clean":
// remove cache directory
dir := cacheDir()
diff --git a/src/reflect/value.go b/src/reflect/value.go
index 51128b81e..577a944ba 100644
--- a/src/reflect/value.go
+++ b/src/reflect/value.go
@@ -92,7 +92,7 @@ func (v Value) Pointer() uintptr {
}
func (v Value) IsValid() bool {
- panic("unimplemented: (reflect.Value).IsValid()")
+ return v.typecode != 0
}
func (v Value) CanInterface() bool {
diff --git a/src/testing/doc.go b/src/testing/doc.go
new file mode 100644
index 000000000..b5026d806
--- /dev/null
+++ b/src/testing/doc.go
@@ -0,0 +1,6 @@
+package testing
+
+/*
+ This is a sad stub of the upstream testing package because it doesn't compile
+ with tinygo right now.
+*/
diff --git a/src/testing/testing.go b/src/testing/testing.go
new file mode 100644
index 000000000..1d7ea051d
--- /dev/null
+++ b/src/testing/testing.go
@@ -0,0 +1,77 @@
+package testing
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+)
+
+// T is a test helper.
+type T struct {
+ name string
+ output io.Writer
+
+ // flags the test as having failed when non-zero
+ failed int
+}
+
+// TestToCall is a reference to a test that should be called during a test suite run.
+type TestToCall struct {
+ // Name of the test to call.
+ Name string
+ // Function reference to the test.
+ Func func(*T)
+}
+
+// M is a test suite.
+type M struct {
+ // tests is a list of the test names to execute
+ Tests []TestToCall
+}
+
+// Run the test suite.
+func (m *M) Run() int {
+ failures := 0
+ for _, test := range m.Tests {
+ t := &T{
+ name: test.Name,
+ output: &bytes.Buffer{},
+ }
+
+ fmt.Printf("=== RUN %s\n", test.Name)
+ test.Func(t)
+
+ if t.failed == 0 {
+ fmt.Printf("--- PASS: %s\n", test.Name)
+ } else {
+ fmt.Printf("--- FAIL: %s\n", test.Name)
+ }
+ fmt.Println(t.output)
+
+ failures += t.failed
+ }
+
+ if failures > 0 {
+ fmt.Printf("exit status %d\n", failures)
+ fmt.Println("FAIL")
+ }
+ return failures
+}
+
+func TestMain(m *M) {
+ os.Exit(m.Run())
+}
+
+// Error is equivalent to Log followed by Fail
+func (t *T) Error(args ...interface{}) {
+ // This doesn't print the same as in upstream go, but works good enough
+ // TODO: buffer test output like go does
+ fmt.Fprintf(t.output, "\t")
+ fmt.Fprintln(t.output, args...)
+ t.Fail()
+}
+
+func (t *T) Fail() {
+ t.failed = 1
+}
diff --git a/tests/tinygotest/main.go b/tests/tinygotest/main.go
new file mode 100644
index 000000000..84ed97000
--- /dev/null
+++ b/tests/tinygotest/main.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "fmt"
+)
+
+func main() {
+ Thing()
+ fmt.Println("normal main")
+}
+
+func Thing() {
+ fmt.Println("THING")
+}
diff --git a/tests/tinygotest/main_test.go b/tests/tinygotest/main_test.go
new file mode 100644
index 000000000..61ce28285
--- /dev/null
+++ b/tests/tinygotest/main_test.go
@@ -0,0 +1,16 @@
+package main
+
+import (
+ "testing" // This is the tinygo testing package
+)
+
+func TestFail1(t *testing.T) {
+ t.Error("TestFail1 failed because of stuff and things")
+}
+
+func TestFail2(t *testing.T) {
+ t.Error("TestFail2 failed for reasons")
+}
+
+func TestPass(t *testing.T) {
+}