Skip to content

Commit c590917

Browse files
committed
Introduce test helpers and run integration tests separately
This introduces a `test` package which contains some test helpers. It also introduces a convention whereby tests with "Integration" in the name are treated as integration tests and run separately from the rest of the test suite using `-skip` and `-run` arguments to `go test` as needed. Integration tests that access an external shared resource such as Redis often need to be run serially, so providing a common pattern for them them will allow us to run non-integration tests as fast as possible.
1 parent 0fda39f commit c590917

File tree

6 files changed

+97
-64
lines changed

6 files changed

+97
-64
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/replicate/go
33
go 1.22
44

55
require (
6+
github.com/alicebob/miniredis/v2 v2.33.0
67
github.com/getsentry/sentry-go v0.28.1
78
github.com/go-logr/logr v1.4.2
89
github.com/go-redis/redismock/v9 v9.2.0
@@ -32,6 +33,7 @@ require (
3233
require (
3334
cloud.google.com/go/compute/metadata v0.3.0 // indirect
3435
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.23.0 // indirect
36+
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
3537
github.com/beorn7/perks v1.0.1 // indirect
3638
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
3739
github.com/cespare/xxhash/v2 v2.3.0 // indirect
@@ -58,6 +60,7 @@ require (
5860
github.com/prometheus/client_model v0.6.1 // indirect
5961
github.com/prometheus/common v0.53.0 // indirect
6062
github.com/prometheus/procfs v0.15.0 // indirect
63+
github.com/yuin/gopher-lua v1.1.1 // indirect
6164
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
6265
go.uber.org/multierr v1.11.0 // indirect
6366
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2Qx
22
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
33
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.23.0 h1:yRhWveg9NbJcJYoJL4FoSauT2dxnt4N9MIAJ7tvU/mQ=
44
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.23.0/go.mod h1:p2puVVSKjQ84Qb1gzw2XHLs34WQyHTYFZLaVxypAFYs=
5+
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
6+
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
7+
github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA=
8+
github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0=
59
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
610
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
711
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
@@ -120,6 +124,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
120124
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
121125
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ=
122126
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
127+
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
128+
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
123129
go.opentelemetry.io/contrib/detectors/gcp v1.27.0 h1:eVfDeFAPnMFZUhNdDZ/BbpEmC7/xxDKTSba5NhJH88s=
124130
go.opentelemetry.io/contrib/detectors/gcp v1.27.0/go.mod h1:amd+4uZxqJAUx7zI1JvygUtAc2EVWtQeyz8D+3161SQ=
125131
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=

lock/lock_test.go

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"os"
87
"sync"
98
"testing"
109
"time"
1110

1211
"github.com/go-redis/redismock/v9"
13-
"github.com/redis/go-redis/v9"
12+
"github.com/replicate/go/test"
1413
"github.com/stretchr/testify/assert"
1514
"github.com/stretchr/testify/require"
1615
)
@@ -151,18 +150,9 @@ func TestLockReleaseReturnsRedisErrors(t *testing.T) {
151150
}
152151

153152
func TestLockAcquireIntegration(t *testing.T) {
154-
redisURL := os.Getenv("REDIS_URL")
155-
if redisURL == "" {
156-
t.Skip("REDIS_URL is not set")
157-
}
158-
159-
ctx := context.Background()
160-
161-
opts, err := redis.ParseURL(redisURL)
162-
require.NoError(t, err)
163-
164-
client := redis.NewClient(opts)
165-
locker := Locker{Client: client}
153+
ctx := test.Context(t)
154+
rdb := test.Redis(ctx, t)
155+
locker := Locker{Client: rdb}
166156

167157
require.NoError(t, locker.Prepare(ctx))
168158

@@ -209,18 +199,9 @@ func TestLockAcquireIntegration(t *testing.T) {
209199
}
210200

211201
func TestLockTryAcquireIntegration(t *testing.T) {
212-
redisURL := os.Getenv("REDIS_URL")
213-
if redisURL == "" {
214-
t.Skip("REDIS_URL is not set")
215-
}
216-
217-
ctx := context.Background()
218-
219-
opts, err := redis.ParseURL(redisURL)
220-
require.NoError(t, err)
221-
222-
client := redis.NewClient(opts)
223-
locker := Locker{Client: client}
202+
ctx := test.Context(t)
203+
rdb := test.Redis(ctx, t)
204+
locker := Locker{Client: rdb}
224205

225206
require.NoError(t, locker.Prepare(ctx))
226207

ratelimit/ratelimit_test.go

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,20 @@ import (
44
"context"
55
"fmt"
66
"math/rand"
7-
"os"
87
"testing"
98
"time"
109

1110
"github.com/redis/go-redis/v9"
11+
"github.com/replicate/go/test"
1212
"github.com/stretchr/testify/assert"
1313
"github.com/stretchr/testify/require"
1414
)
1515

1616
func TestLimiterIntegration(t *testing.T) {
17-
if testing.Short() {
18-
t.SkipNow()
19-
}
20-
21-
redisURL := os.Getenv("REDIS_URL")
22-
if redisURL == "" {
23-
t.Skip("REDIS_URL is not set")
24-
}
25-
26-
ctx, cancel := context.WithCancel(context.Background())
27-
defer cancel()
28-
29-
opts, err := redis.ParseURL(redisURL)
30-
require.NoError(t, err)
17+
ctx := test.Context(t)
18+
rdb := test.Redis(ctx, t)
3119

32-
client := redis.NewClient(opts)
33-
limiter, _ := NewLimiter(client)
20+
limiter, _ := NewLimiter(rdb)
3421
require.NoError(t, limiter.Prepare(ctx))
3522

3623
// result counters
@@ -77,30 +64,21 @@ Outer:
7764
// Regression test for a bug where we weren't setting a TTL on the key the first
7865
// time the limiter was called.
7966
func TestLimiterAlwaysSetsExpiry(t *testing.T) {
80-
redisURL := os.Getenv("REDIS_URL")
81-
if redisURL == "" {
82-
t.Skip("REDIS_URL is not set")
83-
}
8467

8568
key := fmt.Sprintf("limit:testkey:%d", rand.Uint32())
8669

87-
ctx, cancel := context.WithCancel(context.Background())
88-
defer cancel()
89-
90-
opts, err := redis.ParseURL(redisURL)
91-
require.NoError(t, err)
92-
93-
client := redis.NewClient(opts)
94-
limiter, _ := NewLimiter(client)
70+
mr, rdb := test.MiniRedis(t)
71+
ctx := test.Context(t)
72+
limiter, _ := NewLimiter(rdb)
9573
require.NoError(t, limiter.Prepare(ctx))
9674

9775
// Clean up at the end of the test
98-
defer client.Del(ctx, key)
76+
t.Cleanup(func() { rdb.Del(ctx, key) })
9977

10078
_, _ = limiter.Take(ctx, key, 1, 100, 10000)
10179

102-
ttl := client.TTL(ctx, key).Val()
103-
require.Greater(t, ttl, time.Duration(0))
80+
mr.FastForward(time.Minute)
81+
assert.False(t, mr.Exists(key))
10482
}
10583

10684
func TestLimiterTakeWithNegativeInputsReturnsError(t *testing.T) {

script/test

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,33 @@ set -eu
44

55
: "${GITHUB_ACTIONS:=}"
66
: "${GOTESTSUM_FORMAT:=dots-v2}"
7+
: "${INTEGRATION:=}"
8+
: "${LOG_FORMAT:=development}"
79
: "${REDIS_URL:=}"
810

911
cd "$(dirname "$0")"
1012
cd ..
1113

1214
if [ "$GITHUB_ACTIONS" = "true" ]; then
1315
GOTESTSUM_FORMAT=github-actions
16+
INTEGRATION=1
1417
REDIS_URL=redis://
1518
fi
1619

1720
export GOTESTSUM_FORMAT
21+
export LOG_FORMAT
1822
export REDIS_URL
1923

20-
# Run the tests for the entire repository.
21-
#
22-
# You can change what this does by passing paths or other arguments to
23-
# gotestsum. See https://github.com/gotestyourself/gotestsum#documentation
24-
#
25-
exec go run gotest.tools/gotestsum@v1 "$@" -- -shuffle=on -timeout=15s ./...
24+
if [ "$#" -eq 0 ]; then
25+
set -- ./...
26+
fi
27+
28+
# Run unit tests
29+
go run gotest.tools/[email protected] -- -skip=Integration -race -shuffle=on -timeout=1s "$@"
30+
31+
# Run integration tests
32+
if [ -z "$INTEGRATION" ]; then
33+
printf "\033[1mNote:\033[0m skipping integration tests: set INTEGRATION=1 to run them.\n" >&2
34+
exit
35+
fi
36+
go run gotest.tools/[email protected] -- -run=Integration -p=1 -race -shuffle=on -timeout=30s "$@"

test/helpers.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package test
2+
3+
import (
4+
"context"
5+
"os"
6+
"testing"
7+
8+
"github.com/alicebob/miniredis/v2"
9+
"github.com/redis/go-redis/v9"
10+
)
11+
12+
func Context(t testing.TB) context.Context {
13+
t.Helper()
14+
15+
ctx, cancel := context.WithCancel(context.Background())
16+
t.Cleanup(cancel)
17+
18+
return ctx
19+
}
20+
21+
func Redis(ctx context.Context, t testing.TB) *redis.Client {
22+
t.Helper()
23+
24+
redisURL := os.Getenv("REDIS_URL")
25+
if redisURL == "" {
26+
t.Skip("REDIS_URL is not set")
27+
}
28+
29+
opts, err := redis.ParseURL(redisURL)
30+
if err != nil {
31+
t.Fatalf("failed to parse redis url: %v", err)
32+
}
33+
34+
rdb := redis.NewClient(opts)
35+
t.Cleanup(func() { _ = rdb.Close() })
36+
37+
// Reset the database
38+
if err := rdb.FlushDB(ctx).Err(); err != nil {
39+
t.Fatal("failed to flush db")
40+
}
41+
42+
return rdb
43+
}
44+
45+
func MiniRedis(t testing.TB) (*miniredis.Miniredis, *redis.Client) {
46+
t.Helper()
47+
48+
mr := miniredis.RunT(t)
49+
rdb := redis.NewClient(&redis.Options{
50+
Addr: mr.Addr(),
51+
})
52+
53+
return mr, rdb
54+
}

0 commit comments

Comments
 (0)