testwrapper_test.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package main_test
  4. import (
  5. "bytes"
  6. "errors"
  7. "fmt"
  8. "os"
  9. "os/exec"
  10. "path/filepath"
  11. "sync"
  12. "testing"
  13. )
  14. var (
  15. buildPath string
  16. buildErr error
  17. buildOnce sync.Once
  18. )
  19. func cmdTestwrapper(t *testing.T, args ...string) *exec.Cmd {
  20. buildOnce.Do(func() {
  21. buildPath, buildErr = buildTestWrapper()
  22. })
  23. if buildErr != nil {
  24. t.Fatalf("building testwrapper: %s", buildErr)
  25. }
  26. return exec.Command(buildPath, args...)
  27. }
  28. func buildTestWrapper() (string, error) {
  29. dir, err := os.MkdirTemp("", "testwrapper")
  30. if err != nil {
  31. return "", fmt.Errorf("making temp dir: %w", err)
  32. }
  33. _, err = exec.Command("go", "build", "-o", dir, ".").Output()
  34. if err != nil {
  35. return "", fmt.Errorf("go build: %w", err)
  36. }
  37. return filepath.Join(dir, "testwrapper"), nil
  38. }
  39. func TestRetry(t *testing.T) {
  40. t.Parallel()
  41. testfile := filepath.Join(t.TempDir(), "retry_test.go")
  42. code := []byte(`package retry_test
  43. import (
  44. "os"
  45. "testing"
  46. "tailscale.com/cmd/testwrapper/flakytest"
  47. )
  48. func TestOK(t *testing.T) {}
  49. func TestFlakeRun(t *testing.T) {
  50. flakytest.Mark(t, "https://github.com/tailscale/tailscale/issues/0") // random issue
  51. e := os.Getenv(flakytest.FlakeAttemptEnv)
  52. if e == "" {
  53. t.Skip("not running in testwrapper")
  54. }
  55. if e == "1" {
  56. t.Fatal("First run in testwrapper, failing so that test is retried. This is expected.")
  57. }
  58. }
  59. `)
  60. if err := os.WriteFile(testfile, code, 0o644); err != nil {
  61. t.Fatalf("writing package: %s", err)
  62. }
  63. out, err := cmdTestwrapper(t, "-v", testfile).CombinedOutput()
  64. if err != nil {
  65. t.Fatalf("go run . %s: %s with output:\n%s", testfile, err, out)
  66. }
  67. want := []byte("ok\t" + testfile + " [attempt=2]")
  68. if !bytes.Contains(out, want) {
  69. t.Fatalf("wanted output containing %q but got:\n%s", want, out)
  70. }
  71. if okRuns := bytes.Count(out, []byte("=== RUN TestOK")); okRuns != 1 {
  72. t.Fatalf("expected TestOK to be run once but was run %d times in output:\n%s", okRuns, out)
  73. }
  74. if flakeRuns := bytes.Count(out, []byte("=== RUN TestFlakeRun")); flakeRuns != 2 {
  75. t.Fatalf("expected TestFlakeRun to be run twice but was run %d times in output:\n%s", flakeRuns, out)
  76. }
  77. if testing.Verbose() {
  78. t.Logf("success - output:\n%s", out)
  79. }
  80. }
  81. func TestNoRetry(t *testing.T) {
  82. t.Parallel()
  83. testfile := filepath.Join(t.TempDir(), "noretry_test.go")
  84. code := []byte(`package noretry_test
  85. import (
  86. "testing"
  87. "tailscale.com/cmd/testwrapper/flakytest"
  88. )
  89. func TestFlakeRun(t *testing.T) {
  90. flakytest.Mark(t, "https://github.com/tailscale/tailscale/issues/0") // random issue
  91. t.Error("shouldn't be retried")
  92. }
  93. func TestAlwaysError(t *testing.T) {
  94. t.Error("error")
  95. }
  96. `)
  97. if err := os.WriteFile(testfile, code, 0o644); err != nil {
  98. t.Fatalf("writing package: %s", err)
  99. }
  100. out, err := cmdTestwrapper(t, "-v", testfile).Output()
  101. if err == nil {
  102. t.Fatalf("go run . %s: expected error but it succeeded with output:\n%s", testfile, out)
  103. }
  104. if code, ok := errExitCode(err); ok && code != 1 {
  105. t.Fatalf("expected exit code 1 but got %d", code)
  106. }
  107. want := []byte("Not retrying flaky tests because non-flaky tests failed.")
  108. if !bytes.Contains(out, want) {
  109. t.Fatalf("wanted output containing %q but got:\n%s", want, out)
  110. }
  111. if flakeRuns := bytes.Count(out, []byte("=== RUN TestFlakeRun")); flakeRuns != 1 {
  112. t.Fatalf("expected TestFlakeRun to be run once but was run %d times in output:\n%s", flakeRuns, out)
  113. }
  114. if testing.Verbose() {
  115. t.Logf("success - output:\n%s", out)
  116. }
  117. }
  118. func TestBuildError(t *testing.T) {
  119. t.Parallel()
  120. // Construct our broken package.
  121. testfile := filepath.Join(t.TempDir(), "builderror_test.go")
  122. code := []byte("package builderror_test\n\nderp")
  123. err := os.WriteFile(testfile, code, 0o644)
  124. if err != nil {
  125. t.Fatalf("writing package: %s", err)
  126. }
  127. buildErr := []byte("builderror_test.go:3:1: expected declaration, found derp\nFAIL command-line-arguments [setup failed]")
  128. // Confirm `go test` exits with code 1.
  129. goOut, err := exec.Command("go", "test", testfile).CombinedOutput()
  130. if code, ok := errExitCode(err); !ok || code != 1 {
  131. t.Fatalf("go test %s: expected error with exit code 0 but got: %v", testfile, err)
  132. }
  133. if !bytes.Contains(goOut, buildErr) {
  134. t.Fatalf("go test %s: expected build error containing %q but got:\n%s", testfile, buildErr, goOut)
  135. }
  136. // Confirm `testwrapper` exits with code 1.
  137. twOut, err := cmdTestwrapper(t, testfile).CombinedOutput()
  138. if code, ok := errExitCode(err); !ok || code != 1 {
  139. t.Fatalf("testwrapper %s: expected error with exit code 0 but got: %v", testfile, err)
  140. }
  141. if !bytes.Contains(twOut, buildErr) {
  142. t.Fatalf("testwrapper %s: expected build error containing %q but got:\n%s", testfile, buildErr, twOut)
  143. }
  144. if testing.Verbose() {
  145. t.Logf("success - output:\n%s", twOut)
  146. }
  147. }
  148. func TestTimeout(t *testing.T) {
  149. t.Parallel()
  150. // Construct our broken package.
  151. testfile := filepath.Join(t.TempDir(), "timeout_test.go")
  152. code := []byte(`package noretry_test
  153. import (
  154. "testing"
  155. "time"
  156. )
  157. func TestTimeout(t *testing.T) {
  158. time.Sleep(500 * time.Millisecond)
  159. }
  160. `)
  161. err := os.WriteFile(testfile, code, 0o644)
  162. if err != nil {
  163. t.Fatalf("writing package: %s", err)
  164. }
  165. out, err := cmdTestwrapper(t, testfile, "-timeout=20ms").CombinedOutput()
  166. if code, ok := errExitCode(err); !ok || code != 1 {
  167. t.Fatalf("testwrapper %s: expected error with exit code 0 but got: %v; output was:\n%s", testfile, err, out)
  168. }
  169. if want := "panic: test timed out after 20ms"; !bytes.Contains(out, []byte(want)) {
  170. t.Fatalf("testwrapper %s: expected build error containing %q but got:\n%s", testfile, buildErr, out)
  171. }
  172. if testing.Verbose() {
  173. t.Logf("success - output:\n%s", out)
  174. }
  175. }
  176. func errExitCode(err error) (int, bool) {
  177. var exit *exec.ExitError
  178. if errors.As(err, &exit) {
  179. return exit.ExitCode(), true
  180. }
  181. return 0, false
  182. }