migrate/migrate_test.go

1408 lines
36 KiB
Go

package migrate
import (
"bytes"
"database/sql"
"io/ioutil"
"log"
"os"
"strings"
"testing"
)
import (
dStub "github.com/golang-migrate/migrate/v4/database/stub"
"github.com/golang-migrate/migrate/v4/source"
sStub "github.com/golang-migrate/migrate/v4/source/stub"
)
// sourceStubMigrations hold the following migrations:
// u = up migration, d = down migration, n = version
// | 1 | - | 3 | 4 | 5 | - | 7 |
// | u d | - | u | u d | d | - | u d |
var sourceStubMigrations *source.Migrations
func init() {
sourceStubMigrations = source.NewMigrations()
sourceStubMigrations.Append(&source.Migration{Version: 1, Direction: source.Up, Identifier: "CREATE 1"})
sourceStubMigrations.Append(&source.Migration{Version: 1, Direction: source.Down, Identifier: "DROP 1"})
sourceStubMigrations.Append(&source.Migration{Version: 3, Direction: source.Up, Identifier: "CREATE 3"})
sourceStubMigrations.Append(&source.Migration{Version: 4, Direction: source.Up, Identifier: "CREATE 4"})
sourceStubMigrations.Append(&source.Migration{Version: 4, Direction: source.Down, Identifier: "DROP 4"})
sourceStubMigrations.Append(&source.Migration{Version: 5, Direction: source.Down, Identifier: "DROP 5"})
sourceStubMigrations.Append(&source.Migration{Version: 7, Direction: source.Up, Identifier: "CREATE 7"})
sourceStubMigrations.Append(&source.Migration{Version: 7, Direction: source.Down, Identifier: "DROP 7"})
}
type DummyInstance struct{ Name string }
func TestNew(t *testing.T) {
m, err := New("stub://", "stub://")
if err != nil {
t.Fatal(err)
}
if m.sourceName != "stub" {
t.Errorf("expected stub, got %v", m.sourceName)
}
if m.sourceDrv == nil {
t.Error("expected sourceDrv not to be nil")
}
if m.databaseName != "stub" {
t.Errorf("expected stub, got %v", m.databaseName)
}
if m.databaseDrv == nil {
t.Error("expected databaseDrv not to be nil")
}
}
func ExampleNew() {
// Read migrations from /home/mattes/migrations and connect to a local postgres database.
m, err := New("file:///home/mattes/migrations", "postgres://mattes:secret@localhost:5432/database?sslmode=disable")
if err != nil {
log.Fatal(err)
}
// Migrate all the way up ...
if err := m.Up(); err != nil {
log.Fatal(err)
}
}
func TestNewWithDatabaseInstance(t *testing.T) {
dummyDb := &DummyInstance{"database"}
dbInst, err := dStub.WithInstance(dummyDb, &dStub.Config{})
if err != nil {
t.Fatal(err)
}
m, err := NewWithDatabaseInstance("stub://", "stub", dbInst)
if err != nil {
t.Fatal(err)
}
if m.sourceName != "stub" {
t.Errorf("expected stub, got %v", m.sourceName)
}
if m.sourceDrv == nil {
t.Error("expected sourceDrv not to be nil")
}
if m.databaseName != "stub" {
t.Errorf("expected stub, got %v", m.databaseName)
}
if m.databaseDrv == nil {
t.Error("expected databaseDrv not to be nil")
}
}
func ExampleNewWithDatabaseInstance() {
// Create and use an existing database instance.
db, err := sql.Open("postgres", "postgres://mattes:secret@localhost:5432/database?sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Create driver instance from db.
// Check each driver if it supports the WithInstance function.
// `import "github.com/golang-migrate/migrate/v4/database/postgres"`
instance, err := dStub.WithInstance(db, &dStub.Config{})
if err != nil {
log.Fatal(err)
}
// Read migrations from /home/mattes/migrations and connect to a local postgres database.
m, err := NewWithDatabaseInstance("file:///home/mattes/migrations", "postgres", instance)
if err != nil {
log.Fatal(err)
}
// Migrate all the way up ...
if err := m.Up(); err != nil {
log.Fatal(err)
}
}
func TestNewWithSourceInstance(t *testing.T) {
dummySource := &DummyInstance{"source"}
sInst, err := sStub.WithInstance(dummySource, &sStub.Config{})
if err != nil {
t.Fatal(err)
}
m, err := NewWithSourceInstance("stub", sInst, "stub://")
if err != nil {
t.Fatal(err)
}
if m.sourceName != "stub" {
t.Errorf("expected stub, got %v", m.sourceName)
}
if m.sourceDrv == nil {
t.Error("expected sourceDrv not to be nil")
}
if m.databaseName != "stub" {
t.Errorf("expected stub, got %v", m.databaseName)
}
if m.databaseDrv == nil {
t.Error("expected databaseDrv not to be nil")
}
}
func ExampleNewWithSourceInstance() {
di := &DummyInstance{"think any client required for a source here"}
// Create driver instance from DummyInstance di.
// Check each driver if it support the WithInstance function.
// `import "github.com/golang-migrate/migrate/v4/source/stub"`
instance, err := sStub.WithInstance(di, &sStub.Config{})
if err != nil {
log.Fatal(err)
}
// Read migrations from Stub and connect to a local postgres database.
m, err := NewWithSourceInstance("stub", instance, "postgres://mattes:secret@localhost:5432/database?sslmode=disable")
if err != nil {
log.Fatal(err)
}
// Migrate all the way up ...
if err := m.Up(); err != nil {
log.Fatal(err)
}
}
func TestNewWithInstance(t *testing.T) {
dummyDb := &DummyInstance{"database"}
dbInst, err := dStub.WithInstance(dummyDb, &dStub.Config{})
if err != nil {
t.Fatal(err)
}
dummySource := &DummyInstance{"source"}
sInst, err := sStub.WithInstance(dummySource, &sStub.Config{})
if err != nil {
t.Fatal(err)
}
m, err := NewWithInstance("stub", sInst, "stub", dbInst)
if err != nil {
t.Fatal(err)
}
if m.sourceName != "stub" {
t.Errorf("expected stub, got %v", m.sourceName)
}
if m.sourceDrv == nil {
t.Error("expected sourceDrv not to be nil")
}
if m.databaseName != "stub" {
t.Errorf("expected stub, got %v", m.databaseName)
}
if m.databaseDrv == nil {
t.Error("expected databaseDrv not to be nil")
}
}
func ExampleNewWithInstance() {
// See NewWithDatabaseInstance and NewWithSourceInstance for an example.
}
func TestClose(t *testing.T) {
m, _ := New("stub://", "stub://")
sourceErr, databaseErr := m.Close()
if sourceErr != nil {
t.Error(sourceErr)
}
if databaseErr != nil {
t.Error(databaseErr)
}
}
func TestMigrate(t *testing.T) {
m, _ := New("stub://", "stub://")
m.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations
dbDrv := m.databaseDrv.(*dStub.Stub)
tt := []struct {
version uint
expectErr error
expectVersion uint
expectSeq migrationSequence
}{
// migrate all the way Up in single steps
{
version: 0,
expectErr: os.ErrNotExist,
},
{
version: 1,
expectVersion: 1,
expectSeq: migrationSequence{
mr("CREATE 1"),
},
},
{
version: 2,
expectErr: os.ErrNotExist,
expectSeq: migrationSequence{
mr("CREATE 1"),
},
},
{
version: 3,
expectVersion: 3,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
},
},
{
version: 4,
expectVersion: 4,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
},
},
{
version: 5,
expectVersion: 5,
expectSeq: migrationSequence{ // 5 has no up migration
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
},
},
{
version: 6,
expectErr: os.ErrNotExist,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
},
},
{
version: 7,
expectVersion: 7,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
},
},
{
version: 8,
expectErr: os.ErrNotExist,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
},
},
// migrate all the way Down in single steps
{
version: 6,
expectErr: os.ErrNotExist,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
},
},
{
version: 5,
expectVersion: 5,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
},
},
{
version: 4,
expectVersion: 4,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
},
},
{
version: 3,
expectVersion: 3,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
},
},
{
version: 2,
expectErr: os.ErrNotExist,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
},
},
{
version: 1,
expectVersion: 1,
expectSeq: migrationSequence{ // 3 has no down migration
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
},
},
{
version: 0,
expectErr: os.ErrNotExist,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
},
},
// migrate all the way Up in one step
{
version: 7,
expectVersion: 7,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
},
},
// migrate all the way Down in one step
{
version: 1,
expectVersion: 1,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
},
},
// can't migrate the same version twice
{
version: 1,
expectErr: ErrNoChange,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
},
},
}
for i, v := range tt {
err := m.Migrate(v.version)
if (v.expectErr == os.ErrNotExist && !os.IsNotExist(err)) ||
(v.expectErr != os.ErrNotExist && err != v.expectErr) {
t.Errorf("expected err %v, got %v, in %v", v.expectErr, err, i)
} else if err == nil {
version, _, err := m.Version()
if err != nil {
t.Error(err)
}
if version != v.expectVersion {
t.Errorf("expected version %v, got %v, in %v", v.expectVersion, version, i)
}
}
equalDbSeq(t, i, v.expectSeq, dbDrv)
}
}
func TestMigrateDirty(t *testing.T) {
m, _ := New("stub://", "stub://")
dbDrv := m.databaseDrv.(*dStub.Stub)
if err := dbDrv.SetVersion(0, true); err != nil {
t.Fatal(err)
}
err := m.Migrate(1)
if _, ok := err.(ErrDirty); !ok {
t.Fatalf("expected ErrDirty, got %v", err)
}
}
func TestSteps(t *testing.T) {
m, _ := New("stub://", "stub://")
m.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations
dbDrv := m.databaseDrv.(*dStub.Stub)
tt := []struct {
steps int
expectErr error
expectVersion int
expectSeq migrationSequence
}{
// step must be != 0
{
steps: 0,
expectErr: ErrNoChange,
},
// can't go Down if ErrNilVersion
{
steps: -1,
expectErr: os.ErrNotExist,
},
// migrate all the way Up
{
steps: 1,
expectVersion: 1,
expectSeq: migrationSequence{
mr("CREATE 1")},
},
{
steps: 1,
expectVersion: 3,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
},
},
{
steps: 1,
expectVersion: 4,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
},
},
{
steps: 1,
expectVersion: 5,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
},
},
{
steps: 1,
expectVersion: 7,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
},
},
{
steps: 1,
expectErr: os.ErrNotExist,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
},
},
// migrate all the way Down
{
steps: -1,
expectVersion: 5,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
},
},
{
steps: -1,
expectVersion: 4,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
},
},
{
steps: -1,
expectVersion: 3,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
},
},
{
steps: -1,
expectVersion: 1,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
},
},
{
steps: -1,
expectVersion: -1,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("DROP 1"),
},
},
// migrate Up in bigger step
{
steps: 4,
expectVersion: 5,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("DROP 1"),
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
},
},
// apply one migration, then reaches out of boundary
{
steps: 2,
expectErr: ErrShortLimit{1},
expectVersion: 7,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("DROP 1"),
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
},
},
// migrate Down in bigger step
{
steps: -4,
expectVersion: 1,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("DROP 1"),
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
},
},
// apply one migration, then reaches out of boundary
{
steps: -2,
expectErr: ErrShortLimit{1},
expectVersion: -1,
expectSeq: migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("DROP 1"),
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("DROP 1"),
},
},
}
for i, v := range tt {
err := m.Steps(v.steps)
if (v.expectErr == os.ErrNotExist && !os.IsNotExist(err)) ||
(v.expectErr != os.ErrNotExist && err != v.expectErr) {
t.Errorf("expected err %v, got %v, in %v", v.expectErr, err, i)
} else if err == nil {
version, _, err := m.Version()
if err != ErrNilVersion && err != nil {
t.Error(err)
}
if v.expectVersion == -1 && err != ErrNilVersion {
t.Errorf("expected ErrNilVersion, got %v, in %v", version, i)
} else if v.expectVersion >= 0 && version != uint(v.expectVersion) {
t.Errorf("expected version %v, got %v, in %v", v.expectVersion, version, i)
}
}
equalDbSeq(t, i, v.expectSeq, dbDrv)
}
}
func TestStepsDirty(t *testing.T) {
m, _ := New("stub://", "stub://")
dbDrv := m.databaseDrv.(*dStub.Stub)
if err := dbDrv.SetVersion(0, true); err != nil {
t.Fatal(err)
}
err := m.Steps(1)
if _, ok := err.(ErrDirty); !ok {
t.Fatalf("expected ErrDirty, got %v", err)
}
}
func TestUpAndDown(t *testing.T) {
m, _ := New("stub://", "stub://")
m.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations
dbDrv := m.databaseDrv.(*dStub.Stub)
// go Up first
if err := m.Up(); err != nil {
t.Fatal(err)
}
expectedSequence := migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
}
equalDbSeq(t, 0, expectedSequence, dbDrv)
// go Down
if err := m.Down(); err != nil {
t.Fatal(err)
}
expectedSequence = migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("DROP 1"),
}
equalDbSeq(t, 1, expectedSequence, dbDrv)
// go 1 Up and then all the way Up
if err := m.Steps(1); err != nil {
t.Fatal(err)
}
expectedSequence = migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("DROP 1"),
mr("CREATE 1"),
}
equalDbSeq(t, 2, expectedSequence, dbDrv)
if err := m.Up(); err != nil {
t.Fatal(err)
}
expectedSequence = migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("DROP 1"),
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
}
equalDbSeq(t, 3, expectedSequence, dbDrv)
// go 1 Down and then all the way Down
if err := m.Steps(-1); err != nil {
t.Fatal(err)
}
expectedSequence = migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("DROP 1"),
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
}
equalDbSeq(t, 1, expectedSequence, dbDrv)
if err := m.Down(); err != nil {
t.Fatal(err)
}
expectedSequence = migrationSequence{
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("DROP 1"),
mr("CREATE 1"),
mr("CREATE 3"),
mr("CREATE 4"),
mr("CREATE 7"),
mr("DROP 7"),
mr("DROP 5"),
mr("DROP 4"),
mr("DROP 1"),
}
equalDbSeq(t, 1, expectedSequence, dbDrv)
}
func TestUpDirty(t *testing.T) {
m, _ := New("stub://", "stub://")
dbDrv := m.databaseDrv.(*dStub.Stub)
if err := dbDrv.SetVersion(0, true); err != nil {
t.Fatal(err)
}
err := m.Up()
if _, ok := err.(ErrDirty); !ok {
t.Fatalf("expected ErrDirty, got %v", err)
}
}
func TestDownDirty(t *testing.T) {
m, _ := New("stub://", "stub://")
dbDrv := m.databaseDrv.(*dStub.Stub)
if err := dbDrv.SetVersion(0, true); err != nil {
t.Fatal(err)
}
err := m.Down()
if _, ok := err.(ErrDirty); !ok {
t.Fatalf("expected ErrDirty, got %v", err)
}
}
func TestDrop(t *testing.T) {
m, _ := New("stub://", "stub://")
m.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations
dbDrv := m.databaseDrv.(*dStub.Stub)
if err := m.Drop(); err != nil {
t.Fatal(err)
}
if dbDrv.MigrationSequence[len(dbDrv.MigrationSequence)-1] != dStub.DROP {
t.Fatalf("expected database to DROP, got sequence %v", dbDrv.MigrationSequence)
}
}
func TestVersion(t *testing.T) {
m, _ := New("stub://", "stub://")
dbDrv := m.databaseDrv.(*dStub.Stub)
_, _, err := m.Version()
if err != ErrNilVersion {
t.Fatalf("expected ErrNilVersion, got %v", err)
}
if err := dbDrv.Run(bytes.NewBufferString("1_up")); err != nil {
t.Fatal(err)
}
if err := dbDrv.SetVersion(1, false); err != nil {
t.Fatal(err)
}
v, _, err := m.Version()
if err != nil {
t.Fatal(err)
}
if v != 1 {
t.Fatalf("expected version 1, got %v", v)
}
}
func TestRun(t *testing.T) {
m, _ := New("stub://", "stub://")
mx, err := NewMigration(nil, "", 1, 2)
if err != nil {
t.Fatal(err)
}
if err := m.Run(mx); err != nil {
t.Fatal(err)
}
v, _, err := m.Version()
if err != nil {
t.Fatal(err)
}
if v != 2 {
t.Errorf("expected version 2, got %v", v)
}
}
func TestRunDirty(t *testing.T) {
m, _ := New("stub://", "stub://")
dbDrv := m.databaseDrv.(*dStub.Stub)
if err := dbDrv.SetVersion(0, true); err != nil {
t.Fatal(err)
}
migr, err := NewMigration(nil, "", 1, 2)
if err != nil {
t.Fatal(err)
}
err = m.Run(migr)
if _, ok := err.(ErrDirty); !ok {
t.Fatalf("expected ErrDirty, got %v", err)
}
}
func TestForce(t *testing.T) {
m, _ := New("stub://", "stub://")
m.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations
if err := m.Force(7); err != nil {
t.Fatal(err)
}
v, dirty, err := m.Version()
if err != nil {
t.Fatal(err)
}
if dirty {
t.Errorf("expected dirty to be false")
}
if v != 7 {
t.Errorf("expected version to be 7")
}
}
func TestForceDirty(t *testing.T) {
m, _ := New("stub://", "stub://")
dbDrv := m.databaseDrv.(*dStub.Stub)
if err := dbDrv.SetVersion(0, true); err != nil {
t.Fatal(err)
}
if err := m.Force(1); err != nil {
t.Fatal(err)
}
}
func TestRead(t *testing.T) {
m, _ := New("stub://", "stub://")
m.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations
tt := []struct {
from int
to int
expectErr error
expectMigrations migrationSequence
}{
{from: -1, to: -1, expectErr: ErrNoChange},
{from: -1, to: 0, expectErr: os.ErrNotExist},
{from: -1, to: 1, expectErr: nil, expectMigrations: newMigSeq(M(1))},
{from: -1, to: 2, expectErr: os.ErrNotExist},
{from: -1, to: 3, expectErr: nil, expectMigrations: newMigSeq(M(1), M(3))},
{from: -1, to: 4, expectErr: nil, expectMigrations: newMigSeq(M(1), M(3), M(4))},
{from: -1, to: 5, expectErr: nil, expectMigrations: newMigSeq(M(1), M(3), M(4), M(5))},
{from: -1, to: 6, expectErr: os.ErrNotExist},
{from: -1, to: 7, expectErr: nil, expectMigrations: newMigSeq(M(1), M(3), M(4), M(5), M(7))},
{from: -1, to: 8, expectErr: os.ErrNotExist},
{from: 0, to: -1, expectErr: os.ErrNotExist},
{from: 0, to: 0, expectErr: os.ErrNotExist},
{from: 0, to: 1, expectErr: os.ErrNotExist},
{from: 0, to: 2, expectErr: os.ErrNotExist},
{from: 0, to: 3, expectErr: os.ErrNotExist},
{from: 0, to: 4, expectErr: os.ErrNotExist},
{from: 0, to: 5, expectErr: os.ErrNotExist},
{from: 0, to: 6, expectErr: os.ErrNotExist},
{from: 0, to: 7, expectErr: os.ErrNotExist},
{from: 0, to: 8, expectErr: os.ErrNotExist},
{from: 1, to: -1, expectErr: nil, expectMigrations: newMigSeq(M(1, -1))},
{from: 1, to: 0, expectErr: os.ErrNotExist},
{from: 1, to: 1, expectErr: ErrNoChange},
{from: 1, to: 2, expectErr: os.ErrNotExist},
{from: 1, to: 3, expectErr: nil, expectMigrations: newMigSeq(M(3))},
{from: 1, to: 4, expectErr: nil, expectMigrations: newMigSeq(M(3), M(4))},
{from: 1, to: 5, expectErr: nil, expectMigrations: newMigSeq(M(3), M(4), M(5))},
{from: 1, to: 6, expectErr: os.ErrNotExist},
{from: 1, to: 7, expectErr: nil, expectMigrations: newMigSeq(M(3), M(4), M(5), M(7))},
{from: 1, to: 8, expectErr: os.ErrNotExist},
{from: 2, to: -1, expectErr: os.ErrNotExist},
{from: 2, to: 0, expectErr: os.ErrNotExist},
{from: 2, to: 1, expectErr: os.ErrNotExist},
{from: 2, to: 2, expectErr: os.ErrNotExist},
{from: 2, to: 3, expectErr: os.ErrNotExist},
{from: 2, to: 4, expectErr: os.ErrNotExist},
{from: 2, to: 5, expectErr: os.ErrNotExist},
{from: 2, to: 6, expectErr: os.ErrNotExist},
{from: 2, to: 7, expectErr: os.ErrNotExist},
{from: 2, to: 8, expectErr: os.ErrNotExist},
{from: 3, to: -1, expectErr: nil, expectMigrations: newMigSeq(M(3, 1), M(1, -1))},
{from: 3, to: 0, expectErr: os.ErrNotExist},
{from: 3, to: 1, expectErr: nil, expectMigrations: newMigSeq(M(3, 1))},
{from: 3, to: 2, expectErr: os.ErrNotExist},
{from: 3, to: 3, expectErr: ErrNoChange},
{from: 3, to: 4, expectErr: nil, expectMigrations: newMigSeq(M(4))},
{from: 3, to: 5, expectErr: nil, expectMigrations: newMigSeq(M(4), M(5))},
{from: 3, to: 6, expectErr: os.ErrNotExist},
{from: 3, to: 7, expectErr: nil, expectMigrations: newMigSeq(M(4), M(5), M(7))},
{from: 3, to: 8, expectErr: os.ErrNotExist},
{from: 4, to: -1, expectErr: nil, expectMigrations: newMigSeq(M(4, 3), M(3, 1), M(1, -1))},
{from: 4, to: 0, expectErr: os.ErrNotExist},
{from: 4, to: 1, expectErr: nil, expectMigrations: newMigSeq(M(4, 3), M(3, 1))},
{from: 4, to: 2, expectErr: os.ErrNotExist},
{from: 4, to: 3, expectErr: nil, expectMigrations: newMigSeq(M(4, 3))},
{from: 4, to: 4, expectErr: ErrNoChange},
{from: 4, to: 5, expectErr: nil, expectMigrations: newMigSeq(M(5))},
{from: 4, to: 6, expectErr: os.ErrNotExist},
{from: 4, to: 7, expectErr: nil, expectMigrations: newMigSeq(M(5), M(7))},
{from: 4, to: 8, expectErr: os.ErrNotExist},
{from: 5, to: -1, expectErr: nil, expectMigrations: newMigSeq(M(5, 4), M(4, 3), M(3, 1), M(1, -1))},
{from: 5, to: 0, expectErr: os.ErrNotExist},
{from: 5, to: 1, expectErr: nil, expectMigrations: newMigSeq(M(5, 4), M(4, 3), M(3, 1))},
{from: 5, to: 2, expectErr: os.ErrNotExist},
{from: 5, to: 3, expectErr: nil, expectMigrations: newMigSeq(M(5, 4), M(4, 3))},
{from: 5, to: 4, expectErr: nil, expectMigrations: newMigSeq(M(5, 4))},
{from: 5, to: 5, expectErr: ErrNoChange},
{from: 5, to: 6, expectErr: os.ErrNotExist},
{from: 5, to: 7, expectErr: nil, expectMigrations: newMigSeq(M(7))},
{from: 5, to: 8, expectErr: os.ErrNotExist},
{from: 6, to: -1, expectErr: os.ErrNotExist},
{from: 6, to: 0, expectErr: os.ErrNotExist},
{from: 6, to: 1, expectErr: os.ErrNotExist},
{from: 6, to: 2, expectErr: os.ErrNotExist},
{from: 6, to: 3, expectErr: os.ErrNotExist},
{from: 6, to: 4, expectErr: os.ErrNotExist},
{from: 6, to: 5, expectErr: os.ErrNotExist},
{from: 6, to: 6, expectErr: os.ErrNotExist},
{from: 6, to: 7, expectErr: os.ErrNotExist},
{from: 6, to: 8, expectErr: os.ErrNotExist},
{from: 7, to: -1, expectErr: nil, expectMigrations: newMigSeq(M(7, 5), M(5, 4), M(4, 3), M(3, 1), M(1, -1))},
{from: 7, to: 0, expectErr: os.ErrNotExist},
{from: 7, to: 1, expectErr: nil, expectMigrations: newMigSeq(M(7, 5), M(5, 4), M(4, 3), M(3, 1))},
{from: 7, to: 2, expectErr: os.ErrNotExist},
{from: 7, to: 3, expectErr: nil, expectMigrations: newMigSeq(M(7, 5), M(5, 4), M(4, 3))},
{from: 7, to: 4, expectErr: nil, expectMigrations: newMigSeq(M(7, 5), M(5, 4))},
{from: 7, to: 5, expectErr: nil, expectMigrations: newMigSeq(M(7, 5))},
{from: 7, to: 6, expectErr: os.ErrNotExist},
{from: 7, to: 7, expectErr: ErrNoChange},
{from: 7, to: 8, expectErr: os.ErrNotExist},
{from: 8, to: -1, expectErr: os.ErrNotExist},
{from: 8, to: 0, expectErr: os.ErrNotExist},
{from: 8, to: 1, expectErr: os.ErrNotExist},
{from: 8, to: 2, expectErr: os.ErrNotExist},
{from: 8, to: 3, expectErr: os.ErrNotExist},
{from: 8, to: 4, expectErr: os.ErrNotExist},
{from: 8, to: 5, expectErr: os.ErrNotExist},
{from: 8, to: 6, expectErr: os.ErrNotExist},
{from: 8, to: 7, expectErr: os.ErrNotExist},
{from: 8, to: 8, expectErr: os.ErrNotExist},
}
for i, v := range tt {
ret := make(chan interface{})
go m.read(v.from, v.to, ret)
migrations, err := migrationsFromChannel(ret)
if (v.expectErr == os.ErrNotExist && !os.IsNotExist(err)) ||
(v.expectErr != os.ErrNotExist && v.expectErr != err) {
t.Errorf("expected %v, got %v, in %v", v.expectErr, err, i)
t.Logf("%v, in %v", migrations, i)
}
if len(v.expectMigrations) > 0 {
equalMigSeq(t, i, v.expectMigrations, migrations)
}
}
}
func TestReadUp(t *testing.T) {
m, _ := New("stub://", "stub://")
m.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations
tt := []struct {
from int
limit int // -1 means no limit
expectErr error
expectMigrations migrationSequence
}{
{from: -1, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(1), M(3), M(4), M(5), M(7))},
{from: -1, limit: 0, expectErr: ErrNoChange},
{from: -1, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(1))},
{from: -1, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(1), M(3))},
{from: 0, limit: -1, expectErr: os.ErrNotExist},
{from: 0, limit: 0, expectErr: os.ErrNotExist},
{from: 0, limit: 1, expectErr: os.ErrNotExist},
{from: 0, limit: 2, expectErr: os.ErrNotExist},
{from: 1, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(3), M(4), M(5), M(7))},
{from: 1, limit: 0, expectErr: ErrNoChange},
{from: 1, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(3))},
{from: 1, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(3), M(4))},
{from: 2, limit: -1, expectErr: os.ErrNotExist},
{from: 2, limit: 0, expectErr: os.ErrNotExist},
{from: 2, limit: 1, expectErr: os.ErrNotExist},
{from: 2, limit: 2, expectErr: os.ErrNotExist},
{from: 3, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(4), M(5), M(7))},
{from: 3, limit: 0, expectErr: ErrNoChange},
{from: 3, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(4))},
{from: 3, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(4), M(5))},
{from: 4, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(5), M(7))},
{from: 4, limit: 0, expectErr: ErrNoChange},
{from: 4, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(5))},
{from: 4, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(5), M(7))},
{from: 5, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(7))},
{from: 5, limit: 0, expectErr: ErrNoChange},
{from: 5, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(7))},
{from: 5, limit: 2, expectErr: ErrShortLimit{1}, expectMigrations: newMigSeq(M(7))},
{from: 6, limit: -1, expectErr: os.ErrNotExist},
{from: 6, limit: 0, expectErr: os.ErrNotExist},
{from: 6, limit: 1, expectErr: os.ErrNotExist},
{from: 6, limit: 2, expectErr: os.ErrNotExist},
{from: 7, limit: -1, expectErr: ErrNoChange},
{from: 7, limit: 0, expectErr: ErrNoChange},
{from: 7, limit: 1, expectErr: os.ErrNotExist},
{from: 7, limit: 2, expectErr: os.ErrNotExist},
{from: 8, limit: -1, expectErr: os.ErrNotExist},
{from: 8, limit: 0, expectErr: os.ErrNotExist},
{from: 8, limit: 1, expectErr: os.ErrNotExist},
{from: 8, limit: 2, expectErr: os.ErrNotExist},
}
for i, v := range tt {
ret := make(chan interface{})
go m.readUp(v.from, v.limit, ret)
migrations, err := migrationsFromChannel(ret)
if (v.expectErr == os.ErrNotExist && !os.IsNotExist(err)) ||
(v.expectErr != os.ErrNotExist && v.expectErr != err) {
t.Errorf("expected %v, got %v, in %v", v.expectErr, err, i)
t.Logf("%v, in %v", migrations, i)
}
if len(v.expectMigrations) > 0 {
equalMigSeq(t, i, v.expectMigrations, migrations)
}
}
}
func TestReadDown(t *testing.T) {
m, _ := New("stub://", "stub://")
m.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations
tt := []struct {
from int
limit int // -1 means no limit
expectErr error
expectMigrations migrationSequence
}{
{from: -1, limit: -1, expectErr: ErrNoChange},
{from: -1, limit: 0, expectErr: ErrNoChange},
{from: -1, limit: 1, expectErr: os.ErrNotExist},
{from: -1, limit: 2, expectErr: os.ErrNotExist},
{from: 0, limit: -1, expectErr: os.ErrNotExist},
{from: 0, limit: 0, expectErr: os.ErrNotExist},
{from: 0, limit: 1, expectErr: os.ErrNotExist},
{from: 0, limit: 2, expectErr: os.ErrNotExist},
{from: 1, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(1, -1))},
{from: 1, limit: 0, expectErr: ErrNoChange},
{from: 1, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(1, -1))},
{from: 1, limit: 2, expectErr: ErrShortLimit{1}, expectMigrations: newMigSeq(M(1, -1))},
{from: 2, limit: -1, expectErr: os.ErrNotExist},
{from: 2, limit: 0, expectErr: os.ErrNotExist},
{from: 2, limit: 1, expectErr: os.ErrNotExist},
{from: 2, limit: 2, expectErr: os.ErrNotExist},
{from: 3, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(3, 1), M(1, -1))},
{from: 3, limit: 0, expectErr: ErrNoChange},
{from: 3, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(3, 1))},
{from: 3, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(3, 1), M(1, -1))},
{from: 4, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(4, 3), M(3, 1), M(1, -1))},
{from: 4, limit: 0, expectErr: ErrNoChange},
{from: 4, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(4, 3))},
{from: 4, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(4, 3), M(3, 1))},
{from: 5, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(5, 4), M(4, 3), M(3, 1), M(1, -1))},
{from: 5, limit: 0, expectErr: ErrNoChange},
{from: 5, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(5, 4))},
{from: 5, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(5, 4), M(4, 3))},
{from: 6, limit: -1, expectErr: os.ErrNotExist},
{from: 6, limit: 0, expectErr: os.ErrNotExist},
{from: 6, limit: 1, expectErr: os.ErrNotExist},
{from: 6, limit: 2, expectErr: os.ErrNotExist},
{from: 7, limit: -1, expectErr: nil, expectMigrations: newMigSeq(M(7, 5), M(5, 4), M(4, 3), M(3, 1), M(1, -1))},
{from: 7, limit: 0, expectErr: ErrNoChange},
{from: 7, limit: 1, expectErr: nil, expectMigrations: newMigSeq(M(7, 5))},
{from: 7, limit: 2, expectErr: nil, expectMigrations: newMigSeq(M(7, 5), M(5, 4))},
{from: 8, limit: -1, expectErr: os.ErrNotExist},
{from: 8, limit: 0, expectErr: os.ErrNotExist},
{from: 8, limit: 1, expectErr: os.ErrNotExist},
{from: 8, limit: 2, expectErr: os.ErrNotExist},
}
for i, v := range tt {
ret := make(chan interface{})
go m.readDown(v.from, v.limit, ret)
migrations, err := migrationsFromChannel(ret)
if (v.expectErr == os.ErrNotExist && !os.IsNotExist(err)) ||
(v.expectErr != os.ErrNotExist && v.expectErr != err) {
t.Errorf("expected %v, got %v, in %v", v.expectErr, err, i)
t.Logf("%v, in %v", migrations, i)
}
if len(v.expectMigrations) > 0 {
equalMigSeq(t, i, v.expectMigrations, migrations)
}
}
}
func TestLock(t *testing.T) {
m, _ := New("stub://", "stub://")
if err := m.lock(); err != nil {
t.Fatal(err)
}
if err := m.lock(); err == nil {
t.Fatal("should be locked already")
}
}
func migrationsFromChannel(ret chan interface{}) ([]*Migration, error) {
slice := make([]*Migration, 0)
for r := range ret {
switch r.(type) {
case error:
return slice, r.(error)
case *Migration:
slice = append(slice, r.(*Migration))
}
}
return slice, nil
}
type migrationSequence []*Migration
func newMigSeq(migr ...*Migration) migrationSequence {
return migr
}
func (m *migrationSequence) add(migr ...*Migration) migrationSequence {
*m = append(*m, migr...)
return *m
}
func (m *migrationSequence) bodySequence() []string {
r := make([]string, 0)
for _, v := range *m {
if v.Body != nil {
body, err := ioutil.ReadAll(v.Body)
if err != nil {
panic(err) // that should never happen
}
// reset body reader
// TODO: is there a better/nicer way?
v.Body = ioutil.NopCloser(bytes.NewReader(body))
r = append(r, string(body[:]))
} else {
r = append(r, "<empty>")
}
}
return r
}
// M is a convenience func to create a new *Migration
func M(version uint, targetVersion ...int) *Migration {
if len(targetVersion) > 1 {
panic("only one targetVersion allowed")
}
ts := int(version)
if len(targetVersion) == 1 {
ts = targetVersion[0]
}
m, _ := New("stub://", "stub://")
m.sourceDrv.(*sStub.Stub).Migrations = sourceStubMigrations
migr, err := m.newMigration(version, ts)
if err != nil {
panic(err)
}
return migr
}
// mr is a convenience func to create a new *Migration from the raw database query
func mr(value string) *Migration {
return &Migration{
Body: ioutil.NopCloser(strings.NewReader(value)),
}
}
func equalMigSeq(t *testing.T, i int, expected, got migrationSequence) {
if len(expected) != len(got) {
t.Errorf("expected migrations %v, got %v, in %v", expected, got, i)
} else {
for ii := 0; ii < len(expected); ii++ {
if expected[ii].Version != got[ii].Version {
t.Errorf("expected version %v, got %v, in %v", expected[ii].Version, got[ii].Version, i)
}
if expected[ii].TargetVersion != got[ii].TargetVersion {
t.Errorf("expected targetVersion %v, got %v, in %v", expected[ii].TargetVersion, got[ii].TargetVersion, i)
}
}
}
}
func equalDbSeq(t *testing.T, i int, expected migrationSequence, got *dStub.Stub) {
bs := expected.bodySequence()
if !got.EqualSequence(bs) {
t.Fatalf("\nexpected sequence %v,\ngot %v, in %v", bs, got.MigrationSequence, i)
}
}