Skip to content

Commit

Permalink
Introduce test helpers and run integration tests separately
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nickstenning committed Jul 14, 2024
1 parent 0fda39f commit c590917
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 64 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/replicate/go
go 1.22

require (
github.com/alicebob/miniredis/v2 v2.33.0
github.com/getsentry/sentry-go v0.28.1
github.com/go-logr/logr v1.4.2
github.com/go-redis/redismock/v9 v9.2.0
Expand Down Expand Up @@ -32,6 +33,7 @@ require (
require (
cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.23.0 // indirect
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
Expand All @@ -58,6 +60,7 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.53.0 // indirect
github.com/prometheus/procfs v0.15.0 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2Qx
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.23.0 h1:yRhWveg9NbJcJYoJL4FoSauT2dxnt4N9MIAJ7tvU/mQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.23.0/go.mod h1:p2puVVSKjQ84Qb1gzw2XHLs34WQyHTYFZLaVxypAFYs=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA=
github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
Expand Down Expand Up @@ -120,6 +124,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ=
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
go.opentelemetry.io/contrib/detectors/gcp v1.27.0 h1:eVfDeFAPnMFZUhNdDZ/BbpEmC7/xxDKTSba5NhJH88s=
go.opentelemetry.io/contrib/detectors/gcp v1.27.0/go.mod h1:amd+4uZxqJAUx7zI1JvygUtAc2EVWtQeyz8D+3161SQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
Expand Down
33 changes: 7 additions & 26 deletions lock/lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import (
"context"
"errors"
"fmt"
"os"
"sync"
"testing"
"time"

"github.com/go-redis/redismock/v9"
"github.com/redis/go-redis/v9"
"github.com/replicate/go/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -151,18 +150,9 @@ func TestLockReleaseReturnsRedisErrors(t *testing.T) {
}

func TestLockAcquireIntegration(t *testing.T) {
redisURL := os.Getenv("REDIS_URL")
if redisURL == "" {
t.Skip("REDIS_URL is not set")
}

ctx := context.Background()

opts, err := redis.ParseURL(redisURL)
require.NoError(t, err)

client := redis.NewClient(opts)
locker := Locker{Client: client}
ctx := test.Context(t)
rdb := test.Redis(ctx, t)
locker := Locker{Client: rdb}

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

Expand Down Expand Up @@ -209,18 +199,9 @@ func TestLockAcquireIntegration(t *testing.T) {
}

func TestLockTryAcquireIntegration(t *testing.T) {
redisURL := os.Getenv("REDIS_URL")
if redisURL == "" {
t.Skip("REDIS_URL is not set")
}

ctx := context.Background()

opts, err := redis.ParseURL(redisURL)
require.NoError(t, err)

client := redis.NewClient(opts)
locker := Locker{Client: client}
ctx := test.Context(t)
rdb := test.Redis(ctx, t)
locker := Locker{Client: rdb}

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

Expand Down
42 changes: 10 additions & 32 deletions ratelimit/ratelimit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,20 @@ import (
"context"
"fmt"
"math/rand"
"os"
"testing"
"time"

"github.com/redis/go-redis/v9"
"github.com/replicate/go/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestLimiterIntegration(t *testing.T) {
if testing.Short() {
t.SkipNow()
}

redisURL := os.Getenv("REDIS_URL")
if redisURL == "" {
t.Skip("REDIS_URL is not set")
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

opts, err := redis.ParseURL(redisURL)
require.NoError(t, err)
ctx := test.Context(t)
rdb := test.Redis(ctx, t)

client := redis.NewClient(opts)
limiter, _ := NewLimiter(client)
limiter, _ := NewLimiter(rdb)
require.NoError(t, limiter.Prepare(ctx))

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

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

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

opts, err := redis.ParseURL(redisURL)
require.NoError(t, err)

client := redis.NewClient(opts)
limiter, _ := NewLimiter(client)
mr, rdb := test.MiniRedis(t)
ctx := test.Context(t)
limiter, _ := NewLimiter(rdb)
require.NoError(t, limiter.Prepare(ctx))

// Clean up at the end of the test
defer client.Del(ctx, key)
t.Cleanup(func() { rdb.Del(ctx, key) })

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

ttl := client.TTL(ctx, key).Val()
require.Greater(t, ttl, time.Duration(0))
mr.FastForward(time.Minute)
assert.False(t, mr.Exists(key))
}

func TestLimiterTakeWithNegativeInputsReturnsError(t *testing.T) {
Expand Down
23 changes: 17 additions & 6 deletions script/test
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,33 @@ set -eu

: "${GITHUB_ACTIONS:=}"
: "${GOTESTSUM_FORMAT:=dots-v2}"
: "${INTEGRATION:=}"
: "${LOG_FORMAT:=development}"
: "${REDIS_URL:=}"

cd "$(dirname "$0")"
cd ..

if [ "$GITHUB_ACTIONS" = "true" ]; then
GOTESTSUM_FORMAT=github-actions
INTEGRATION=1
REDIS_URL=redis://
fi

export GOTESTSUM_FORMAT
export LOG_FORMAT
export REDIS_URL

# Run the tests for the entire repository.
#
# You can change what this does by passing paths or other arguments to
# gotestsum. See https://github.com/gotestyourself/gotestsum#documentation
#
exec go run gotest.tools/gotestsum@v1 "$@" -- -shuffle=on -timeout=15s ./...
if [ "$#" -eq 0 ]; then
set -- ./...
fi

# Run unit tests
go run gotest.tools/[email protected] -- -skip=Integration -race -shuffle=on -timeout=1s "$@"

# Run integration tests
if [ -z "$INTEGRATION" ]; then
printf "\033[1mNote:\033[0m skipping integration tests: set INTEGRATION=1 to run them.\n" >&2
exit
fi
go run gotest.tools/[email protected] -- -run=Integration -p=1 -race -shuffle=on -timeout=30s "$@"
54 changes: 54 additions & 0 deletions test/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package test

import (
"context"
"os"
"testing"

"github.com/alicebob/miniredis/v2"
"github.com/redis/go-redis/v9"
)

func Context(t testing.TB) context.Context {
t.Helper()

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)

return ctx
}

func Redis(ctx context.Context, t testing.TB) *redis.Client {
t.Helper()

redisURL := os.Getenv("REDIS_URL")
if redisURL == "" {
t.Skip("REDIS_URL is not set")
}

opts, err := redis.ParseURL(redisURL)
if err != nil {
t.Fatalf("failed to parse redis url: %v", err)
}

rdb := redis.NewClient(opts)
t.Cleanup(func() { _ = rdb.Close() })

// Reset the database
if err := rdb.FlushDB(ctx).Err(); err != nil {
t.Fatal("failed to flush db")
}

return rdb
}

func MiniRedis(t testing.TB) (*miniredis.Miniredis, *redis.Client) {
t.Helper()

mr := miniredis.RunT(t)
rdb := redis.NewClient(&redis.Options{
Addr: mr.Addr(),
})

return mr, rdb
}

0 comments on commit c590917

Please sign in to comment.