execution.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. package testjson
  2. import (
  3. "bufio"
  4. "bytes"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "sort"
  9. "strings"
  10. "time"
  11. "github.com/jonboulle/clockwork"
  12. "github.com/pkg/errors"
  13. "github.com/sirupsen/logrus"
  14. "golang.org/x/sync/errgroup"
  15. )
  16. // Action of TestEvent
  17. type Action string
  18. // nolint: unused
  19. const (
  20. ActionRun Action = "run"
  21. ActionPause Action = "pause"
  22. ActionCont Action = "cont"
  23. ActionPass Action = "pass"
  24. ActionBench Action = "bench"
  25. ActionFail Action = "fail"
  26. ActionOutput Action = "output"
  27. ActionSkip Action = "skip"
  28. )
  29. // TestEvent is a structure output by go tool test2json and go test -json.
  30. type TestEvent struct {
  31. // Time encoded as an RFC3339-format string
  32. Time time.Time
  33. Action Action
  34. Package string
  35. Test string
  36. // Elapsed time in seconds
  37. Elapsed float64
  38. // Output of test or benchmark
  39. Output string
  40. // raw is the raw JSON bytes of the event
  41. raw []byte
  42. }
  43. // PackageEvent returns true if the event is a package start or end event
  44. func (e TestEvent) PackageEvent() bool {
  45. return e.Test == ""
  46. }
  47. // ElapsedFormatted returns Elapsed formatted in the go test format, ex (0.00s).
  48. func (e TestEvent) ElapsedFormatted() string {
  49. return fmt.Sprintf("(%.2fs)", e.Elapsed)
  50. }
  51. // Bytes returns the serialized JSON bytes that were parsed to create the event.
  52. func (e TestEvent) Bytes() []byte {
  53. return e.raw
  54. }
  55. // Package is the set of TestEvents for a single go package
  56. type Package struct {
  57. // TODO: this could be Total()
  58. Total int
  59. Failed []TestCase
  60. Skipped []TestCase
  61. Passed []TestCase
  62. output map[string][]string
  63. // coverage stores the code coverage output for the package without the
  64. // trailing newline (ex: coverage: 91.1% of statements).
  65. coverage string
  66. // action identifies if the package passed or failed. A package may fail
  67. // with no test failures if an init() or TestMain exits non-zero.
  68. // skip indicates there were no tests.
  69. action Action
  70. }
  71. // Result returns if the package passed, failed, or was skipped because there
  72. // were no tests.
  73. func (p Package) Result() Action {
  74. return p.action
  75. }
  76. // Elapsed returns the sum of the elapsed time for all tests in the package.
  77. func (p Package) Elapsed() time.Duration {
  78. elapsed := time.Duration(0)
  79. for _, testcase := range p.TestCases() {
  80. elapsed += testcase.Elapsed
  81. }
  82. return elapsed
  83. }
  84. // TestCases returns all the test cases.
  85. func (p Package) TestCases() []TestCase {
  86. return append(append(p.Passed, p.Failed...), p.Skipped...)
  87. }
  88. // Output returns the full test output for a test.
  89. func (p Package) Output(test string) string {
  90. return strings.Join(p.output[test], "")
  91. }
  92. // TestMainFailed returns true if the package failed, but there were no tests.
  93. // This may occur if the package init() or TestMain exited non-zero.
  94. func (p Package) TestMainFailed() bool {
  95. return p.action == ActionFail && len(p.Failed) == 0
  96. }
  97. // TestCase stores the name and elapsed time for a test case.
  98. type TestCase struct {
  99. Package string
  100. Test string
  101. Elapsed time.Duration
  102. }
  103. func newPackage() *Package {
  104. return &Package{output: make(map[string][]string)}
  105. }
  106. // Execution of one or more test packages
  107. type Execution struct {
  108. started time.Time
  109. packages map[string]*Package
  110. errors []string
  111. }
  112. func (e *Execution) add(event TestEvent) {
  113. pkg, ok := e.packages[event.Package]
  114. if !ok {
  115. pkg = newPackage()
  116. e.packages[event.Package] = pkg
  117. }
  118. if event.PackageEvent() {
  119. e.addPackageEvent(pkg, event)
  120. return
  121. }
  122. e.addTestEvent(pkg, event)
  123. }
  124. func (e *Execution) addPackageEvent(pkg *Package, event TestEvent) {
  125. switch event.Action {
  126. case ActionPass, ActionFail:
  127. pkg.action = event.Action
  128. case ActionOutput:
  129. if isCoverageOutput(event.Output) {
  130. pkg.coverage = strings.TrimRight(event.Output, "\n")
  131. }
  132. pkg.output[""] = append(pkg.output[""], event.Output)
  133. }
  134. }
  135. func (e *Execution) addTestEvent(pkg *Package, event TestEvent) {
  136. switch event.Action {
  137. case ActionRun:
  138. pkg.Total++
  139. case ActionFail:
  140. pkg.Failed = append(pkg.Failed, TestCase{
  141. Package: event.Package,
  142. Test: event.Test,
  143. Elapsed: elapsedDuration(event.Elapsed),
  144. })
  145. case ActionSkip:
  146. pkg.Skipped = append(pkg.Skipped, TestCase{
  147. Package: event.Package,
  148. Test: event.Test,
  149. Elapsed: elapsedDuration(event.Elapsed),
  150. })
  151. case ActionOutput, ActionBench:
  152. // TODO: limit size of buffered test output
  153. pkg.output[event.Test] = append(pkg.output[event.Test], event.Output)
  154. case ActionPass:
  155. pkg.Passed = append(pkg.Passed, TestCase{
  156. Package: event.Package,
  157. Test: event.Test,
  158. Elapsed: elapsedDuration(event.Elapsed),
  159. })
  160. // Remove test output once a test passes, it wont be used
  161. delete(pkg.output, event.Test)
  162. }
  163. }
  164. func elapsedDuration(elapsed float64) time.Duration {
  165. return time.Duration(elapsed*1000) * time.Millisecond
  166. }
  167. func isCoverageOutput(output string) bool {
  168. return all(
  169. strings.HasPrefix(output, "coverage:"),
  170. strings.HasSuffix(output, "% of statements\n"))
  171. }
  172. // Output returns the full test output for a test.
  173. func (e *Execution) Output(pkg, test string) string {
  174. return strings.Join(e.packages[pkg].output[test], "")
  175. }
  176. // OutputLines returns the full test output for a test as an array of lines.
  177. func (e *Execution) OutputLines(pkg, test string) []string {
  178. return e.packages[pkg].output[test]
  179. }
  180. // Package returns the Package by name.
  181. func (e *Execution) Package(name string) *Package {
  182. return e.packages[name]
  183. }
  184. // Packages returns a sorted list of all package names.
  185. func (e *Execution) Packages() []string {
  186. return sortedKeys(e.packages)
  187. }
  188. var clock = clockwork.NewRealClock()
  189. // Elapsed returns the time elapsed since the execution started.
  190. func (e *Execution) Elapsed() time.Duration {
  191. return clock.Now().Sub(e.started)
  192. }
  193. // Failed returns a list of all the failed test cases.
  194. func (e *Execution) Failed() []TestCase {
  195. var failed []TestCase
  196. for _, name := range sortedKeys(e.packages) {
  197. pkg := e.packages[name]
  198. // Add package-level failure output if there were no failed tests.
  199. if pkg.TestMainFailed() {
  200. failed = append(failed, TestCase{Package: name})
  201. } else {
  202. failed = append(failed, pkg.Failed...)
  203. }
  204. }
  205. return failed
  206. }
  207. func sortedKeys(pkgs map[string]*Package) []string {
  208. keys := make([]string, 0, len(pkgs))
  209. for key := range pkgs {
  210. keys = append(keys, key)
  211. }
  212. sort.Strings(keys)
  213. return keys
  214. }
  215. // Skipped returns a list of all the skipped test cases.
  216. func (e *Execution) Skipped() []TestCase {
  217. skipped := make([]TestCase, 0, len(e.packages))
  218. for _, pkg := range sortedKeys(e.packages) {
  219. skipped = append(skipped, e.packages[pkg].Skipped...)
  220. }
  221. return skipped
  222. }
  223. // Total returns a count of all test cases.
  224. func (e *Execution) Total() int {
  225. total := 0
  226. for _, pkg := range e.packages {
  227. total += pkg.Total
  228. }
  229. return total
  230. }
  231. func (e *Execution) addError(err string) {
  232. // Build errors start with a header
  233. if strings.HasPrefix(err, "# ") {
  234. return
  235. }
  236. // TODO: may need locking, or use a channel
  237. e.errors = append(e.errors, err)
  238. }
  239. // Errors returns a list of all the errors.
  240. func (e *Execution) Errors() []string {
  241. return e.errors
  242. }
  243. // NewExecution returns a new Execution and records the current time as the
  244. // time the test execution started.
  245. func NewExecution() *Execution {
  246. return &Execution{
  247. started: time.Now(),
  248. packages: make(map[string]*Package),
  249. }
  250. }
  251. // ScanConfig used by ScanTestOutput
  252. type ScanConfig struct {
  253. Stdout io.Reader
  254. Stderr io.Reader
  255. Handler EventHandler
  256. }
  257. // EventHandler is called by ScanTestOutput for each event and write to stderr.
  258. type EventHandler interface {
  259. Event(event TestEvent, execution *Execution) error
  260. Err(text string) error
  261. }
  262. // ScanTestOutput reads lines from stdout and stderr, creates an Execution,
  263. // calls the Handler for each event, and returns the Execution.
  264. func ScanTestOutput(config ScanConfig) (*Execution, error) {
  265. execution := NewExecution()
  266. var group errgroup.Group
  267. group.Go(func() error {
  268. return readStdout(config, execution)
  269. })
  270. group.Go(func() error {
  271. return readStderr(config, execution)
  272. })
  273. return execution, group.Wait()
  274. }
  275. func readStdout(config ScanConfig, execution *Execution) error {
  276. scanner := bufio.NewScanner(config.Stdout)
  277. for scanner.Scan() {
  278. raw := scanner.Bytes()
  279. event, err := parseEvent(raw)
  280. switch {
  281. case err == errBadEvent:
  282. // nolint: errcheck
  283. config.Handler.Err(errBadEvent.Error() + ": " + scanner.Text())
  284. continue
  285. case err != nil:
  286. return errors.Wrapf(err, "failed to parse test output: %s", string(raw))
  287. }
  288. execution.add(event)
  289. if err := config.Handler.Event(event, execution); err != nil {
  290. return err
  291. }
  292. }
  293. return errors.Wrap(scanner.Err(), "failed to scan test output")
  294. }
  295. func readStderr(config ScanConfig, execution *Execution) error {
  296. scanner := bufio.NewScanner(config.Stderr)
  297. for scanner.Scan() {
  298. line := scanner.Text()
  299. config.Handler.Err(line) // nolint: errcheck
  300. if isGoModuleOutput(line) {
  301. continue
  302. }
  303. execution.addError(line)
  304. }
  305. return errors.Wrap(scanner.Err(), "failed to scan test stderr")
  306. }
  307. func isGoModuleOutput(scannerText string) bool {
  308. prefixes := []string{
  309. "go: copying",
  310. "go: creating",
  311. "go: downloading",
  312. "go: extracting",
  313. "go: finding",
  314. }
  315. for _, prefix := range prefixes {
  316. if strings.HasPrefix(scannerText, prefix) {
  317. return true
  318. }
  319. }
  320. return false
  321. }
  322. func parseEvent(raw []byte) (TestEvent, error) {
  323. // TODO: this seems to be a bug in the `go test -json` output
  324. if bytes.HasPrefix(raw, []byte("FAIL")) {
  325. logrus.Warn(string(raw))
  326. return TestEvent{}, errBadEvent
  327. }
  328. event := TestEvent{}
  329. err := json.Unmarshal(raw, &event)
  330. event.raw = raw
  331. return event, err
  332. }
  333. var errBadEvent = errors.New("bad output from test2json")