Skip to content

Instantly share code, notes, and snippets.

@balta2ar
Created October 14, 2020 07:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save balta2ar/fee87ce480297bef3c8e5104f80f269b to your computer and use it in GitHub Desktop.
Save balta2ar/fee87ce480297bef3c8e5104f80f269b to your computer and use it in GitHub Desktop.
golisttests
package main
import (
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
var rootPath = flag.String("root", ".", "root path to start scan for test files (*_test.go)")
var limitExecution = flag.Bool("limit", false, "enable execution limiter")
var maxFiles = flag.Int("maxFiles", 10000, "max number of files to scan")
var maxExecution = flag.Duration("maxExecution", time.Second, "max time limit for scan")
func IsTestFilename(name string) bool {
return strings.HasSuffix(name, "_test.go")
}
func IsSingleArgumentTestingT(fn *ast.FuncDecl) bool {
if len(fn.Type.Params.List) != 1 {
return false
}
for _, param := range fn.Type.Params.List {
if star, ok := param.Type.(*ast.StarExpr); ok {
return "&{testing T}" == fmt.Sprintf("%s", star.X)
}
break
}
return false
}
func GetReceiverTypeNoStar(fn *ast.FuncDecl) string {
switch t := fn.Recv.List[0].Type.(type) {
case *ast.StarExpr:
return fmt.Sprintf("%s", t.X)
case *ast.Ident:
return t.Name
}
panic("unknown receiver type")
}
func HasReceiver(fn *ast.FuncDecl) bool {
return fn.Recv != nil
}
func HasReceiverAndNoArguments(fn *ast.FuncDecl) bool {
if len(fn.Type.Params.List) != 0 {
return false
}
return HasReceiver(fn)
}
func IsTestName(name string) bool {
return strings.HasPrefix(name, "Test")
}
func IsSimpleTest(fn *ast.FuncDecl) bool {
return IsTestName(fn.Name.Name) && !HasReceiver(fn) && IsSingleArgumentTestingT(fn)
}
func IsPossibleSuiteTest(fn *ast.FuncDecl) bool {
return IsTestName(fn.Name.Name) && HasReceiverAndNoArguments(fn)
}
func GetFirstIdentName(expr ast.Expr) string {
var result string
ast.Inspect(expr, func(node ast.Node) bool {
if ident, ok := node.(*ast.Ident); ok {
if result == "" {
result = ident.Name
}
}
return true
})
if result == "" {
panic("cannot find first identifier")
}
return result
}
func FindSuiteRunTypes(fn *ast.FuncDecl) []string {
seen := make(map[string]bool)
result := make([]string, 0)
ast.Inspect(fn, func(node ast.Node) bool {
if call, ok := node.(*ast.CallExpr); ok {
if len(call.Args) != 2 {
return true
}
callName := fmt.Sprintf("%s", call.Fun)
if callName != "&{suite Run}" {
return true
}
name := GetFirstIdentName(call.Args[1])
if _, ok := seen[name]; !ok {
result = append(result, name)
seen[name] = true
}
}
return true
})
return result
}
func IsSuiteRunner(fn *ast.FuncDecl) bool {
return IsSimpleTest(fn) && len(FindSuiteRunTypes(fn)) > 0
}
type Tracker struct {
result []string
seenTests map[string]bool
suiteTypesAndTestsWhoRanThem map[string]map[string]bool
}
func NewTracker() *Tracker {
return &Tracker{
result: make([]string, 0),
seenTests: make(map[string]bool, 0),
suiteTypesAndTestsWhoRanThem: make(map[string]map[string]bool, 0),
}
}
func (t *Tracker) AddTest(name string) {
if _, ok := t.seenTests[name]; !ok {
t.result = append(t.result, name)
t.seenTests[name] = true
}
}
func (t *Tracker) SeenTests() []string {
sort.Strings(t.result)
return t.result
}
func (t *Tracker) SuiteRanByTest(suiteTypeName string, testName string) {
if _, ok := t.suiteTypesAndTestsWhoRanThem[suiteTypeName]; !ok {
t.suiteTypesAndTestsWhoRanThem[suiteTypeName] = make(map[string]bool, 0)
}
t.suiteTypesAndTestsWhoRanThem[suiteTypeName][testName] = true
}
func (t *Tracker) WhoRanSuiteType(suiteTypeName string) []string {
if _, ok := t.suiteTypesAndTestsWhoRanThem[suiteTypeName]; !ok {
return []string{}
}
var result []string
for testName, _ := range t.suiteTypesAndTestsWhoRanThem[suiteTypeName] {
result = append(result, testName)
}
return result
}
func ParseTestNames(filename string) []string {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
if err != nil {
return []string{}
}
tracker := NewTracker()
scan := func() {
for _, f := range node.Decls {
if fn, ok := f.(*ast.FuncDecl); ok {
testName := fn.Name.Name
if IsSimpleTest(fn) {
tracker.AddTest(testName)
for _, runnableSuiteTypeName := range FindSuiteRunTypes(fn) {
tracker.SuiteRanByTest(runnableSuiteTypeName, testName)
}
}
if IsPossibleSuiteTest(fn) {
receiverTypeName := GetReceiverTypeNoStar(fn)
for _, testNameWhoRan := range tracker.WhoRanSuiteType(receiverTypeName) {
tracker.AddTest(testNameWhoRan + "/" + testName)
}
}
}
}
}
scan()
scan()
return tracker.SeenTests()
}
type Deadliner interface {
Tick() error
}
type Limited struct {
expiry time.Time
numFiles int
}
func (l *Limited) Tick() error {
l.numFiles--
if l.numFiles <= 0 {
return fmt.Errorf("number of files exceeded limit (%d)", *maxFiles)
}
if time.Now().After(l.expiry) {
return fmt.Errorf("execution time exceeded limit (%s)", *maxExecution)
}
return nil
}
type Unlimited struct{}
func (u *Unlimited) Tick() error { return nil }
func ListTestNames(root string, limit Deadliner) ([]string, error) {
var result []string
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if err := limit.Tick(); err != nil {
return err
}
if !IsTestFilename(path) {
return nil
}
result = append(result, ParseTestNames(path)...)
return nil
})
return result, err
}
func main() {
flag.Parse()
var names []string
var err error
if *limitExecution {
names, err = ListTestNames(*rootPath, &Limited{time.Now().Add(*maxExecution), *maxFiles})
} else {
names, err = ListTestNames(*rootPath, &Unlimited{})
}
for _, name := range names {
fmt.Println(name)
}
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
package main
import (
"github.com/stretchr/testify/require"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"os"
"path"
"testing"
)
func Spit(data string) string {
tempname := path.Join(os.TempDir(), "golisttests.tmp")
err := ioutil.WriteFile(tempname, []byte(data), 0644)
if err != nil {
panic(err)
}
return tempname
}
func MustParse(code string) *ast.File {
tempname := path.Join(os.TempDir(), "golisttests.tmp")
err := ioutil.WriteFile(tempname, []byte(code), 0644)
if err != nil {
panic(err)
}
fset := token.NewFileSet()
expr, err := parser.ParseFile(fset, tempname, nil, parser.ParseComments)
if err != nil {
panic(err)
}
return expr
}
func FirstFunction(code string) *ast.FuncDecl {
for _, decl := range MustParse(code).Decls {
fn, ok := decl.(*ast.FuncDecl)
if ok {
return fn
}
}
panic("functions not found")
}
func TestIsSingleArgumentTestingT(t *testing.T) {
require.True(t, IsSingleArgumentTestingT(FirstFunction(`
package test
func TestSimple(t *testing.T) {}`)))
require.True(t, IsSingleArgumentTestingT(FirstFunction(`
package test
func NoTestSimple(t *testing.T) {}`)))
require.True(t, IsSingleArgumentTestingT(FirstFunction(`
package test
func NoTestSimple(randomName *testing.T) {}`)))
require.False(t, IsSingleArgumentTestingT(FirstFunction(`
package test
func TestSimple(t *testing.T, more bool) {}`)))
require.False(t, IsSingleArgumentTestingT(FirstFunction(`
package test
func TestSimple(t *testing.B, more bool) {}`)))
}
func TestIsReceiverNoArguments(t *testing.T) {
require.False(t, HasReceiverAndNoArguments(FirstFunction(`
package test
func TestSimple1(t *testing.T) {}`)))
require.False(t, HasReceiverAndNoArguments(FirstFunction(`
package test
func (p *int) TestSimple2(t *testing.T) {}`)))
require.False(t, HasReceiverAndNoArguments(FirstFunction(`
package test
func (p int) TestSimple3(t *testing.T) {}`)))
require.True(t, HasReceiverAndNoArguments(FirstFunction(`
package test
func (p int) TestSimple4() {}`)))
require.True(t, HasReceiverAndNoArguments(FirstFunction(`
package test
func (p *int) TestSimple5() {}`)))
require.True(t, HasReceiverAndNoArguments(FirstFunction(`
package test
func (p * int) TestSimple6() {}`)))
}
func TestGetReceiverTypeNoStar(t *testing.T) {
require.Equal(t, "int", GetReceiverTypeNoStar(FirstFunction(`
package test
func (p int) TestSimple4() {}`)))
require.Equal(t, "int", GetReceiverTypeNoStar(FirstFunction(`
package test
func (p *int) TestSimple5() {}`)))
require.Equal(t, "int", GetReceiverTypeNoStar(FirstFunction(`
package test
func (p * int) TestSimple6() {}`)))
}
func TestFindSuiteRunTypes(t *testing.T) {
require.Equal(t,
[]string{"aggregatorStarSuite"},
FindSuiteRunTypes(FirstFunction(`
package test
func TestAggregatorStarSuite(t *testing.T) {
rand.Seed(0)
suite.Run(t, &aggregatorStarSuite{})
//_ = &aggregatorStarSuite{}
}`)))
require.Equal(t,
[]string{},
FindSuiteRunTypes(FirstFunction(`
package test
func TestAggregatorStarSuite(t *testing.T) {
rand.Seed(0)
//suite.Run(t, &aggregatorStarSuite{})
//_ = &aggregatorStarSuite{}
}`)))
require.Equal(t,
[]string{"aggregatorStarSuite", "aggregatorSuite"},
FindSuiteRunTypes(FirstFunction(`
package test
func TestAggregatorStarSuite(t *testing.T) {
rand.Seed(0)
suite.Run(t, &aggregatorStarSuite{})
//_ = &aggregatorStarSuite{}
suite.Run(t, &aggregatorSuite{})
}`)))
require.Equal(t,
[]string{"aggregatorStarSuite", "aggregatorSuite"},
FindSuiteRunTypes(FirstFunction(`
package test
func TestAggregatorStarSuite(t *testing.T) {
rand.Seed(0)
suite.Run(t, &aggregatorStarSuite{})
suite.Run(t, &aggregatorSuite{})
//_ = &aggregatorStarSuite{}
suite.Run(t, &aggregatorSuite{})
suite.Run(t, &aggregatorStarSuite{})
}`)))
}
func TestIsSuiteRunner(t *testing.T) {
require.False(t, IsSuiteRunner(FirstFunction(`
package test
func (p int) TestSimple4() {}`)))
require.False(t, IsSuiteRunner(FirstFunction(`
package test
func NotSuiteRunner(t *testing.T) {
suite.Run(t, &aggregatorStarSuite{})
}`)))
require.False(t, IsSuiteRunner(FirstFunction(`
package test
func TestSuiteRunner(t *testing.T) {
//suite.Run(t, &aggregatorStarSuite{})
}`)))
require.True(t, IsSuiteRunner(FirstFunction(`
package test
func TestSuiteRunner(t *testing.T) {
suite.Run(t, &aggregatorStarSuite{})
}`)))
require.True(t, IsSuiteRunner(FirstFunction(`
package test
func TestSuiteRunner(t *testing.T) {
suite.Run(t, &aggregatorStarSuite{
})
}`)))
require.True(t, IsSuiteRunner(FirstFunction(`
package test
func TestSuiteRunner(t *testing.T) {
suite.Run(t, &aggregatorStarSuite{
name: "test",
})
}`)))
}
func TestParseTestNamesSimple(t *testing.T) {
require.Equal(t,
[]string{},
ParseTestNames(Spit(`
package test
func (p int) TestSimple1() {}
`)))
require.Equal(t,
[]string{},
ParseTestNames(Spit(`
package test
func TestSimple2() {}
`)))
require.Equal(t,
[]string{},
ParseTestNames(Spit(`
package test
func TestSimple3(t *something.T) {}
`)))
require.Equal(t,
[]string{"TestSimple4"},
ParseTestNames(Spit(`
package test
func TestSimple4(t *testing.T) {}
`)))
require.Equal(t,
[]string{"TestSimple5"},
ParseTestNames(Spit(`
package test
func TestSimple5(t * testing.T) {}
`)))
}
func TestParseTestNamesSuite(t *testing.T) {
require.Equal(t,
[]string{
"TestSampleSuite",
"TestSimple1",
},
ParseTestNames(Spit(`
package test
func TestSimple1(t *testing.T) {}
func TestSampleSuite(t *testing.T) {
suite.Run(t, &someType{})
}
`)))
require.Equal(t,
[]string{
"TestSampleSuite",
"TestSampleSuite/TestValidAfter1",
"TestSampleSuite/TestValidAfter2",
"TestSampleSuite/TestValidBefore1",
"TestSampleSuite/TestValidBefore2",
"TestSimple1",
},
ParseTestNames(Spit(`
package test
func TestSimple1(t *testing.T) {}
func (s someType) TestInvalidArgs1(t *testing.T) {}
func (s *someType) TestInvalidArgs2(t *testing.T) {}
func (s someType) IncorrectName1() {}
func (s *someType) IncorrectName2() {}
func (s someType) TestValidBefore1() {}
func (s *someType) TestValidBefore2() {}
func TestSampleSuite(t *testing.T) {
suite.Run(t, &someType{})
}
func (s someType) TestValidAfter1() {}
func (s *someType) TestValidAfter2() {}
func (s unknownType) TestNeverRun1() {}
func (s *unknownType) TestNeverRun2() {}
`)))
require.Equal(t,
[]string{
"TestSameTypeDifferentSuite",
"TestSameTypeDifferentSuite/TestValidAfter1",
"TestSameTypeDifferentSuite/TestValidAfter2",
"TestSameTypeDifferentSuite/TestValidBefore1",
"TestSameTypeDifferentSuite/TestValidBefore2",
"TestSampleSuite",
"TestSampleSuite/TestValidAfter1",
"TestSampleSuite/TestValidAfter2",
"TestSampleSuite/TestValidBefore1",
"TestSampleSuite/TestValidBefore2",
"TestSimple1",
},
ParseTestNames(Spit(`
package test
func TestSimple1(t *testing.T) {}
func (s someType) TestInvalidArgs1(t *testing.T) {}
func (s *someType) TestInvalidArgs2(t *testing.T) {}
func (s someType) IncorrectName1() {}
func (s *someType) IncorrectName2() {}
func (s someType) TestValidBefore1() {}
func (s *someType) TestValidBefore2() {}
func TestSampleSuite(t *testing.T) {
suite.Run(t, &someType{})
}
func (s someType) TestValidAfter1() {}
func (s *someType) TestValidAfter2() {}
func (s unknownType) TestNeverRun1() {}
func (s *unknownType) TestNeverRun2() {}
func TestSameTypeDifferentSuite(t *testing.T) {
suite.Run(t, &someType{})
}
`)))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment