@@ -3,101 +3,86 @@ package loopdb
33import (
44 "context"
55 "database/sql"
6- "fmt"
7- "strconv"
8- "strings"
6+ "net"
7+ "os"
8+ "path/filepath"
9+ "sync"
910 "testing"
1011 "time"
1112
13+ embeddedpostgres "github.com/fergusstrange/embedded-postgres"
1214 _ "github.com/lib/pq"
13- "github.com/ory/dockertest/v3"
14- "github.com/ory/dockertest/v3/docker"
1515 "github.com/stretchr/testify/require"
1616)
1717
1818const (
1919 testPgUser = "test"
2020 testPgPass = "test"
2121 testPgDBName = "test"
22- PostgresTag = "11"
2322)
2423
25- // TestPgFixture is a test fixture that starts a Postgres 11 instance in a
26- // docker container .
24+ // TestPgFixture is a test fixture that starts a Postgres 11 instance using an
25+ // embedded Postgres runtime .
2726type TestPgFixture struct {
28- db * sql.DB
29- pool * dockertest.Pool
30- resource * dockertest.Resource
31- host string
32- port int
27+ db * sql.DB
28+ pg * embeddedpostgres.EmbeddedPostgres
29+ host string
30+ port int
31+ expiryTimer * time.Timer
32+ stopOnce sync.Once
3333}
3434
35- // NewTestPgFixture constructs a new TestPgFixture starting up a docker
36- // container running Postgres 11. The started container will expire in after
37- // the passed duration .
35+ // NewTestPgFixture constructs a new TestPgFixture starting up an embedded
36+ // Postgres 11 server . The process will be auto-stopped after the specified
37+ // expiry if TearDown is not called first .
3838func NewTestPgFixture (t * testing.T , expiry time.Duration ) * TestPgFixture {
39- // Use a sensible default on Windows (tcp/http) and linux/osx (socket)
40- // by specifying an empty endpoint.
41- pool , err := dockertest .NewPool ("" )
42- require .NoError (t , err , "Could not connect to docker" )
43-
44- // Pulls an image, creates a container based on it and runs it.
45- resource , err := pool .RunWithOptions (& dockertest.RunOptions {
46- Repository : "postgres" ,
47- Tag : PostgresTag ,
48- Env : []string {
49- fmt .Sprintf ("POSTGRES_USER=%v" , testPgUser ),
50- fmt .Sprintf ("POSTGRES_PASSWORD=%v" , testPgPass ),
51- fmt .Sprintf ("POSTGRES_DB=%v" , testPgDBName ),
52- "listen_addresses='*'" ,
53- },
54- Cmd : []string {
55- "postgres" ,
56- "-c" , "log_statement=all" ,
57- "-c" , "log_destination=stderr" ,
58- },
59- }, func (config * docker.HostConfig ) {
60- // Set AutoRemove to true so that stopped container goes away
61- // by itself.
62- config .AutoRemove = true
63- config .RestartPolicy = docker.RestartPolicy {Name : "no" }
64- })
65- require .NoError (t , err , "Could not start resource" )
66-
67- hostAndPort := resource .GetHostPort ("5432/tcp" )
68- parts := strings .Split (hostAndPort , ":" )
69- host := parts [0 ]
70- port , err := strconv .ParseInt (parts [1 ], 10 , 64 )
71- require .NoError (t , err )
39+ port := getFreePort (t )
40+ runtimePath := t .TempDir ()
41+ logDir := filepath .Join (runtimePath , "logs" )
42+ require .NoError (t , os .MkdirAll (logDir , 0o755 ))
43+ config := embeddedpostgres .DefaultConfig ().
44+ Version (embeddedpostgres .V11 ).
45+ Database (testPgDBName ).
46+ Username (testPgUser ).
47+ Password (testPgPass ).
48+ Port (uint32 (port )).
49+ RuntimePath (runtimePath ).
50+ StartParameters (map [string ]string {
51+ "listen_addresses" : "127.0.0.1" ,
52+
53+ // Logging collector only captures stderr output, so
54+ // keep this destination in sync with the log file
55+ // settings below.
56+ "log_statement" : "all" ,
57+ "log_destination" : "stderr" ,
58+ "logging_collector" : "on" ,
59+ "log_directory" : logDir ,
60+ "log_filename" : "postgres.log" ,
61+ })
62+
63+ pg := embeddedpostgres .NewDatabase (config )
64+ require .NoError (t , pg .Start (), "Could not start embedded Postgres" )
7265
7366 fixture := & TestPgFixture {
74- host : host ,
75- port : int (port ),
67+ host : "127.0.0.1" ,
68+ port : port ,
69+ pg : pg ,
7670 }
77- databaseURL := fixture .GetDSN ()
78- log .Infof ("Connecting to Postgres fixture: %v\n " , databaseURL )
79-
80- // Tell docker to hard kill the container in "expiry" seconds.
81- require .NoError (t , resource .Expire (uint (expiry .Seconds ())))
8271
83- // Exponential backoff-retry, because the application in the container
84- // might not be ready to accept connections yet.
85- pool .MaxWait = 120 * time .Second
72+ if expiry > 0 {
73+ fixture .expiryTimer = time .AfterFunc (expiry , func () {
74+ log .Warnf ("Postgres fixture exceeded lifetime; tearing down" )
75+ fixture .TearDown (t )
76+ })
77+ }
8678
87- var testDB * sql.DB
88- err = pool .Retry (func () error {
89- testDB , err = sql .Open ("postgres" , databaseURL )
90- if err != nil {
91- return err
92- }
93- return testDB .Ping ()
94- })
95- require .NoError (t , err , "Could not connect to docker" )
79+ databaseURL := fixture .GetDSN ()
80+ log .Infof ("Connecting to Postgres fixture: %v\n " , databaseURL )
9681
97- // Now fill in the rest of the fixture.
82+ testDB , err := sql .Open ("postgres" , databaseURL )
83+ require .NoError (t , err , "Could not open connection to Postgres fixture" )
84+ require .NoError (t , testDB .Ping (), "Could not connect to embedded Postgres" )
9885 fixture .db = testDB
99- fixture .pool = pool
100- fixture .resource = resource
10186
10287 return fixture
10388}
@@ -119,10 +104,26 @@ func (f *TestPgFixture) GetConfig() *PostgresConfig {
119104 }
120105}
121106
122- // TearDown stops the underlying docker container .
107+ // TearDown stops the embedded Postgres process and releases resources .
123108func (f * TestPgFixture ) TearDown (t * testing.T ) {
124- err := f .pool .Purge (f .resource )
125- require .NoError (t , err , "Could not purge resource" )
109+ if f .expiryTimer != nil {
110+ f .expiryTimer .Stop ()
111+ }
112+
113+ f .stopOnce .Do (func () {
114+ if f .db != nil {
115+ err := f .db .Close ()
116+ require .NoErrorf (t , err , "failed to close postgres" )
117+ }
118+
119+ if f .pg != nil {
120+ err := f .pg .Stop ()
121+ require .NoErrorf (t , err , "failed to stop postgres" )
122+ }
123+
124+ f .db = nil
125+ f .pg = nil
126+ })
126127}
127128
128129// ClearDB clears the database.
@@ -137,3 +138,15 @@ func (f *TestPgFixture) ClearDB(t *testing.T) {
137138 )
138139 require .NoError (t , err )
139140}
141+
142+ // getFreePort returns an available TCP port on localhost.
143+ func getFreePort (t * testing.T ) int {
144+ listener , err := net .Listen ("tcp" , "127.0.0.1:0" )
145+ require .NoError (t , err )
146+
147+ defer func () {
148+ require .NoError (t , listener .Close ())
149+ }()
150+
151+ return listener .Addr ().(* net.TCPAddr ).Port
152+ }
0 commit comments