shell.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. package system
  2. import (
  3. "log"
  4. "os/exec"
  5. "path/filepath"
  6. "regexp"
  7. "strings"
  8. )
  9. ///////////////////////////////////////////////////////////////////////////////
  10. // Integration: ///////////////////////////////////////////////////////////////
  11. ///////////////////////////////////////////////////////////////////////////////
  12. type Shell struct {
  13. coverage bool
  14. gobin string
  15. reportsPath string
  16. defaultTimeout string
  17. }
  18. func NewShell(gobin, reportsPath string, coverage bool, defaultTimeout string) *Shell {
  19. return &Shell{
  20. coverage: coverage,
  21. gobin: gobin,
  22. reportsPath: reportsPath,
  23. defaultTimeout: defaultTimeout,
  24. }
  25. }
  26. func (self *Shell) GoTest(directory, packageName string, tags, arguments []string) (output string, err error) {
  27. reportFilename := strings.Replace(packageName, "/", "-", -1)
  28. reportPath := filepath.Join(self.reportsPath, reportFilename)
  29. reportData := reportPath + ".txt"
  30. reportHTML := reportPath + ".html"
  31. tagsArg := "-tags=" + strings.Join(tags, ",")
  32. goconvey := findGoConvey(directory, self.gobin, packageName, tagsArg).Execute()
  33. compilation := compile(directory, self.gobin, tagsArg).Execute()
  34. withCoverage := runWithCoverage(compilation, goconvey, self.coverage, reportData, directory, self.gobin, self.defaultTimeout, tagsArg, arguments).Execute()
  35. final := runWithoutCoverage(compilation, withCoverage, goconvey, directory, self.gobin, self.defaultTimeout, tagsArg, arguments).Execute()
  36. go generateReports(final, self.coverage, directory, self.gobin, reportData, reportHTML).Execute()
  37. return final.Output, final.Error
  38. }
  39. ///////////////////////////////////////////////////////////////////////////////
  40. // Functional Core:////////////////////////////////////////////////////////////
  41. ///////////////////////////////////////////////////////////////////////////////
  42. func findGoConvey(directory, gobin, packageName, tagsArg string) Command {
  43. return NewCommand(directory, gobin, "list", "-f", "'{{.TestImports}}{{.XTestImports}}'", tagsArg, packageName)
  44. }
  45. func compile(directory, gobin, tagsArg string) Command {
  46. return NewCommand(directory, gobin, "test", "-i", tagsArg)
  47. }
  48. func runWithCoverage(compile, goconvey Command, coverage bool, reportPath, directory, gobin, defaultTimeout, tagsArg string, customArguments []string) Command {
  49. if compile.Error != nil || goconvey.Error != nil {
  50. return compile
  51. }
  52. if !coverage {
  53. return compile
  54. }
  55. arguments := []string{"test", "-v", "-coverprofile=" + reportPath, tagsArg}
  56. customArgsText := strings.Join(customArguments, "\t")
  57. if !strings.Contains(customArgsText, "-covermode=") {
  58. arguments = append(arguments, "-covermode=set")
  59. }
  60. if !strings.Contains(customArgsText, "-timeout=") {
  61. arguments = append(arguments, "-timeout="+defaultTimeout)
  62. }
  63. if strings.Contains(goconvey.Output, goconveyDSLImport) {
  64. arguments = append(arguments, "-convey-json")
  65. }
  66. arguments = append(arguments, customArguments...)
  67. return NewCommand(directory, gobin, arguments...)
  68. }
  69. func runWithoutCoverage(compile, withCoverage, goconvey Command, directory, gobin, defaultTimeout, tagsArg string, customArguments []string) Command {
  70. if compile.Error != nil {
  71. return compile
  72. }
  73. if goconvey.Error != nil {
  74. log.Println(gopathProblem, goconvey.Output, goconvey.Error)
  75. return goconvey
  76. }
  77. if coverageStatementRE.MatchString(withCoverage.Output) {
  78. return withCoverage
  79. }
  80. log.Printf("Coverage output: %v", withCoverage.Output)
  81. log.Print("Run without coverage")
  82. arguments := []string{"test", "-v", tagsArg}
  83. customArgsText := strings.Join(customArguments, "\t")
  84. if !strings.Contains(customArgsText, "-timeout=") {
  85. arguments = append(arguments, "-timeout="+defaultTimeout)
  86. }
  87. if strings.Contains(goconvey.Output, goconveyDSLImport) {
  88. arguments = append(arguments, "-convey-json")
  89. }
  90. arguments = append(arguments, customArguments...)
  91. return NewCommand(directory, gobin, arguments...)
  92. }
  93. func generateReports(previous Command, coverage bool, directory, gobin, reportData, reportHTML string) Command {
  94. if previous.Error != nil {
  95. return previous
  96. }
  97. if !coverage {
  98. return previous
  99. }
  100. return NewCommand(directory, gobin, "tool", "cover", "-html="+reportData, "-o", reportHTML)
  101. }
  102. ///////////////////////////////////////////////////////////////////////////////
  103. // Imperative Shell: //////////////////////////////////////////////////////////
  104. ///////////////////////////////////////////////////////////////////////////////
  105. type Command struct {
  106. directory string
  107. executable string
  108. arguments []string
  109. Output string
  110. Error error
  111. }
  112. func NewCommand(directory, executable string, arguments ...string) Command {
  113. return Command{
  114. directory: directory,
  115. executable: executable,
  116. arguments: arguments,
  117. }
  118. }
  119. func (this Command) Execute() Command {
  120. if len(this.executable) == 0 {
  121. return this
  122. }
  123. if len(this.Output) > 0 || this.Error != nil {
  124. return this
  125. }
  126. command := exec.Command(this.executable, this.arguments...)
  127. command.Dir = this.directory
  128. var rawOutput []byte
  129. rawOutput, this.Error = command.CombinedOutput()
  130. this.Output = string(rawOutput)
  131. return this
  132. }
  133. ///////////////////////////////////////////////////////////////////////////////
  134. const goconveyDSLImport = "github.com/smartystreets/goconvey/convey " // note the trailing space: we don't want to target packages nested in the /convey package.
  135. const gopathProblem = "Please run goconvey from within $GOPATH/src (also, symlinks might be problematic). Output and Error: "
  136. var coverageStatementRE = regexp.MustCompile(`(?m)^coverage: \d+\.\d% of statements(.*)$|^panic: test timed out after `)