StressErr is the same as Stress except that the given allowedStartErr is tolerated on start (either no error or an error that is allowedStartErr is allowed). This is useful for services that may want to return an error if they're shut down as they're still starting up.
(ctx context.Context, tb testingT, svc startstop.Service, allowedStartErr error)
| 24 | // allowed). This is useful for services that may want to return an error if |
| 25 | // they're shut down as they're still starting up. |
| 26 | func StressErr(ctx context.Context, tb testingT, svc startstop.Service, allowedStartErr error) { |
| 27 | tb.Helper() |
| 28 | |
| 29 | var wg sync.WaitGroup |
| 30 | |
| 31 | isAllowedStartError := func(err error) bool { |
| 32 | if allowedStartErr != nil { |
| 33 | if errors.Is(err, allowedStartErr) { |
| 34 | return true |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | // Always allow this one because a fairly common intermittent failure is |
| 39 | // to produce an I/O timeout while trying to connect to Postgres. |
| 40 | // |
| 41 | // write failed: write tcp 127.0.0.1:60976->127.0.0.1:5432: i/o timeout |
| 42 | // |
| 43 | if strings.HasSuffix(err.Error(), "i/o timeout") { |
| 44 | return true |
| 45 | } |
| 46 | |
| 47 | return false |
| 48 | } |
| 49 | |
| 50 | for range 10 { |
| 51 | wg.Go(func() { |
| 52 | for range 50 { |
| 53 | err := svc.Start(ctx) |
| 54 | if err != nil && !isAllowedStartError(err) { |
| 55 | require.NoError(tb, err) |
| 56 | } |
| 57 | |
| 58 | stopped := make(chan struct{}) |
| 59 | |
| 60 | go func() { |
| 61 | defer close(stopped) |
| 62 | svc.Stop() |
| 63 | }() |
| 64 | |
| 65 | select { |
| 66 | case <-stopped: |
| 67 | case <-time.After(5 * time.Second): |
| 68 | require.FailNow(tb, "Timed out waiting for service to stop") |
| 69 | } |
| 70 | } |
| 71 | }) |
| 72 | } |
| 73 | |
| 74 | wg.Wait() |
| 75 | } |
| 76 | |
| 77 | // Minimal interface for *testing.B/*testing.T that lets us test a failure |
| 78 | // condition for our test helpers above. |