call.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "strings"
  7. "sync"
  8. "sync/atomic"
  9. "time"
  10. "github.com/bfirsh/funker-go"
  11. "github.com/docker/docker/hack/integration-cli-on-swarm/agent/types"
  12. )
  13. const (
  14. // funkerRetryTimeout is for the issue https://github.com/bfirsh/funker/issues/3
  15. // When all the funker replicas are busy in their own job, we cannot connect to funker.
  16. funkerRetryTimeout = 1 * time.Hour
  17. funkerRetryDuration = 1 * time.Second
  18. )
  19. // ticker is needed for some CI (e.g., on Travis, job is aborted when no output emitted for 10 minutes)
  20. func ticker(d time.Duration) chan struct{} {
  21. t := time.NewTicker(d)
  22. stop := make(chan struct{})
  23. go func() {
  24. for {
  25. select {
  26. case <-t.C:
  27. log.Printf("tick (just for keeping CI job active) per %s", d.String())
  28. case <-stop:
  29. t.Stop()
  30. }
  31. }
  32. }()
  33. return stop
  34. }
  35. func executeTests(funkerName string, testChunks [][]string) error {
  36. tickerStopper := ticker(9*time.Minute + 55*time.Second)
  37. defer func() {
  38. close(tickerStopper)
  39. }()
  40. begin := time.Now()
  41. log.Printf("Executing %d chunks in parallel, against %q", len(testChunks), funkerName)
  42. var wg sync.WaitGroup
  43. var passed, failed uint32
  44. for chunkID, tests := range testChunks {
  45. log.Printf("Executing chunk %d (contains %d test filters)", chunkID, len(tests))
  46. wg.Add(1)
  47. go func(chunkID int, tests []string) {
  48. defer wg.Done()
  49. chunkBegin := time.Now()
  50. result, err := executeTestChunkWithRetry(funkerName, types.Args{
  51. ChunkID: chunkID,
  52. Tests: tests,
  53. })
  54. if result.RawLog != "" {
  55. for _, s := range strings.Split(result.RawLog, "\n") {
  56. log.Printf("Log (chunk %d): %s", chunkID, s)
  57. }
  58. }
  59. if err != nil {
  60. log.Printf("Error while executing chunk %d: %v",
  61. chunkID, err)
  62. atomic.AddUint32(&failed, 1)
  63. } else {
  64. if result.Code == 0 {
  65. atomic.AddUint32(&passed, 1)
  66. } else {
  67. atomic.AddUint32(&failed, 1)
  68. }
  69. log.Printf("Finished chunk %d [%d/%d] with %d test filters in %s, code=%d.",
  70. chunkID, passed+failed, len(testChunks), len(tests),
  71. time.Since(chunkBegin), result.Code)
  72. }
  73. }(chunkID, tests)
  74. }
  75. wg.Wait()
  76. // TODO: print actual tests rather than chunks
  77. log.Printf("Executed %d chunks in %s. PASS: %d, FAIL: %d.",
  78. len(testChunks), time.Since(begin), passed, failed)
  79. if failed > 0 {
  80. return fmt.Errorf("%d chunks failed", failed)
  81. }
  82. return nil
  83. }
  84. func executeTestChunk(funkerName string, args types.Args) (types.Result, error) {
  85. ret, err := funker.Call(funkerName, args)
  86. if err != nil {
  87. return types.Result{}, err
  88. }
  89. tmp, err := json.Marshal(ret)
  90. if err != nil {
  91. return types.Result{}, err
  92. }
  93. var result types.Result
  94. err = json.Unmarshal(tmp, &result)
  95. return result, err
  96. }
  97. func executeTestChunkWithRetry(funkerName string, args types.Args) (types.Result, error) {
  98. begin := time.Now()
  99. for i := 0; time.Since(begin) < funkerRetryTimeout; i++ {
  100. result, err := executeTestChunk(funkerName, args)
  101. if err == nil {
  102. log.Printf("executeTestChunk(%q, %d) returned code %d in trial %d", funkerName, args.ChunkID, result.Code, i)
  103. return result, nil
  104. }
  105. if errorSeemsInteresting(err) {
  106. log.Printf("Error while calling executeTestChunk(%q, %d), will retry (trial %d): %v",
  107. funkerName, args.ChunkID, i, err)
  108. }
  109. // TODO: non-constant sleep
  110. time.Sleep(funkerRetryDuration)
  111. }
  112. return types.Result{}, fmt.Errorf("could not call executeTestChunk(%q, %d) in %v", funkerName, args.ChunkID, funkerRetryTimeout)
  113. }
  114. // errorSeemsInteresting returns true if err does not seem about https://github.com/bfirsh/funker/issues/3
  115. func errorSeemsInteresting(err error) bool {
  116. boringSubstrs := []string{"connection refused", "connection reset by peer", "no such host", "transport endpoint is not connected", "no route to host"}
  117. errS := err.Error()
  118. for _, boringS := range boringSubstrs {
  119. if strings.Contains(errS, boringS) {
  120. return false
  121. }
  122. }
  123. return true
  124. }