Skip to content

Commit 7a43138

Browse files
committed
[!] use testcontainers for integration tests
1 parent 6d7b4a8 commit 7a43138

9 files changed

Lines changed: 318 additions & 86 deletions

File tree

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"files.eol": "\n"
2+
"files.eol": "\n",
3+
"terminal.integrated.defaultProfile.windows": "PowerShell"
34
}

go.mod

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,76 @@ require (
1414
github.com/sirupsen/logrus v1.9.3
1515
github.com/spf13/viper v1.21.0
1616
github.com/stretchr/testify v1.11.1
17+
github.com/testcontainers/testcontainers-go v0.39.0
18+
github.com/testcontainers/testcontainers-go/modules/postgres v0.39.0
1719
gopkg.in/natefinch/lumberjack.v2 v2.2.1
1820
)
1921

2022
require (
23+
dario.cat/mergo v1.0.2 // indirect
24+
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
25+
github.com/Microsoft/go-winio v0.6.2 // indirect
26+
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
27+
github.com/containerd/errdefs v1.0.0 // indirect
28+
github.com/containerd/errdefs/pkg v0.3.0 // indirect
29+
github.com/containerd/log v0.1.0 // indirect
30+
github.com/containerd/platforms v0.2.1 // indirect
31+
github.com/cpuguy83/dockercfg v0.3.2 // indirect
2132
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
33+
github.com/distribution/reference v0.6.0 // indirect
34+
github.com/docker/docker v28.4.0+incompatible // indirect
35+
github.com/docker/go-connections v0.6.0 // indirect
36+
github.com/docker/go-units v0.5.0 // indirect
37+
github.com/ebitengine/purego v0.9.0 // indirect
38+
github.com/felixge/httpsnoop v1.0.4 // indirect
2239
github.com/fsnotify/fsnotify v1.9.0 // indirect
40+
github.com/go-logr/logr v1.4.3 // indirect
41+
github.com/go-logr/stdr v1.2.2 // indirect
42+
github.com/go-ole/go-ole v1.3.0 // indirect
2343
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
44+
github.com/google/uuid v1.6.0 // indirect
45+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
2446
github.com/jackc/pgpassfile v1.0.0 // indirect
2547
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
2648
github.com/jackc/puddle/v2 v2.2.2 // indirect
49+
github.com/klauspost/compress v1.18.0 // indirect
50+
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
51+
github.com/magiconair/properties v1.8.10 // indirect
52+
github.com/moby/docker-image-spec v1.3.1 // indirect
53+
github.com/moby/go-archive v0.1.0 // indirect
54+
github.com/moby/patternmatcher v0.6.0 // indirect
55+
github.com/moby/sys/sequential v0.6.0 // indirect
56+
github.com/moby/sys/user v0.4.0 // indirect
57+
github.com/moby/sys/userns v0.1.0 // indirect
58+
github.com/moby/term v0.5.2 // indirect
59+
github.com/morikuni/aec v1.0.0 // indirect
60+
github.com/opencontainers/go-digest v1.0.0 // indirect
61+
github.com/opencontainers/image-spec v1.1.1 // indirect
2762
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
2863
github.com/pkg/errors v0.9.1 // indirect
2964
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
65+
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
3066
github.com/sagikazarmark/locafero v0.12.0 // indirect
67+
github.com/shirou/gopsutil/v4 v4.25.8 // indirect
3168
github.com/spf13/afero v1.15.0 // indirect
3269
github.com/spf13/cast v1.10.0 // indirect
3370
github.com/spf13/pflag v1.0.10 // indirect
3471
github.com/subosito/gotenv v1.6.0 // indirect
72+
github.com/tklauser/go-sysconf v0.3.15 // indirect
73+
github.com/tklauser/numcpus v0.10.0 // indirect
74+
github.com/yusufpapurcu/wmi v1.2.4 // indirect
75+
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
76+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
77+
go.opentelemetry.io/otel v1.38.0 // indirect
78+
go.opentelemetry.io/otel/metric v1.38.0 // indirect
79+
go.opentelemetry.io/otel/trace v1.38.0 // indirect
3580
go.yaml.in/yaml/v3 v3.0.4 // indirect
3681
golang.org/x/crypto v0.42.0 // indirect
3782
golang.org/x/sync v0.17.0 // indirect
3883
golang.org/x/sys v0.36.0 // indirect
3984
golang.org/x/text v0.29.0 // indirect
85+
google.golang.org/grpc v1.75.0 // indirect
86+
google.golang.org/protobuf v1.36.7 // indirect
4087
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
4188
gopkg.in/yaml.v3 v3.0.1 // indirect
4289
)

go.sum

Lines changed: 138 additions & 4 deletions
Large diffs are not rendered by default.

internal/pgengine/copy_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import (
66
"testing"
77

88
"github.com/cybertec-postgresql/pg_timetable/internal/pgengine"
9+
"github.com/cybertec-postgresql/pg_timetable/internal/testutils"
910
"github.com/stretchr/testify/assert"
1011
)
1112

1213
func TestCopyFromFile(t *testing.T) {
13-
teardownTestCase := SetupTestCase(t)
14-
defer teardownTestCase(t)
14+
container, cleanup := testutils.SetupPostgresContainer(t)
15+
defer cleanup()
1516
ctx := context.Background()
17+
pge := container.Engine
1618
_, err := pge.CopyFromFile(ctx, "fake.csv", "COPY location FROM STDIN")
1719
assert.Error(t, err, "Should fail for missing file")
1820
_, err = pge.ConfigDb.Exec(ctx, "CREATE UNLOGGED TABLE csv_test(id integer, val text)")
@@ -29,9 +31,10 @@ func TestCopyFromFile(t *testing.T) {
2931
}
3032

3133
func TestCopyToFile(t *testing.T) {
32-
teardownTestCase := SetupTestCase(t)
33-
defer teardownTestCase(t)
34+
container, cleanup := testutils.SetupPostgresContainer(t)
35+
defer cleanup()
3436
ctx := context.Background()
37+
pge := container.Engine
3538
_, err := pge.CopyToFile(ctx, "", "COPY location TO STDOUT")
3639
assert.Error(t, err, "Should fail for empty file name")
3740
cnt, err := pge.CopyToFile(ctx, "test.csv", "COPY (SELECT generate_series(1,5)) TO STDOUT (FORMAT csv)")

internal/pgengine/migration_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"testing"
77

88
"github.com/cybertec-postgresql/pg_timetable/internal/pgengine"
9+
"github.com/cybertec-postgresql/pg_timetable/internal/testutils"
910
migrator "github.com/cybertec-postgresql/pgx-migrator"
1011
"github.com/stretchr/testify/assert"
1112
)
@@ -14,10 +15,11 @@ import (
1415
var initialsql string
1516

1617
func TestMigrations(t *testing.T) {
17-
teardownTestCase := SetupTestCase(t)
18-
defer teardownTestCase(t)
18+
container, cleanup := testutils.SetupPostgresContainer(t)
19+
defer cleanup()
1920

2021
ctx := context.Background()
22+
pge := container.Engine
2123
_, err := pge.ConfigDb.Exec(ctx, "DROP SCHEMA IF EXISTS timetable CASCADE")
2224
assert.NoError(t, err)
2325
_, err = pge.ConfigDb.Exec(ctx, string(initialsql))
@@ -38,13 +40,14 @@ func TestExecuteMigrationScript(t *testing.T) {
3840
}
3941

4042
func TestInitMigrator(t *testing.T) {
41-
teardownTestCase := SetupTestCase(t)
42-
defer teardownTestCase(t)
43+
container, cleanup := testutils.SetupPostgresContainer(t)
44+
defer cleanup()
4345
pgengine.Migrations = func() migrator.Option {
4446
return migrator.Migrations()
4547
}
4648

4749
ctx := context.Background()
50+
pge := container.Engine
4851
err := pge.MigrateDb(ctx)
4952
assert.Error(t, err, "Empty migrations")
5053
_, err = pge.CheckNeedMigrateDb(ctx)

internal/pgengine/notification_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import (
88

99
"github.com/cybertec-postgresql/pg_timetable/internal/config"
1010
"github.com/cybertec-postgresql/pg_timetable/internal/pgengine"
11+
"github.com/cybertec-postgresql/pg_timetable/internal/testutils"
1112
"github.com/stretchr/testify/assert"
1213
)
1314

1415
// notify sends NOTIFY each second until context is available
15-
func notifyAndCheck(ctx context.Context, conn pgengine.PgxIface, t *testing.T, channel string) {
16+
func notifyAndCheck(ctx context.Context, conn pgengine.PgxIface, pge *pgengine.PgEngine, t *testing.T, channel string) {
1617
vals := []string{
1718
`{"ConfigID": 1, "Command": "STOP", "Ts": 1}`, //{1, "STOP", 1, 0},
1819
`{"ConfigID": 2, "Command": "START", "Ts": 1}`, //{2, "START", 1, 0},
@@ -42,18 +43,19 @@ func notifyAndCheck(ctx context.Context, conn pgengine.PgxIface, t *testing.T, c
4243
}
4344

4445
func TestHandleNotifications(t *testing.T) {
45-
teardownTestCase := SetupTestCaseEx(t, func(c *config.CmdOptions) {
46+
container, cleanup := testutils.SetupPostgresContainerWithOptions(t, func(c *config.CmdOptions) {
4647
c.Start.Debug = true
4748
})
48-
defer teardownTestCase(t)
49+
defer cleanup()
4950
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
5051
defer cancel()
52+
pge := container.Engine
5153
go pge.HandleNotifications(ctx)
5254
time.Sleep(5 * time.Second)
5355
conn, err := pge.ConfigDb.Acquire(ctx) // HandleNotifications() uses blocking manner, so we want another connection
5456
assert.NoError(t, err)
5557
defer conn.Release()
5658
_, err = conn.Exec(ctx, "UNLISTEN *") // do not interfere with the main handler
5759
assert.NoError(t, err)
58-
notifyAndCheck(ctx, pge.ConfigDb, t, "pgengine_unit_test")
60+
notifyAndCheck(ctx, pge.ConfigDb, pge, t, "pgengine_unit_test")
5961
}

internal/pgengine/pgengine_test.go

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,45 +15,15 @@ import (
1515
"github.com/cybertec-postgresql/pg_timetable/internal/log"
1616
"github.com/cybertec-postgresql/pg_timetable/internal/pgengine"
1717
"github.com/cybertec-postgresql/pg_timetable/internal/scheduler"
18+
"github.com/cybertec-postgresql/pg_timetable/internal/testutils"
1819
)
1920

20-
// this instance used for all engine tests
21-
var pge *pgengine.PgEngine
22-
23-
var cmdOpts *config.CmdOptions = config.NewCmdOptions("--clientname=pgengine_unit_test", "--connstr=postgresql://scheduler:somestrong@localhost/timetable")
24-
25-
// SetupTestCaseEx allows to configure the test case before execution
26-
func SetupTestCaseEx(t *testing.T, fc func(c *config.CmdOptions)) func(t *testing.T) {
27-
fc(cmdOpts)
28-
return SetupTestCase(t)
29-
}
30-
31-
// SetupTestCase used to connect and to initialize test PostgreSQL database
32-
func SetupTestCase(t *testing.T) func(t *testing.T) {
33-
t.Helper()
34-
timeout := time.After(30 * time.Second)
35-
done := make(chan bool)
36-
go func() {
37-
pge, _ = pgengine.New(context.Background(), *cmdOpts, log.Init(config.LoggingOpts{LogLevel: "error"}))
38-
done <- true
39-
}()
40-
select {
41-
case <-timeout:
42-
t.Fatal("Cannot connect and initialize test database in time")
43-
case <-done:
44-
}
45-
return func(t *testing.T) {
46-
_, _ = pge.ConfigDb.Exec(context.Background(), "DROP SCHEMA IF EXISTS timetable CASCADE")
47-
pge.ConfigDb.Close()
48-
t.Log("Test schema dropped")
49-
}
50-
}
51-
5221
func TestInitAndTestConfigDBConnection(t *testing.T) {
53-
teardownTestCase := SetupTestCase(t)
54-
defer teardownTestCase(t)
22+
container, cleanup := testutils.SetupPostgresContainer(t)
23+
defer cleanup()
5524

5625
ctx := context.Background()
26+
pge := container.Engine
5727

5828
require.NotNil(t, pge.ConfigDb, "ConfigDB should be initialized")
5929

@@ -105,8 +75,10 @@ func TestInitAndTestConfigDBConnection(t *testing.T) {
10575
t.Run("Check connection closing", func(t *testing.T) {
10676
pge.Finalize()
10777
assert.Nil(t, pge.ConfigDb, "Connection isn't closed properly")
108-
// reinit connection to execute teardown actions
109-
pge, _ = pgengine.New(context.Background(), *cmdOpts, log.Init(config.LoggingOpts{LogLevel: "error"}))
78+
// reinit connection to execute teardown actions - create new container
79+
newContainer, newCleanup := testutils.SetupPostgresContainer(t)
80+
defer newCleanup()
81+
pge = newContainer.Engine
11082
})
11183

11284
}
@@ -120,10 +92,11 @@ func TestFailedConnect(t *testing.T) {
12092
}
12193

12294
func TestSchedulerFunctions(t *testing.T) {
123-
teardownTestCase := SetupTestCase(t)
124-
defer teardownTestCase(t)
95+
container, cleanup := testutils.SetupPostgresContainer(t)
96+
defer cleanup()
12597

12698
ctx := context.Background()
99+
pge := container.Engine
127100

128101
t.Run("Check DeleteChainConfig function", func(t *testing.T) {
129102
assert.Equal(t, false, pge.DeleteChain(ctx, 0), "Should not delete in clean database")
@@ -180,10 +153,11 @@ func TestSchedulerFunctions(t *testing.T) {
180153
}
181154

182155
func TestGetRemoteDBTransaction(t *testing.T) {
183-
teardownTestCase := SetupTestCase(t)
184-
defer teardownTestCase(t)
156+
container, cleanup := testutils.SetupPostgresContainer(t)
157+
defer cleanup()
185158
ctx := context.Background()
186-
remoteDb, err := pge.GetRemoteDBConnection(context.Background(), cmdOpts.ConnStr)
159+
pge := container.Engine
160+
remoteDb, err := pge.GetRemoteDBConnection(context.Background(), container.ConnStr)
187161
defer pge.FinalizeDBConnection(ctx, remoteDb)
188162
require.NoError(t, err, "remoteDB should be initialized")
189163
require.NotNil(t, remoteDb, "remoteDB should be initialized")
@@ -196,12 +170,13 @@ func TestGetRemoteDBTransaction(t *testing.T) {
196170
}
197171

198172
func TestSamplesScripts(t *testing.T) {
199-
teardownTestCase := SetupTestCase(t)
200-
defer teardownTestCase(t)
173+
container, cleanup := testutils.SetupPostgresContainer(t)
174+
defer cleanup()
201175

202176
files, err := os.ReadDir("../../samples")
203177
assert.NoError(t, err, "Cannot read samples directory")
204178
l := log.Init(config.LoggingOpts{LogLevel: "error"})
179+
pge := container.Engine
205180
for _, f := range files {
206181
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
207182
defer cancel()

internal/scheduler/scheduler_test.go

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,14 @@ import (
1010

1111
"github.com/cybertec-postgresql/pg_timetable/internal/config"
1212
"github.com/cybertec-postgresql/pg_timetable/internal/log"
13-
"github.com/cybertec-postgresql/pg_timetable/internal/pgengine"
13+
"github.com/cybertec-postgresql/pg_timetable/internal/testutils"
1414
)
1515

16-
var pge *pgengine.PgEngine
17-
18-
// SetupTestCase used to connect and to initialize test PostgreSQL database
19-
func SetupTestCase(t *testing.T) func(t *testing.T) {
20-
cmdOpts := config.NewCmdOptions("-c", "pgengine_unit_test", "--connstr=postgresql://scheduler:somestrong@localhost/timetable")
21-
t.Log("Setup test case")
22-
timeout := time.After(6 * time.Second)
23-
done := make(chan bool)
24-
go func() {
25-
pge, _ = pgengine.New(context.Background(), *cmdOpts, log.Init(config.LoggingOpts{LogLevel: "error"}))
26-
done <- true
27-
}()
28-
select {
29-
case <-timeout:
30-
t.Fatal("Cannot connect and initialize test database in time")
31-
case <-done:
32-
}
33-
return func(t *testing.T) {
34-
_, _ = pge.ConfigDb.Exec(context.Background(), "DROP SCHEMA IF EXISTS timetable CASCADE")
35-
t.Log("Test schema dropped")
36-
}
37-
}
38-
3916
func TestRun(t *testing.T) {
40-
teardownTestCase := SetupTestCase(t)
41-
defer teardownTestCase(t)
17+
container, cleanup := testutils.SetupPostgresContainer(t)
18+
defer cleanup()
4219

20+
pge := container.Engine
4321
require.NotNil(t, pge.ConfigDb, "ConfigDB should be initialized")
4422

4523
err := pge.ExecuteCustomScripts(context.Background(), "../../samples/Exclusive.sql")

0 commit comments

Comments
 (0)