Skip to content

Commit 1d1a6b7

Browse files
committed
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
1 parent a465044 commit 1d1a6b7

File tree

1 file changed

+119
-25
lines changed

1 file changed

+119
-25
lines changed

tool.go

Lines changed: 119 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package main
22

33
import (
44
"bytes"
5+
"errors"
56
"fmt"
67
"go/ast"
78
"go/build"
9+
"go/doc"
810
"go/parser"
911
"go/scanner"
1012
"go/token"
@@ -18,11 +20,14 @@ import (
1820
"path"
1921
"path/filepath"
2022
"runtime"
23+
// "sort"
2124
"strconv"
2225
"strings"
2326
"syscall"
2427
"text/template"
2528
"time"
29+
"unicode"
30+
"unicode/utf8"
2631

2732
gbuild "github.com/gopherjs/gopherjs/build"
2833
"github.com/gopherjs/gopherjs/compiler"
@@ -325,28 +330,25 @@ func main() {
325330
fmt.Printf("? \t%s\t[no test files]\n", pkg.ImportPath)
326331
continue
327332
}
328-
329333
s := gbuild.NewSession(options)
334+
330335
tests := &testFuncs{Package: pkg.Package}
331336
collectTests := func(testPkg *gbuild.PackageData, testPkgName string, needVar *bool) error {
332337
archive, err := s.BuildPackage(testPkg)
333338
if err != nil {
334339
return err
335340
}
336-
337-
for _, decl := range archive.Declarations {
338-
switch {
339-
case decl.FullName == testPkg.ImportPath+".TestMain":
340-
if tests.TestMain != nil {
341-
return fmt.Errorf("multiple definitions of TestMain")
341+
if testPkgName == "_test" {
342+
for _, file := range pkg.TestGoFiles {
343+
if err := tests.load(filepath.Join(pkg.Package.Dir, file), testPkgName, &tests.ImportTest, &tests.NeedTest); err != nil {
344+
return err
345+
}
346+
}
347+
} else {
348+
for _, file := range pkg.XTestGoFiles {
349+
if err := tests.load(filepath.Join(pkg.Package.Dir, file), "_xtest", &tests.ImportXtest, &tests.NeedXtest); err != nil {
350+
return err
342351
}
343-
tests.TestMain = &testFunc{Package: testPkgName, Name: decl.FullName[len(testPkg.ImportPath)+1:]}
344-
case strings.HasPrefix(decl.FullName, testPkg.ImportPath+".Test"):
345-
tests.Tests = append(tests.Tests, testFunc{Package: testPkgName, Name: decl.FullName[len(testPkg.ImportPath)+1:]})
346-
*needVar = true
347-
case strings.HasPrefix(decl.FullName, testPkg.ImportPath+".Benchmark"):
348-
tests.Benchmarks = append(tests.Benchmarks, testFunc{Package: testPkgName, Name: decl.FullName[len(testPkg.ImportPath)+1:]})
349-
*needVar = true
350352
}
351353
}
352354
return nil
@@ -775,13 +777,15 @@ func runNode(script string, args []string, dir string, quiet bool) error {
775777
}
776778

777779
type testFuncs struct {
778-
Tests []testFunc
779-
Benchmarks []testFunc
780-
Examples []testFunc
781-
TestMain *testFunc
782-
Package *build.Package
783-
NeedTest bool
784-
NeedXtest bool
780+
Tests []testFunc
781+
Benchmarks []testFunc
782+
Examples []testFunc
783+
TestMain *testFunc
784+
Package *build.Package
785+
ImportTest bool
786+
NeedTest bool
787+
ImportXtest bool
788+
NeedXtest bool
785789
}
786790

787791
type testFunc struct {
@@ -790,6 +794,96 @@ type testFunc struct {
790794
Output string // output, for examples
791795
}
792796

797+
var testFileSet = token.NewFileSet()
798+
799+
func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
800+
f, err := parser.ParseFile(testFileSet, filename, nil, parser.ParseComments)
801+
if err != nil {
802+
return err
803+
}
804+
for _, d := range f.Decls {
805+
n, ok := d.(*ast.FuncDecl)
806+
if !ok {
807+
continue
808+
}
809+
if n.Recv != nil {
810+
continue
811+
}
812+
name := n.Name.String()
813+
switch {
814+
case isTestMain(n):
815+
if t.TestMain != nil {
816+
return errors.New("multiple definitions of TestMain")
817+
}
818+
t.TestMain = &testFunc{pkg, name, ""}
819+
*doImport, *seen = true, true
820+
case isTest(name, "Test"):
821+
t.Tests = append(t.Tests, testFunc{pkg, name, ""})
822+
*doImport, *seen = true, true
823+
case isTest(name, "Benchmark"):
824+
t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, ""})
825+
*doImport, *seen = true, true
826+
}
827+
}
828+
// ex := doc.Examples(f)
829+
// sort.Sort(byOrder(ex))
830+
// for _, e := range ex {
831+
// *doImport = true // import test file whether executed or not
832+
// if e.Output == "" && !e.EmptyOutput {
833+
// // Don't run examples with no output.
834+
// continue
835+
// }
836+
// t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output})
837+
// *seen = true
838+
// }
839+
return nil
840+
}
841+
842+
type byOrder []*doc.Example
843+
844+
func (x byOrder) Len() int { return len(x) }
845+
func (x byOrder) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
846+
func (x byOrder) Less(i, j int) bool { return x[i].Order < x[j].Order }
847+
848+
// isTestMain tells whether fn is a TestMain(m *testing.M) function.
849+
func isTestMain(fn *ast.FuncDecl) bool {
850+
if fn.Name.String() != "TestMain" ||
851+
fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
852+
fn.Type.Params == nil ||
853+
len(fn.Type.Params.List) != 1 ||
854+
len(fn.Type.Params.List[0].Names) > 1 {
855+
return false
856+
}
857+
ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
858+
if !ok {
859+
return false
860+
}
861+
// We can't easily check that the type is *testing.M
862+
// because we don't know how testing has been imported,
863+
// but at least check that it's *M or *something.M.
864+
if name, ok := ptr.X.(*ast.Ident); ok && name.Name == "M" {
865+
return true
866+
}
867+
if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == "M" {
868+
return true
869+
}
870+
return false
871+
}
872+
873+
// isTest tells whether name looks like a test (or benchmark, according to prefix).
874+
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
875+
// We don't want TesticularCancer.
876+
func isTest(name, prefix string) bool {
877+
if !strings.HasPrefix(name, prefix) {
878+
return false
879+
}
880+
if len(name) == len(prefix) { // "Test" is ok
881+
return true
882+
}
883+
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
884+
return !unicode.IsLower(rune)
885+
}
886+
793887
var testmainTmpl = template.Must(template.New("main").Parse(`
794888
package main
795889
@@ -800,11 +894,11 @@ import (
800894
"regexp"
801895
"testing"
802896
803-
{{if .NeedTest}}
804-
_test {{.Package.ImportPath | printf "%q"}}
897+
{{if .ImportTest}}
898+
{{if .NeedTest}}_test{{else}}_{{end}} {{.Package.ImportPath | printf "%q"}}
805899
{{end}}
806-
{{if .NeedXtest}}
807-
_xtest {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
900+
{{if .ImportXtest}}
901+
{{if .NeedXtest}}_xtest{{else}}_{{end}} {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
808902
{{end}}
809903
)
810904

0 commit comments

Comments
 (0)