packageParser.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package parser
  2. import (
  3. "fmt"
  4. "regexp"
  5. "sort"
  6. "strconv"
  7. "strings"
  8. "github.com/smartystreets/goconvey/web/server/contract"
  9. )
  10. var (
  11. testNamePattern = regexp.MustCompile("^=== RUN:? +(.+)$")
  12. )
  13. func ParsePackageResults(result *contract.PackageResult, rawOutput string) {
  14. newOutputParser(result, rawOutput).parse()
  15. }
  16. type outputParser struct {
  17. raw string
  18. lines []string
  19. result *contract.PackageResult
  20. tests []*contract.TestResult
  21. // place holders for loops
  22. line string
  23. test *contract.TestResult
  24. testMap map[string]*contract.TestResult
  25. }
  26. func newOutputParser(result *contract.PackageResult, rawOutput string) *outputParser {
  27. self := new(outputParser)
  28. self.raw = strings.TrimSpace(rawOutput)
  29. self.lines = strings.Split(self.raw, "\n")
  30. self.result = result
  31. self.tests = []*contract.TestResult{}
  32. self.testMap = make(map[string]*contract.TestResult)
  33. return self
  34. }
  35. func (self *outputParser) parse() {
  36. self.separateTestFunctionsAndMetadata()
  37. self.parseEachTestFunction()
  38. }
  39. func (self *outputParser) separateTestFunctionsAndMetadata() {
  40. for _, self.line = range self.lines {
  41. if self.processNonTestOutput() {
  42. break
  43. }
  44. self.processTestOutput()
  45. }
  46. }
  47. func (self *outputParser) processNonTestOutput() bool {
  48. if noGoFiles(self.line) {
  49. self.recordFinalOutcome(contract.NoGoFiles)
  50. } else if buildFailed(self.line) {
  51. self.recordFinalOutcome(contract.BuildFailure)
  52. } else if noTestFiles(self.line) {
  53. self.recordFinalOutcome(contract.NoTestFiles)
  54. } else if noTestFunctions(self.line) {
  55. self.recordFinalOutcome(contract.NoTestFunctions)
  56. } else {
  57. return false
  58. }
  59. return true
  60. }
  61. func (self *outputParser) recordFinalOutcome(outcome string) {
  62. self.result.Outcome = outcome
  63. self.result.BuildOutput = strings.Join(self.lines, "\n")
  64. }
  65. func (self *outputParser) processTestOutput() {
  66. self.line = strings.TrimSpace(self.line)
  67. if isNewTest(self.line) {
  68. self.registerTestFunction()
  69. } else if isTestResult(self.line) {
  70. self.recordTestMetadata()
  71. } else if isPackageReport(self.line) {
  72. self.recordPackageMetadata()
  73. } else {
  74. self.saveLineForParsingLater()
  75. }
  76. }
  77. func (self *outputParser) registerTestFunction() {
  78. testNameReg := testNamePattern.FindStringSubmatch(self.line)
  79. if len(testNameReg) < 2 { // Test-related lines that aren't about a new test
  80. return
  81. }
  82. self.test = contract.NewTestResult(testNameReg[1])
  83. self.tests = append(self.tests, self.test)
  84. self.testMap[self.test.TestName] = self.test
  85. }
  86. func (self *outputParser) recordTestMetadata() {
  87. testName := strings.Split(self.line, " ")[2]
  88. if test, ok := self.testMap[testName]; ok {
  89. self.test = test
  90. self.test.Passed = !strings.HasPrefix(self.line, "--- FAIL: ")
  91. self.test.Skipped = strings.HasPrefix(self.line, "--- SKIP: ")
  92. self.test.Elapsed = parseTestFunctionDuration(self.line)
  93. }
  94. }
  95. func (self *outputParser) recordPackageMetadata() {
  96. if packageFailed(self.line) {
  97. self.recordTestingOutcome(contract.Failed)
  98. } else if packagePassed(self.line) {
  99. self.recordTestingOutcome(contract.Passed)
  100. } else if isCoverageSummary(self.line) {
  101. self.recordCoverageSummary(self.line)
  102. }
  103. }
  104. func (self *outputParser) recordTestingOutcome(outcome string) {
  105. self.result.Outcome = outcome
  106. fields := strings.Split(self.line, "\t")
  107. self.result.PackageName = strings.TrimSpace(fields[1])
  108. self.result.Elapsed = parseDurationInSeconds(fields[2], 3)
  109. }
  110. func (self *outputParser) recordCoverageSummary(summary string) {
  111. start := len("coverage: ")
  112. end := strings.Index(summary, "%")
  113. value := summary[start:end]
  114. parsed, err := strconv.ParseFloat(value, 64)
  115. if err != nil {
  116. self.result.Coverage = -1
  117. } else {
  118. self.result.Coverage = parsed
  119. }
  120. }
  121. func (self *outputParser) saveLineForParsingLater() {
  122. self.line = strings.TrimLeft(self.line, "\t")
  123. if self.test == nil {
  124. fmt.Println("Potential error parsing output of", self.result.PackageName, "; couldn't handle this stray line:", self.line)
  125. return
  126. }
  127. self.test.RawLines = append(self.test.RawLines, self.line)
  128. }
  129. // TestResults is a collection of TestResults that implements sort.Interface.
  130. type TestResults []contract.TestResult
  131. func (r TestResults) Len() int {
  132. return len(r)
  133. }
  134. // Less compares TestResults on TestName
  135. func (r TestResults) Less(i, j int) bool {
  136. return r[i].TestName < r[j].TestName
  137. }
  138. func (r TestResults) Swap(i, j int) {
  139. r[i], r[j] = r[j], r[i]
  140. }
  141. func (self *outputParser) parseEachTestFunction() {
  142. for _, self.test = range self.tests {
  143. self.test = parseTestOutput(self.test)
  144. if self.test.Error != "" {
  145. self.result.Outcome = contract.Panicked
  146. }
  147. self.test.RawLines = []string{}
  148. self.result.TestResults = append(self.result.TestResults, *self.test)
  149. }
  150. sort.Sort(TestResults(self.result.TestResults))
  151. }