Skip to content

Commit

Permalink
WIP for 100% test coverage.
Browse files Browse the repository at this point in the history
  • Loading branch information
adlio committed Nov 23, 2021
1 parent e39e998 commit a7bc68f
Show file tree
Hide file tree
Showing 9 changed files with 447 additions and 239 deletions.
28 changes: 18 additions & 10 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@ import (

var (
// ErrBeginFailed indicates that the Begin() method failed (couldn't start Tx)
ErrBeginFailed = fmt.Errorf("Begin Failed")
ErrBeginFailed = fmt.Errorf("begin failed")

// ErrPriorFailure indicates a failure happened earlier in the Migrator Apply()
ErrPriorFailure = fmt.Errorf("previous error")
)

// BadQueryer implements the Connection interface, but fails on every call to
// Exec or Query. The error message will include the SQL statement to help
// verify the "right" failure occurred.
type BadQueryer struct{}

func (bt BadQueryer) Begin() (*sql.Tx, error) {
return nil, nil
}

func (bq BadQueryer) Exec(sql string, args ...interface{}) (sql.Result, error) {
return nil, fmt.Errorf("FAIL: %s", strings.TrimSpace(sql))
}
Expand All @@ -28,18 +27,27 @@ func (bq BadQueryer) Query(sql string, args ...interface{}) (*sql.Rows, error) {
return nil, fmt.Errorf("FAIL: %s", strings.TrimSpace(sql))
}

// BadTransactor implements the Connector interface with no-ops for Exec() and
// BadTransactor implements the Transactor interface with no-ops for Exec() and
// Query(), and failures on all calls to Begin()
type BadTransactor struct{}

func (bt BadTransactor) Begin() (*sql.Tx, error) {
return nil, ErrBeginFailed
}

func (bt BadTransactor) Exec(sql string, args ...interface{}) (sql.Result, error) {
return nil, nil
// BadConnection implements the Connection interface, but fails on all calls to
// Begin(), Query() or Exec()
//
type BadConnection struct{}

func (bc BadConnection) Begin() (*sql.Tx, error) {
return nil, ErrBeginFailed
}

func (bt BadTransactor) Query(sql string, args ...interface{}) (*sql.Rows, error) {
return nil, nil
func (bc BadConnection) Exec(sql string, args ...interface{}) (sql.Result, error) {
return nil, fmt.Errorf("FAIL: %s", strings.TrimSpace(sql))
}

func (bc BadConnection) Query(sql string, args ...interface{}) (*sql.Rows, error) {
return nil, fmt.Errorf("FAIL: %s", strings.TrimSpace(sql))
}
111 changes: 111 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package schema

import (
"database/sql"
"fmt"
"log"
"os"
"testing"

"github.com/ory/dockertest"
)

type ConnInfo struct {
Driver string
DockerRepo string
DockerTag string
DSN string
Resource *dockertest.Resource
}

var DBConns map[string]*ConnInfo = map[string]*ConnInfo{
"postgres11": {
Driver: "postgres",
DockerRepo: "postgres",
DockerTag: "11",
},
}

// TestMain replaces the normal test runner for this package. It connects to
// Docker running on the local machine and launches testing database
// containers to which we then connect and store the connection in a package
// global variable
//
func TestMain(m *testing.M) {
pool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("Can't run schema tests. Docker is not running: %s", err)
}

for _, info := range DBConns {
switch info.Driver {
case "postgres":
// Provision the container
info.Resource, err = pool.Run(info.DockerRepo, info.DockerTag, []string{
"POSTGRES_USER=postgres",
"POSTGRES_PASSWORD=secret",
"POSTGRES_DB=schematests",
})
if err != nil {
log.Fatalf("Could not start container %s:%s: %s", info.DockerRepo, info.DockerTag, err)
}

// Set the container to expire in a minute to avoid orphaned contianers
// hanging around
err = info.Resource.Expire(60)
if err != nil {
log.Fatalf("Could not set expiration time for docker test containers: %s", err)
}

// Save the DSN to make new connections later
info.DSN = fmt.Sprintf("postgres://postgres:secret@localhost:%s/schematests?sslmode=disable", info.Resource.GetPort("5432/tcp"))

// Wait for the database to come online
if err = pool.Retry(func() error {
testConn, err := sql.Open(info.Driver, info.DSN)
if err != nil {
return err
}
defer func() { _ = testConn.Close() }()
return testConn.Ping()
}); err != nil {
log.Fatalf("Could not connect to container: %s", err)
return
}

}
}

code := m.Run()

// Purge all the containers we created
// You can't defer this because os.Exit doesn't execute defers
for _, info := range DBConns {
if err := pool.Purge(info.Resource); err != nil {
log.Fatalf("Could not purge resource: %s", err)
}
}

os.Exit(code)
}

func withEachDB(t *testing.T, f func(db *sql.DB)) {
for dbName := range DBConns {
t.Run(dbName, func(t *testing.T) {
db := connectDB(t, dbName)
f(db)
})
}
}

func connectDB(t *testing.T, name string) *sql.DB {
info, exists := DBConns[name]
if !exists {
t.Errorf("Database '%s' doesn't exist.", name)
}
db, err := sql.Open(info.Driver, info.DSN)
if err != nil {
t.Error(err)
}
return db
}
2 changes: 1 addition & 1 deletion migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Migration struct {
// MD5 computes the MD5 hash of the Script for this migration so that it
// can be uniquely identified later.
func (m *Migration) MD5() string {
return fmt.Sprintf("%x", md5.Sum([]byte(m.Script))) // #nosec not using MD5 cryptographically
return fmt.Sprintf("%x", md5.Sum([]byte(m.Script)))
}

// SortMigrations sorts a slice of migrations by their IDs
Expand Down
Loading

0 comments on commit a7bc68f

Please sign in to comment.