-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathtestdb_test.go
236 lines (211 loc) · 6.11 KB
/
testdb_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package schema
import (
"database/sql"
"fmt"
"io/ioutil"
"log"
"os"
"runtime"
"testing"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
)
// TestDB represents a specific database instance against which we would like
// to run database migration tests.
type TestDB struct {
Dialect Dialect
Driver string
DockerRepo string
DockerTag string
Resource *dockertest.Resource
SkippedArchs []string
path string
}
func (c *TestDB) Username() string {
switch c.Driver {
case MSSQLDriverName:
return "SA"
default:
return "schemauser"
}
}
func (c *TestDB) Password() string {
switch c.Driver {
case MSSQLDriverName:
return "Th1sI5AMor3_Compl1c4tedPasswd!"
default:
return "schemasecret"
}
}
func (c *TestDB) DatabaseName() string {
switch c.Driver {
case MSSQLDriverName:
return "master"
default:
return "schematests"
}
}
// Port asks Docker for the host-side port we can use to connect to the
// relevant container's database port.
func (c *TestDB) Port() string {
switch c.Driver {
case MySQLDriverName:
return c.Resource.GetPort("3306/tcp")
case PostgresDriverName:
return c.Resource.GetPort("5432/tcp")
case MSSQLDriverName:
return c.Resource.GetPort("1433/tcp")
}
return ""
}
func (c *TestDB) IsDocker() bool {
return c.DockerRepo != "" && c.DockerTag != ""
}
func (c *TestDB) IsSQLite() bool {
return c.Driver == SQLiteDriverName
}
// DockerEnvars computes the environment variables that are needed for a
// docker instance.
func (c *TestDB) DockerEnvars() []string {
switch c.Driver {
case PostgresDriverName:
return []string{
fmt.Sprintf("POSTGRES_USER=%s", c.Username()),
fmt.Sprintf("POSTGRES_PASSWORD=%s", c.Password()),
fmt.Sprintf("POSTGRES_DB=%s", c.DatabaseName()),
}
case MySQLDriverName:
return []string{
"MYSQL_RANDOM_ROOT_PASSWORD=true",
fmt.Sprintf("MYSQL_USER=%s", c.Username()),
fmt.Sprintf("MYSQL_PASSWORD=%s", c.Password()),
fmt.Sprintf("MYSQL_DATABASE=%s", c.DatabaseName()),
}
case MSSQLDriverName:
return []string{
"ACCEPT_EULA=Y",
fmt.Sprintf("SA_USER=%s", c.Username()),
fmt.Sprintf("SA_PASSWORD=%s", c.Password()),
fmt.Sprintf("SA_DATABASE=%s", c.DatabaseName()),
}
default:
return []string{}
}
}
func (c *TestDB) IsRunnable() bool {
for _, skippedArch := range c.SkippedArchs {
if skippedArch == runtime.GOARCH {
return false
}
}
return true
}
// Path computes the full path to the database on disk (applies only to SQLite
// instances).
func (c *TestDB) Path() string {
switch c.Driver {
case SQLiteDriverName:
if c.path == "" {
tmpF, _ := ioutil.TempFile("", "schema.*.sqlite3")
c.path = tmpF.Name()
}
return c.path
default:
return ""
}
}
func (c *TestDB) DSN() string {
switch c.Driver {
case PostgresDriverName:
return fmt.Sprintf("postgres://%s:%s@localhost:%s/%s?sslmode=disable", c.Username(), c.Password(), c.Port(), c.DatabaseName())
case SQLiteDriverName:
return c.Path()
case MySQLDriverName:
/**
* Since we want the system to be compatible with both parseTime=true and
* not, we use different querystrings with MariaDB and MySQL.
*/
if c.DockerRepo == "mariadb" {
return fmt.Sprintf("%s:%s@(localhost:%s)/%s?parseTime=true&multiStatements=true", c.Username(), c.Password(), c.Port(), c.DatabaseName())
}
return fmt.Sprintf("%s:%s@(localhost:%s)/%s?multiStatements=true", c.Username(), c.Password(), c.Port(), c.DatabaseName())
case MSSQLDriverName:
return fmt.Sprintf("sqlserver://%s:%s@localhost:%s/?database=%s", c.Username(), c.Password(), c.Port(), c.DatabaseName())
}
// TODO Return error
return "NoDSN"
}
// Init sets up a test database instance for connections. For dockertest-based
// instances, this function triggers the `docker run` call. For SQLite-based
// test instances, this creates the data file. In all cases, we verify that
// the database is connectable via a test connection.
func (c *TestDB) Init(pool *dockertest.Pool) {
var err error
if c.IsDocker() {
// For Docker-based test databases, we send a startup signal to have Docker
// launch a container for this test run.
log.Printf("Starting docker container %s:%s\n", c.DockerRepo, c.DockerTag)
// The container is started with AutoRemove: true, and a restart policy to
// not restart
c.Resource, err = pool.RunWithOptions(&dockertest.RunOptions{
Repository: c.DockerRepo,
Tag: c.DockerTag,
Env: c.DockerEnvars(),
}, func(config *docker.HostConfig) {
config.AutoRemove = true
config.RestartPolicy = docker.RestartPolicy{
Name: "no",
}
})
if err != nil {
log.Fatalf("Could not start container %s:%s: %s", c.DockerRepo, c.DockerTag, err)
}
// Even if everything goes OK, kill off the container after n seconds
_ = c.Resource.Expire(120)
}
// Regardless of whether the DB is docker-based on not, we use the pool's
// exponential backoff helper to wait until connections succeed for this
// database
err = pool.Retry(func() error {
testConn, err := sql.Open(c.Driver, c.DSN())
if err != nil {
return err
}
// We close the test connection... other code will re-open via the DSN()
defer func() { _ = testConn.Close() }()
return testConn.Ping()
})
if err != nil {
log.Fatalf("Could not connect to %s: %s", c.DSN(), err)
} else {
log.Printf("Successfully connected to %s", c.DSN())
}
}
// Connect creates an additional *database/sql.DB connection for a particular
// test database.
func (c *TestDB) Connect(t *testing.T) *sql.DB {
db, err := sql.Open(c.Driver, c.DSN())
if err != nil {
t.Error(err)
}
return db
}
// Cleanup should be called after all tests with a database instance are
// complete. For dockertest-based tests, it deletes the docker containers.
// For SQLite tests, it deletes the database file from the temp directory.
func (c *TestDB) Cleanup(pool *dockertest.Pool) {
var err error
switch {
case c.Driver == SQLiteDriverName:
err = os.Remove(c.Path())
if os.IsNotExist(err) {
// Ignore error cleaning up nonexistent file
err = nil
}
case c.IsDocker() && c.Resource != nil:
err = pool.Purge(c.Resource)
}
if err != nil {
log.Fatalf("Could not cleanup %s: %s", c.DSN(), err)
}
}