@@ -4,12 +4,15 @@ import (
44 "context"
55 "database/sql"
66 "fmt"
7+ stdlog "log"
8+ "os"
79 "testing"
810
911 "github.com/google/uuid"
1012 _ "github.com/jackc/pgx/v4/stdlib"
1113 "github.com/kr/pretty"
12- "github.com/stretchr/testify/suite"
14+ "github.com/stretchr/testify/assert"
15+ "github.com/stretchr/testify/require"
1316 "github.com/stripe/pg-schema-diff/internal/pgdump"
1417 "github.com/stripe/pg-schema-diff/internal/pgengine"
1518 "github.com/stripe/pg-schema-diff/pkg/diff"
@@ -60,37 +63,45 @@ type (
6063 // If no expectedDBSchemaDDL is specified, the newSchemaDDL will be used
6164 expectedDBSchemaDDL []string
6265 }
63-
64- acceptanceTestSuite struct {
65- suite.Suite
66- pgEngine * pgengine.Engine
67- }
6866)
6967
70- func (suite * acceptanceTestSuite ) SetupSuite () {
71- engine , err := pgengine .StartEngine ()
72- suite .Require ().NoError (err )
73- suite .pgEngine = engine
74- }
68+ var pgEngine * pgengine.Engine
7569
76- func (suite * acceptanceTestSuite ) TearDownSuite () {
77- suite .pgEngine .Close ()
70+ func TestMain (m * testing.M ) {
71+ engine , err := pgengine .StartEngine ()
72+ if err != nil {
73+ stdlog .Fatalf ("Failed to start engine: %v" , err )
74+ }
75+ pgEngine = engine
76+ exitCode := m .Run ()
77+ if err := pgEngine .Close (); err != nil {
78+ stdlog .Fatalf ("Failed to close engine: %v" , err )
79+ }
80+ os .Exit (exitCode )
7881}
7982
8083// Simulates migrating a database and uses pgdump to compare the actual state to the expected state
81- func (suite * acceptanceTestSuite ) runTestCases (acceptanceTestCases []acceptanceTestCase ) {
82- for _ , tc := range acceptanceTestCases {
83- suite .Run (tc .name , func () {
84- suite .runTest (tc )
84+ func runTestCases (t * testing.T , acceptanceTestCases []acceptanceTestCase ) {
85+ t .Parallel ()
86+ for _ , _tc := range acceptanceTestCases {
87+ // Copy the test case since we are using t.Parallel (effectively spinning out a go routine).
88+ tc := _tc
89+ t .Run (tc .name , func (t * testing.T ) {
90+ t .Parallel ()
91+ runTest (t , tc )
8592 })
8693 }
8794}
8895
89- func (suite * acceptanceTestSuite ) runTest (tc acceptanceTestCase ) {
90- uuid .SetRand (& deterministicRandReader {})
91-
92- // Normalize the subtest
93- tc .planOpts = append (tc .planOpts , diff .WithLogger (log .SimpleLogger ()))
96+ func runTest (t * testing.T , tc acceptanceTestCase ) {
97+ deterministicRandReader := & deterministicRandReader {}
98+ // We moved a call to the random when we made tests run in parallel. This caused assertions on exact statements to fail.
99+ // To keep the assertions passing, we will generate a UUID and throw it out. In the future, we should just create
100+ // a more advanced system for asserting random statements that captures variables and allows them to be referenced
101+ // in future assertions.
102+ _ , err := uuid .NewRandomFromReader (deterministicRandReader )
103+ require .NoError (t , err )
104+ tc .planOpts = append ([]diff.PlanOpt {diff .WithLogger (log .SimpleLogger ()), diff .WithRandReader (deterministicRandReader )}, tc .planOpts ... )
94105 if tc .expectedDBSchemaDDL == nil {
95106 tc .expectedDBSchemaDDL = tc .newSchemaDDL
96107 }
@@ -106,70 +117,77 @@ func (suite *acceptanceTestSuite) runTest(tc acceptanceTestCase) {
106117 }
107118 }
108119
120+ engine := pgEngine
121+ if len (tc .roles ) > 0 {
122+ // If the test needs roles (server-wide), provide isolation by spinning out a dedicated pgengine.
123+ dedicatedEngine , err := pgengine .StartEngine ()
124+ require .NoError (t , err )
125+ defer dedicatedEngine .Close ()
126+ engine = dedicatedEngine
127+ }
128+
109129 // Create roles since they are global
110- rootDb , err := sql .Open ("pgx" , suite . pgEngine .GetPostgresDatabaseDSN ())
111- suite . Require (). NoError (err )
130+ rootDb , err := sql .Open ("pgx" , engine .GetPostgresDatabaseDSN ())
131+ require . NoError (t , err )
112132 defer rootDb .Close ()
113133 for _ , r := range tc .roles {
114134 _ , err := rootDb .Exec (fmt .Sprintf ("CREATE ROLE %s" , r ))
115- suite . Require (). NoError (err )
135+ require . NoError (t , err )
116136 }
117- defer func () {
118- // This will drop the roles (and attempt to reset other cluster-level state)
119- suite .Require ().NoError (pgengine .ResetInstance (context .Background (), rootDb ))
120- }()
121137
122138 // Apply old schema DDL to old DB
123- oldDb , err := suite .pgEngine .CreateDatabase ()
124- suite .Require ().NoError (err )
139+ require .NoError (t , err )
140+ oldDb , err := engine .CreateDatabaseWithName (fmt .Sprintf ("pgtemp_%s" , uuid .NewString ()))
141+ require .NoError (t , err )
125142 defer oldDb .DropDB ()
126143 // Apply the old schema
127- suite . Require (). NoError (applyDDL (oldDb , tc .oldSchemaDDL ))
144+ require . NoError (t , applyDDL (oldDb , tc .oldSchemaDDL ))
128145
129146 // Migrate the old DB
130147 oldDBConnPool , err := sql .Open ("pgx" , oldDb .GetDSN ())
131- suite . Require (). NoError (err )
148+ require . NoError (t , err )
132149 defer oldDBConnPool .Close ()
150+ oldDBConnPool .SetMaxOpenConns (1 )
133151
134152 tempDbFactory , err := tempdb .NewOnInstanceFactory (context .Background (), func (ctx context.Context , dbName string ) (* sql.DB , error ) {
135- return sql .Open ("pgx" , suite . pgEngine .GetPostgresDatabaseConnOpts ().With ("dbname" , dbName ).ToDSN ())
136- })
137- suite . Require (). NoError (err )
153+ return sql .Open ("pgx" , engine .GetPostgresDatabaseConnOpts ().With ("dbname" , dbName ).ToDSN ())
154+ }, tempdb . WithRandReader ( deterministicRandReader ) )
155+ require . NoError (t , err )
138156 defer func (tempDbFactory tempdb.Factory ) {
139157 // It's important that this closes properly (the temp database is dropped),
140158 // so assert it has no error for acceptance tests
141- suite . Require (). NoError (tempDbFactory .Close ())
159+ require . NoError (t , tempDbFactory .Close ())
142160 }(tempDbFactory )
143161
144162 plan , err := tc .planFactory (context .Background (), oldDBConnPool , tempDbFactory , tc .newSchemaDDL , tc .planOpts ... )
145163 if tc .expectedPlanErrorIs != nil || len (tc .expectedPlanErrorContains ) > 0 {
146164 if tc .expectedPlanErrorIs != nil {
147- suite .ErrorIs (err , tc .expectedPlanErrorIs )
165+ assert .ErrorIs (t , err , tc .expectedPlanErrorIs )
148166 }
149167 if len (tc .expectedPlanErrorContains ) > 0 {
150- suite .ErrorContains (err , tc .expectedPlanErrorContains )
168+ assert .ErrorContains (t , err , tc .expectedPlanErrorContains )
151169 }
152170 return
153171 }
154- suite . Require (). NoError (err )
172+ require . NoError (t , err )
155173
156- suite . assertValidPlan (plan )
174+ assertValidPlan (t , plan )
157175 if tc .expectEmptyPlan {
158176 // It shouldn't be necessary, but we'll run all checks below this point just in case rather than exiting early
159- suite .Empty (plan .Statements )
177+ assert .Empty (t , plan .Statements )
160178 }
161- suite .ElementsMatch (tc .expectedHazardTypes , getUniqueHazardTypesFromStatements (plan .Statements ), prettySprintPlan (plan ))
179+ assert .ElementsMatch (t , tc .expectedHazardTypes , getUniqueHazardTypesFromStatements (plan .Statements ), prettySprintPlan (plan ))
162180
163181 // Apply the plan
164- suite . Require (). NoError (applyPlan (oldDb , plan ), prettySprintPlan (plan ))
182+ require . NoError (t , applyPlan (oldDb , plan ), prettySprintPlan (plan ))
165183
166184 // Make sure the pgdump after running the migration is the same as the
167185 // pgdump from a database where we directly run the newSchemaDDL
168186 oldDbDump , err := pgdump .GetDump (oldDb , pgdump .WithSchemaOnly ())
169- suite . Require (). NoError (err )
187+ require . NoError (t , err )
170188
171- newDbDump := suite . directlyRunDDLAndGetDump (tc .expectedDBSchemaDDL )
172- suite .Equal (newDbDump , oldDbDump , prettySprintPlan (plan ))
189+ newDbDump := directlyRunDDLAndGetDump (t , engine , tc .expectedDBSchemaDDL )
190+ assert .Equal (t , newDbDump , oldDbDump , prettySprintPlan (plan ))
173191
174192 if tc .expectedPlanDDL != nil {
175193 var generatedDDL []string
@@ -181,30 +199,30 @@ func (suite *acceptanceTestSuite) runTest(tc acceptanceTestCase) {
181199 // We can also make the system more advanced by using tokens in place of the "randomly" generated UUIDs, such
182200 // the test case doesn't need to be updated if the UUID generation changes. If we built this functionality, we
183201 // should also integrate it with the schema_migration_plan_test.go tests.
184- suite .Equal (tc .expectedPlanDDL , generatedDDL , "data packing can change the the generated UUID and DDL" )
202+ assert .Equal (t , tc .expectedPlanDDL , generatedDDL , "data packing can change the the generated UUID and DDL" )
185203 }
186204
187205 // Make sure no diff is found if we try to regenerate a plan
188206 plan , err = tc .planFactory (context .Background (), oldDBConnPool , tempDbFactory , tc .newSchemaDDL , tc .planOpts ... )
189- suite . Require (). NoError (err )
190- suite .Empty (plan .Statements , prettySprintPlan (plan ))
207+ require . NoError (t , err )
208+ assert .Empty (t , plan .Statements , prettySprintPlan (plan ))
191209}
192210
193- func ( suite * acceptanceTestSuite ) assertValidPlan ( plan diff.Plan ) {
211+ func assertValidPlan ( t * testing. T , plan diff.Plan ) {
194212 for _ , stmt := range plan .Statements {
195- suite .Greater (stmt .Timeout .Nanoseconds (), int64 (0 ), "timeout should be greater than 0. stmt=%+v" , stmt )
196- suite .Greater (stmt .LockTimeout .Nanoseconds (), int64 (0 ), "lock timeout should be greater than 0. stmt=%+v" , stmt )
213+ assert .Greater (t , stmt .Timeout .Nanoseconds (), int64 (0 ), "timeout should be greater than 0. stmt=%+v" , stmt )
214+ assert .Greater (t , stmt .LockTimeout .Nanoseconds (), int64 (0 ), "lock timeout should be greater than 0. stmt=%+v" , stmt )
197215 }
198216}
199217
200- func ( suite * acceptanceTestSuite ) directlyRunDDLAndGetDump ( ddl []string ) string {
201- newDb , err := suite . pgEngine .CreateDatabase ()
202- suite . Require (). NoError (err )
218+ func directlyRunDDLAndGetDump ( t * testing. T , engine * pgengine. Engine , ddl []string ) string {
219+ newDb , err := engine .CreateDatabase ()
220+ require . NoError (t , err )
203221 defer newDb .DropDB ()
204- suite . Require (). NoError (applyDDL (newDb , ddl ))
222+ require . NoError (t , applyDDL (newDb , ddl ))
205223
206224 newDbDump , err := pgdump .GetDump (newDb , pgdump .WithSchemaOnly ())
207- suite . Require (). NoError (err )
225+ require . NoError (t , err )
208226 return newDbDump
209227}
210228
@@ -261,7 +279,3 @@ func (r *deterministicRandReader) Read(p []byte) (int, error) {
261279 }
262280 return len (p ), nil
263281}
264-
265- func TestAcceptanceSuite (t * testing.T ) {
266- suite .Run (t , new (acceptanceTestSuite ))
267- }
0 commit comments