From a465044eae9a400e105f78233ee90dd30916c585 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Tue, 19 Jan 2016 20:43:46 +0100 Subject: [PATCH 1/4] Add support for TestMain function in test mode --- tool.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/tool.go b/tool.go index 8673ce5de..f6263f76e 100644 --- a/tool.go +++ b/tool.go @@ -335,11 +335,16 @@ func main() { } for _, decl := range archive.Declarations { - if strings.HasPrefix(decl.FullName, testPkg.ImportPath+".Test") { + switch { + case decl.FullName == testPkg.ImportPath+".TestMain": + if tests.TestMain != nil { + return fmt.Errorf("multiple definitions of TestMain") + } + tests.TestMain = &testFunc{Package: testPkgName, Name: decl.FullName[len(testPkg.ImportPath)+1:]} + case strings.HasPrefix(decl.FullName, testPkg.ImportPath+".Test"): tests.Tests = append(tests.Tests, testFunc{Package: testPkgName, Name: decl.FullName[len(testPkg.ImportPath)+1:]}) *needVar = true - } - if strings.HasPrefix(decl.FullName, testPkg.ImportPath+".Benchmark") { + case strings.HasPrefix(decl.FullName, testPkg.ImportPath+".Benchmark"): tests.Benchmarks = append(tests.Benchmarks, testFunc{Package: testPkgName, Name: decl.FullName[len(testPkg.ImportPath)+1:]}) *needVar = true } @@ -773,6 +778,7 @@ type testFuncs struct { Tests []testFunc Benchmarks []testFunc Examples []testFunc + TestMain *testFunc Package *build.Package NeedTest bool NeedXtest bool @@ -788,6 +794,9 @@ var testmainTmpl = template.Must(template.New("main").Parse(` package main import ( +{{if not .TestMain}} + "os" +{{end}} "regexp" "testing" @@ -832,7 +841,12 @@ func matchString(pat, str string) (result bool, err error) { } func main() { - testing.Main(matchString, tests, benchmarks, examples) + m := testing.MainStart(matchString, tests, benchmarks, examples) +{{with .TestMain}} + {{.Package}}.{{.Name}}(m) +{{else}} + os.Exit(m.Run()) +{{end}} } `)) From 1d1a6b7ca0dcbe659cacee26de52b811cd4506bf Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Sat, 23 Jan 2016 13:31:35 +0100 Subject: [PATCH 2/4] Borrow a lot more logic from Go for test handling Specifically, we now borrow Go's testFuncs.load(), isTestMain, and isTest functions, instead of relying on our own, less robust, implementation. In terms of functionality, this means that we: - We require the first character after the 'Test' prefix to be capital. e.g. don't treat TesticularCancer() as a test. - We don't treat TestMain(t *testing.T) as TestMain(m *testing.M) - We should be able to support Examples as well (issue #239) with minimal effort --- tool.go | 144 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 119 insertions(+), 25 deletions(-) diff --git a/tool.go b/tool.go index f6263f76e..cf7833b08 100644 --- a/tool.go +++ b/tool.go @@ -2,9 +2,11 @@ package main import ( "bytes" + "errors" "fmt" "go/ast" "go/build" + "go/doc" "go/parser" "go/scanner" "go/token" @@ -18,11 +20,14 @@ import ( "path" "path/filepath" "runtime" + // "sort" "strconv" "strings" "syscall" "text/template" "time" + "unicode" + "unicode/utf8" gbuild "github.com/gopherjs/gopherjs/build" "github.com/gopherjs/gopherjs/compiler" @@ -325,28 +330,25 @@ func main() { fmt.Printf("? \t%s\t[no test files]\n", pkg.ImportPath) continue } - s := gbuild.NewSession(options) + tests := &testFuncs{Package: pkg.Package} collectTests := func(testPkg *gbuild.PackageData, testPkgName string, needVar *bool) error { archive, err := s.BuildPackage(testPkg) if err != nil { return err } - - for _, decl := range archive.Declarations { - switch { - case decl.FullName == testPkg.ImportPath+".TestMain": - if tests.TestMain != nil { - return fmt.Errorf("multiple definitions of TestMain") + if testPkgName == "_test" { + for _, file := range pkg.TestGoFiles { + if err := tests.load(filepath.Join(pkg.Package.Dir, file), testPkgName, &tests.ImportTest, &tests.NeedTest); err != nil { + return err + } + } + } else { + for _, file := range pkg.XTestGoFiles { + if err := tests.load(filepath.Join(pkg.Package.Dir, file), "_xtest", &tests.ImportXtest, &tests.NeedXtest); err != nil { + return err } - tests.TestMain = &testFunc{Package: testPkgName, Name: decl.FullName[len(testPkg.ImportPath)+1:]} - case strings.HasPrefix(decl.FullName, testPkg.ImportPath+".Test"): - tests.Tests = append(tests.Tests, testFunc{Package: testPkgName, Name: decl.FullName[len(testPkg.ImportPath)+1:]}) - *needVar = true - case strings.HasPrefix(decl.FullName, testPkg.ImportPath+".Benchmark"): - tests.Benchmarks = append(tests.Benchmarks, testFunc{Package: testPkgName, Name: decl.FullName[len(testPkg.ImportPath)+1:]}) - *needVar = true } } return nil @@ -775,13 +777,15 @@ func runNode(script string, args []string, dir string, quiet bool) error { } type testFuncs struct { - Tests []testFunc - Benchmarks []testFunc - Examples []testFunc - TestMain *testFunc - Package *build.Package - NeedTest bool - NeedXtest bool + Tests []testFunc + Benchmarks []testFunc + Examples []testFunc + TestMain *testFunc + Package *build.Package + ImportTest bool + NeedTest bool + ImportXtest bool + NeedXtest bool } type testFunc struct { @@ -790,6 +794,96 @@ type testFunc struct { Output string // output, for examples } +var testFileSet = token.NewFileSet() + +func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error { + f, err := parser.ParseFile(testFileSet, filename, nil, parser.ParseComments) + if err != nil { + return err + } + for _, d := range f.Decls { + n, ok := d.(*ast.FuncDecl) + if !ok { + continue + } + if n.Recv != nil { + continue + } + name := n.Name.String() + switch { + case isTestMain(n): + if t.TestMain != nil { + return errors.New("multiple definitions of TestMain") + } + t.TestMain = &testFunc{pkg, name, ""} + *doImport, *seen = true, true + case isTest(name, "Test"): + t.Tests = append(t.Tests, testFunc{pkg, name, ""}) + *doImport, *seen = true, true + case isTest(name, "Benchmark"): + t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, ""}) + *doImport, *seen = true, true + } + } + // ex := doc.Examples(f) + // sort.Sort(byOrder(ex)) + // for _, e := range ex { + // *doImport = true // import test file whether executed or not + // if e.Output == "" && !e.EmptyOutput { + // // Don't run examples with no output. + // continue + // } + // t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output}) + // *seen = true + // } + return nil +} + +type byOrder []*doc.Example + +func (x byOrder) Len() int { return len(x) } +func (x byOrder) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byOrder) Less(i, j int) bool { return x[i].Order < x[j].Order } + +// isTestMain tells whether fn is a TestMain(m *testing.M) function. +func isTestMain(fn *ast.FuncDecl) bool { + if fn.Name.String() != "TestMain" || + fn.Type.Results != nil && len(fn.Type.Results.List) > 0 || + fn.Type.Params == nil || + len(fn.Type.Params.List) != 1 || + len(fn.Type.Params.List[0].Names) > 1 { + return false + } + ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr) + if !ok { + return false + } + // We can't easily check that the type is *testing.M + // because we don't know how testing has been imported, + // but at least check that it's *M or *something.M. + if name, ok := ptr.X.(*ast.Ident); ok && name.Name == "M" { + return true + } + if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == "M" { + return true + } + return false +} + +// isTest tells whether name looks like a test (or benchmark, according to prefix). +// It is a Test (say) if there is a character after Test that is not a lower-case letter. +// We don't want TesticularCancer. +func isTest(name, prefix string) bool { + if !strings.HasPrefix(name, prefix) { + return false + } + if len(name) == len(prefix) { // "Test" is ok + return true + } + rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) + return !unicode.IsLower(rune) +} + var testmainTmpl = template.Must(template.New("main").Parse(` package main @@ -800,11 +894,11 @@ import ( "regexp" "testing" -{{if .NeedTest}} - _test {{.Package.ImportPath | printf "%q"}} +{{if .ImportTest}} + {{if .NeedTest}}_test{{else}}_{{end}} {{.Package.ImportPath | printf "%q"}} {{end}} -{{if .NeedXtest}} - _xtest {{.Package.ImportPath | printf "%s_test" | printf "%q"}} +{{if .ImportXtest}} + {{if .NeedXtest}}_xtest{{else}}_{{end}} {{.Package.ImportPath | printf "%s_test" | printf "%q"}} {{end}} ) From 15d38c0c1cb889104eb46896c990e6be38df055a Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Sun, 15 May 2016 12:27:29 +0200 Subject: [PATCH 3/4] Ignore archive return value from BuildPackage for tests --- tool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool.go b/tool.go index cf7833b08..3ea5d9702 100644 --- a/tool.go +++ b/tool.go @@ -334,7 +334,7 @@ func main() { tests := &testFuncs{Package: pkg.Package} collectTests := func(testPkg *gbuild.PackageData, testPkgName string, needVar *bool) error { - archive, err := s.BuildPackage(testPkg) + _, err := s.BuildPackage(testPkg) if err != nil { return err } From 6c270aebfb43f59f77d951c830ec1fe1a0857d28 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Sun, 15 May 2016 12:39:22 +0200 Subject: [PATCH 4/4] Re-order logic to give TestMain-specific error messages precedence --- tool.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tool.go b/tool.go index 3ea5d9702..6d9cc5aa2 100644 --- a/tool.go +++ b/tool.go @@ -334,10 +334,6 @@ func main() { tests := &testFuncs{Package: pkg.Package} collectTests := func(testPkg *gbuild.PackageData, testPkgName string, needVar *bool) error { - _, err := s.BuildPackage(testPkg) - if err != nil { - return err - } if testPkgName == "_test" { for _, file := range pkg.TestGoFiles { if err := tests.load(filepath.Join(pkg.Package.Dir, file), testPkgName, &tests.ImportTest, &tests.NeedTest); err != nil { @@ -351,6 +347,10 @@ func main() { } } } + _, err := s.BuildPackage(testPkg) + if err != nil { + return err + } return nil }