main.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "go/importer"
  6. "go/token"
  7. "go/types"
  8. "os"
  9. "path/filepath"
  10. "regexp"
  11. "runtime"
  12. "sort"
  13. "strings"
  14. "unicode"
  15. "unicode/utf8"
  16. )
  17. const (
  18. usageTemplate = "Usage: %s [-benchmarks] [-examples] [-tests] import-path\n"
  19. )
  20. func findObjectByName(pkg *types.Package, re *regexp.Regexp, name string) types.Object {
  21. if pkg != nil && re != nil && len(name) > 0 {
  22. if obj := pkg.Scope().Lookup(name); obj != nil {
  23. if re.MatchString(obj.Type().String()) {
  24. return obj
  25. }
  26. }
  27. }
  28. return nil
  29. }
  30. func isTestName(name, prefix string) bool {
  31. ok := false
  32. if strings.HasPrefix(name, prefix) {
  33. if len(name) == len(prefix) {
  34. ok = true
  35. } else {
  36. rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
  37. ok = !unicode.IsLower(rune)
  38. }
  39. }
  40. return ok
  41. }
  42. func main() {
  43. testsPtr := flag.Bool("tests", false, "report tests")
  44. benchmarksPtr := flag.Bool("benchmarks", false, "report benchmarks")
  45. examplesPtr := flag.Bool("examples", false, "report examples")
  46. flag.Usage = func() {
  47. _, _ = fmt.Fprintf(flag.CommandLine.Output(), usageTemplate, filepath.Base(os.Args[0]))
  48. flag.PrintDefaults()
  49. }
  50. flag.Parse()
  51. // Check if the number of positional parameters matches
  52. args := flag.Args()
  53. argsCount := len(args)
  54. if argsCount != 1 {
  55. exitCode := 0
  56. if argsCount > 1 {
  57. fmt.Println("Error: invalid number of parameters...")
  58. exitCode = 1
  59. }
  60. flag.Usage()
  61. os.Exit(exitCode)
  62. }
  63. importPath := args[0]
  64. var fset token.FileSet
  65. imp := importer.ForCompiler(&fset, runtime.Compiler, nil)
  66. pkg, err := imp.Import(importPath)
  67. if err != nil {
  68. fmt.Printf("Error: %v\n", err)
  69. os.Exit(1)
  70. }
  71. if !*testsPtr && !*benchmarksPtr && !*examplesPtr {
  72. // Nothing to do, just exit normally
  73. os.Exit(0)
  74. }
  75. // // First approach: just dump the package scope as a string
  76. // // package "junk/snermolaev/libmath" scope 0xc0000df540 {
  77. // // . func junk/snermolaev/libmath.Abs(a int) int
  78. // // . func junk/snermolaev/libmath.AbsReport(s string)
  79. // // . func junk/snermolaev/libmath.Sum(a int, b int) int
  80. // // . func junk/snermolaev/libmath.TestAbs(t *testing.T)
  81. // // . func junk/snermolaev/libmath.TestSum(t *testing.T)
  82. // // . func junk/snermolaev/libmath.init()
  83. // // }
  84. // // and then collect all functions that match test function signature
  85. // pkgPath := pkg.Path()
  86. // scopeContent := strings.Split(pkg.Scope().String(), "\n")
  87. // re := regexp.MustCompile("^\\.\\s*func\\s*" + pkgPath + "\\.(Test\\w*)\\(\\s*\\w*\\s*\\*\\s*testing\\.T\\s*\\)$")
  88. // for _, name := range scopeContent {
  89. // match := re.FindAllStringSubmatch(name, -1)
  90. // if len(match) > 0 {
  91. // fmt.Println(match[0][1])
  92. // }
  93. // }
  94. // Second approach: look through all names defined in the pkg scope
  95. // and collect those functions that match test function signature
  96. // Unfortunately I failed to employ reflection mechinary for signature
  97. // comparison for unknown reasons (this needs additional investigation
  98. // I am going to use regexp as workaround for a while)
  99. // testFunc := func (*testing.T) {}
  100. // for ...
  101. // ...
  102. // if reflect.DeepEqual(obj.Type(), reflect.TypeOf(testFunc)) {
  103. // // this condition doesn't work
  104. // }
  105. reBenchmark := regexp.MustCompile(`^func\(\w*\s*\*testing\.B\)$`)
  106. reExample := regexp.MustCompile(`^func\(\s*\)$`)
  107. reTest := regexp.MustCompile(`^func\(\w*\s*\*testing\.T\)$`)
  108. reTestMain := regexp.MustCompile(`^func\(\w*\s*\*testing\.M\)$`)
  109. var re *regexp.Regexp
  110. names := pkg.Scope().Names()
  111. var testFns []types.Object
  112. for _, name := range names {
  113. if name == "TestMain" && findObjectByName(pkg, reTestMain, name) != nil {
  114. fmt.Println("#TestMain")
  115. continue
  116. }
  117. switch {
  118. case *benchmarksPtr && isTestName(name, "Benchmark"):
  119. re = reBenchmark
  120. case *examplesPtr && isTestName(name, "Example"):
  121. re = reExample
  122. case *testsPtr && isTestName(name, "Test"):
  123. re = reTest
  124. default:
  125. continue
  126. }
  127. if obj := findObjectByName(pkg, re, name); obj != nil {
  128. testFns = append(testFns, obj)
  129. }
  130. }
  131. sort.Slice(testFns, func(i, j int) bool {
  132. iPos := testFns[i].Pos()
  133. jPos := testFns[j].Pos()
  134. if !iPos.IsValid() || !jPos.IsValid() {
  135. return iPos < jPos
  136. }
  137. iPosition := fset.PositionFor(iPos, true)
  138. jPosition := fset.PositionFor(jPos, true)
  139. return iPosition.Filename < jPosition.Filename ||
  140. (iPosition.Filename == jPosition.Filename && iPosition.Line < jPosition.Line)
  141. })
  142. for _, testFn := range testFns {
  143. fmt.Println(testFn.Name())
  144. }
  145. }