@@ -18,7 +18,7 @@ import (
1818 _ "github.com/lib/pq"
1919)
2020
21- // application represents a single instance of a application running an ORM and
21+ // application represents a single instance of an application running an ORM and
2222// exposing an HTTP REST API.
2323type application struct {
2424 language string
@@ -33,6 +33,7 @@ func (app application) dbName() string {
3333 return fmt .Sprintf ("company_%s" , app .orm )
3434}
3535
36+ // initTestDatabase launches a test database as a subprocess.
3637func initTestDatabase (t * testing.T , app application ) (* sql.DB , * url.URL , func ()) {
3738 ts , err := testserver .NewTestServer ()
3839 if err != nil {
@@ -56,21 +57,29 @@ func initTestDatabase(t *testing.T, app application) (*sql.DB, *url.URL, func())
5657
5758 ts .WaitForInit (db )
5859
60+ // Create the database if it does not exist.
5961 if _ , err := db .Exec ("CREATE DATABASE IF NOT EXISTS " + app .dbName ()); err != nil {
6062 t .Fatal (err )
6163 }
62- if _ , err := db .Exec ("SET DATABASE = " + app .dbName ()); err != nil {
64+
65+ // Connect to the database again, now with the database in the URL.
66+ url .Path = app .dbName ()
67+ db , err = sql .Open ("postgres" , url .String ())
68+ if err != nil {
6369 t .Fatal (err )
6470 }
65- url .Path = app .dbName ()
6671
6772 return db , url , func () {
6873 _ = db .Close ()
6974 ts .Stop ()
7075 }
7176}
7277
73- func initORMApp (t * testing.T , app application , dbURL * url.URL ) func () {
78+ type killFunc func ()
79+ type restartFunc func () (killFunc , restartFunc )
80+
81+ // initORMApp launches an ORM application as a subprocess.
82+ func initORMApp (t * testing.T , app application , dbURL * url.URL ) (killFunc , restartFunc ) {
7483 addrFlag := fmt .Sprintf ("ADDR=%s" , dbURL .String ())
7584 args := []string {"make" , "start" , "-C" , app .dir (), addrFlag }
7685
@@ -79,27 +88,59 @@ func initORMApp(t *testing.T, app application, dbURL *url.URL) func() {
7988 // make will launch the application in a child process, and this is the most
8089 // straightforward way to kill all ancestors.
8190 cmd .SysProcAttr = & syscall.SysProcAttr {Setpgid : true }
91+ killed := false
8292 killCmd := func () {
83- syscall .Kill (- cmd .Process .Pid , syscall .SIGKILL )
93+ if ! killed {
94+ syscall .Kill (- cmd .Process .Pid , syscall .SIGKILL )
95+ }
96+ killed = true
8497 }
8598
8699 // Set up stderr so we can later verify that it's clean.
87100 stderr := new (bytes.Buffer )
88101 cmd .Stderr = stderr
89102
90103 if err := cmd .Start (); err != nil {
104+ killCmd ()
91105 t .Fatal (err )
92106 }
93107 if cmd .Process != nil {
94108 log .Printf ("process %d started: %s" , cmd .Process .Pid , strings .Join (args , " " ))
95109 }
96110
97- time .Sleep (3 * time .Second )
111+ if err := waitForInit (); err != nil {
112+ killCmd ()
113+ t .Fatalf ("error waiting for http server initialization: %v stderr=%s" , err , stderr .String ())
114+ }
98115 if s := stderr .String (); len (s ) > 0 {
116+ killCmd ()
99117 t .Fatalf ("stderr=%s" , s )
100118 }
101119
102- return killCmd
120+ restartCmd := func () (killFunc , restartFunc ) {
121+ killCmd ()
122+ return initORMApp (t , app , dbURL )
123+ }
124+
125+ return killCmd , restartCmd
126+ }
127+
128+ // waitForInit retries until a connection is successfully established.
129+ func waitForInit () error {
130+ const maxWait = 15 * time .Second
131+ const waitDelay = 250 * time .Millisecond
132+ const maxWaitLoops = int (maxWait / waitDelay )
133+
134+ var err error
135+ var api apiHandler
136+ for i := 0 ; i < maxWaitLoops ; i ++ {
137+ if err = api .ping (); err == nil {
138+ return err
139+ }
140+ log .Printf ("waitForInit: %v" , err )
141+ time .Sleep (waitDelay )
142+ }
143+ return err
103144}
104145
105146func testORM (t * testing.T , language , orm string ) {
@@ -111,7 +152,7 @@ func testORM(t *testing.T, language, orm string) {
111152 db , dbURL , stopDB := initTestDatabase (t , app )
112153 defer stopDB ()
113154
114- stopApp := initORMApp (t , app , dbURL )
155+ stopApp , restartApp := initORMApp (t , app , dbURL )
115156 defer stopApp ()
116157
117158 td := testDriver {
@@ -120,27 +161,59 @@ func testORM(t *testing.T, language, orm string) {
120161 }
121162
122163 // Test that the correct tables were generated.
123- t .Run ("TestGeneratedTables " , td .TestGeneratedTables )
164+ t .Run ("GeneratedTables " , td .TestGeneratedTables )
124165
125166 // Test that the correct columns in those tables were generated.
126- t .Run ("TestGeneratedCustomersTableColumns" , td .TestGeneratedCustomersTableColumns )
127- t .Run ("TestGeneratedOrdersTableColumns" , td .TestGeneratedOrdersTableColumns )
128- t .Run ("TestGeneratedProductsTableColumns" , td .TestGeneratedProductsTableColumns )
129- t .Run ("TestGeneratedOrderProductsTableColumns" , td .TestGeneratedOrderProductsTableColumns )
167+ t .Run ("GeneratedColumns" , parallelTestGroup {
168+ "CustomersTable" : td .TestGeneratedCustomersTableColumns ,
169+ "ProductsTable" : td .TestGeneratedProductsTableColumns ,
170+ "OrdersTable" : td .TestGeneratedOrdersTableColumns ,
171+ "OrderProductsTable" : td .TestGeneratedOrderProductsTableColumns ,
172+ }.T )
130173
131174 // Test that the tables begin empty.
132- t .Run ("TestOrdersTableEmpty" , td .TestOrdersTableEmpty )
133- t .Run ("TestProductsTableEmpty" , td .TestProductsTableEmpty )
134- t .Run ("TestCustomersEmpty" , td .TestCustomersEmpty )
135- t .Run ("TestOrderProductsTableEmpty" , td .TestOrderProductsTableEmpty )
136-
137- // Test the creation of objects.
138- t .Run ("TestRetrieveCustomerBeforeCreation" , td .TestRetrieveCustomerBeforeCreation )
139- t .Run ("TestRetrieveProductBeforeCreation" , td .TestRetrieveProductBeforeCreation )
140- t .Run ("TestCreateCustomer" , td .TestCreateCustomer )
141- t .Run ("TestCreateProduct" , td .TestCreateProduct )
142- t .Run ("TestRetrieveCustomerAfterCreation" , td .TestRetrieveCustomerAfterCreation )
143- t .Run ("TestRetrieveProductAfterCreation" , td .TestRetrieveProductAfterCreation )
175+ t .Run ("EmptyTables" , parallelTestGroup {
176+ "CustomersTable" : td .TestCustomersEmpty ,
177+ "ProductsTable" : td .TestProductsTableEmpty ,
178+ "OrdersTable" : td .TestOrdersTableEmpty ,
179+ "OrderProductsTable" : td .TestOrderProductsTableEmpty ,
180+ }.T )
181+
182+ // Test that the API returns empty sets for each collection.
183+ t .Run ("RetrieveFromAPIBeforeCreation" , parallelTestGroup {
184+ "Customers" : td .TestRetrieveCustomersBeforeCreation ,
185+ "Products" : td .TestRetrieveProductsBeforeCreation ,
186+ "Orders" : td .TestRetrieveOrdersBeforeCreation ,
187+ }.T )
188+
189+ // Test the creation of initial objects.
190+ t .Run ("CreateCustomer" , td .TestCreateCustomer )
191+ t .Run ("CreateProduct" , td .TestCreateProduct )
192+
193+ // Test that the API returns what we just created.
194+ t .Run ("RetrieveFromAPIAfterInitialCreation" , parallelTestGroup {
195+ "Customers" : td .TestRetrieveCustomerAfterCreation ,
196+ "Products" : td .TestRetrieveProductAfterCreation ,
197+ }.T )
198+
199+ // Test the creation of dependent objects.
200+ t .Run ("CreateOrder" , td .TestCreateOrder )
201+
202+ // Test that the API returns what we just created.
203+ t .Run ("RetrieveFromAPIAfterDependentCreation" , parallelTestGroup {
204+ "Order" : td .TestRetrieveProductAfterCreation ,
205+ }.T )
206+
207+ // Restart the application.
208+ stopApp , restartApp = restartApp ()
209+ defer stopApp ()
210+
211+ // Test that the API still returns all created objects.
212+ t .Run ("RetrieveFromAPIAfterRestart" , parallelTestGroup {
213+ "Customers" : td .TestRetrieveCustomerAfterCreation ,
214+ "Products" : td .TestRetrieveProductAfterCreation ,
215+ "Order" : td .TestRetrieveProductAfterCreation ,
216+ }.T )
144217}
145218
146219func TestGORM (t * testing.T ) {
0 commit comments