func TestSchemaDump(t *testing.T) {
	schema, db := newSchemaAndDB(t)
	schema.Add(updateCreateTable)
	schema.Add(updateAddColumn)
	_, err := schema.Ensure(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	dump, err := schema.Dump(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	_, db = newSchemaAndDB(t)
	schema.Fresh(dump)
	_, err = schema.Ensure(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	tx, err := db.Begin()
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	versions, err := query.SelectIntegers(tx, "SELECT version FROM schema")
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	if expected, actual := []int{2}, versions; !reflect.DeepEqual(expected, actual) {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
	_, err = tx.Exec("SELECT id, name FROM test")
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
}
The SQL text returns by Dump() can be used to create the schema from
scratch, without applying each individual update.
package schema_test
import (
	"database/sql"
	"reflect"
	"testing"
	"github.com/pkg/errors"
	"github.com/spoke-d/thermionic/internal/db/database"
	"github.com/spoke-d/thermionic/internal/db/query"
	"github.com/spoke-d/thermionic/internal/db/schema"
	"github.com/spoke-d/thermionic/internal/fsys"
)
func TestNewFromMap(t *testing.T) {
	db := newDB(t)
	fs := newFileSystem(t)
	s := schema.New(fs, []schema.Update{
		updateCreateTable,
		updateInsertValue,
	})
	initial, err := s.Ensure(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	if expected, actual := 0, initial; expected != actual {
		t.Errorf("expected: %d, actual: %d", expected, actual)
	}
}
func TestSchemaEnsure_VersionMostRecentThanExpected(t *testing.T) {
	schema, db := newSchemaAndDB(t)
	schema.Add(updateNoop)
	_, err := schema.Ensure(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	schema, _ = newSchemaAndDB(t)
	_, err = schema.Ensure(db)
	if err == nil {
		t.Errorf("expected err not to be nil")
	}
	if expected, actual := "schema version '1' is more recent than expected '0'", errors.Cause(err).Error(); expected != actual {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
}
func TestSchemaEnsure_FreshStatementError(t *testing.T) {
	schema, db := newSchemaAndDB(t)
	schema.Add(updateNoop)
	schema.Fresh("garbage")
	_, err := schema.Ensure(db)
	if err == nil {
		t.Errorf("expected err not to be nil")
	}
	if expected, actual := "cannot apply fresh schema: near \"garbage\": syntax error", err.Error(); expected != actual {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
}
func TestSchemaEnsure_MissingVersion(t *testing.T) {
	schema, db := newSchemaAndDB(t)
	schema.Add(updateNoop)
	_, err := schema.Ensure(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	tx, err := db.Begin()
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	_, err = tx.Exec(`INSERT INTO schema (version, updated_at) VALUES (3, strftime("%s"))`)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	if err := tx.Commit(); err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	schema.Add(updateNoop)
	schema.Add(updateNoop)
	_, err = schema.Ensure(db)
	if err == nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	if expected, actual := "missing updates: 1 -> 3", err.Error(); expected != actual {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
}
func TestSchemaEnsure_ZeroUpdates(t *testing.T) {
	schema, db := newSchemaAndDB(t)
	_, err := schema.Ensure(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	tx, err := db.Begin()
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	versions, err := query.SelectIntegers(tx, "SELECT version FROM SCHEMA")
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	var want []int
	if expected, actual := want, versions; !reflect.DeepEqual(expected, actual) {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
}
func TestSchemaEnsure_ApplyAllUpdates(t *testing.T) {
	schema, db := newSchemaAndDB(t)
	schema.Add(updateCreateTable)
	schema.Add(updateInsertValue)
	initial, err := schema.Ensure(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	if expected, actual := 0, initial; expected != actual {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
	tx, err := db.Begin()
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	versions, err := query.SelectIntegers(tx, "SELECT version FROM SCHEMA")
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	if expected, actual := []int{1, 2}, versions; !reflect.DeepEqual(expected, actual) {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
	ids, err := query.SelectIntegers(tx, "SELECT id FROM test")
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	if expected, actual := []int{1}, ids; !reflect.DeepEqual(expected, actual) {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
}
func TestSchemaEnsure_ApplyAfterInitialDumpCreation(t *testing.T) {
	schema, db := newSchemaAndDB(t)
	schema.Add(updateCreateTable)
	schema.Add(updateAddColumn)
	_, err := schema.Ensure(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	dump, err := schema.Dump(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	_, db = newSchemaAndDB(t)
	schema.Fresh(dump)
	_, err = schema.Ensure(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	schema.Add(updateNoop)
	_, err = schema.Ensure(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	tx, err := db.Begin()
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	versions, err := query.SelectIntegers(tx, "SELECT version FROM schema")
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	if expected, actual := []int{2, 3}, versions; !reflect.DeepEqual(expected, actual) {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
}
func TestSchemaEnsure_OnlyApplyMissing(t *testing.T) {
	schema, db := newSchemaAndDB(t)
	schema.Add(updateCreateTable)
	_, err := schema.Ensure(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	schema.Add(updateInsertValue)
	initial, err := schema.Ensure(db)
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	if expected, actual := 1, initial; expected != actual {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
	tx, err := db.Begin()
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	versions, err := query.SelectIntegers(tx, "SELECT version FROM SCHEMA")
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	if expected, actual := []int{1, 2}, versions; !reflect.DeepEqual(expected, actual) {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
	ids, err := query.SelectIntegers(tx, "SELECT id FROM test")
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	if expected, actual := []int{1}, ids; !reflect.DeepEqual(expected, actual) {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
}
func TestSchemaEnsure_FailingUpdate(t *testing.T) {
	schema, db := newSchemaAndDB(t)
	schema.Add(updateCreateTable)
	schema.Add(updateBoom)
	_, err := schema.Ensure(db)
	if expected, actual := "failed to apply update 1: boom", err.Error(); expected != actual {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
	tx, err := db.Begin()
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	tables, err := query.SelectStrings(tx, "SELECT name FROM sqlite_master WHERE type = 'table'")
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	if expected, actual := "schema", tables; contains(actual, expected) {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
	if expected, actual := "test", tables; contains(actual, expected) {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
}
func TestSchemaEnsure_FailingHook(t *testing.T) {
	schema, db := newSchemaAndDB(t)
	schema.Add(updateCreateTable)
	schema.Hook(func(int, database.Tx) error { return errors.Errorf("boom") })
	_, err := schema.Ensure(db)
	if expected, actual := "failed to execute hook (version 0): boom", err.Error(); expected != actual {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
	tx, err := db.Begin()
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	tables, err := query.SelectStrings(tx, "SELECT name FROM sqlite_master WHERE type = 'table'")
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	if expected, actual := "schema", tables; contains(actual, expected) {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
	if expected, actual := "test", tables; contains(actual, expected) {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
}
func TestSchemaEnsure_CheckGracefulAbort(t *testing.T) {
	check := func(current int, tx database.Tx) error {
		_, err := tx.Exec("CREATE TABLE test (n INTEGER)")
		if err != nil {
			t.Errorf("expected err to be nil: %v", err)
		}
		return schema.ErrGracefulAbort
	}
	schema, db := newSchemaAndDB(t)
	schema.Add(updateCreateTable)
	schema.Check(check)
	_, err := schema.Ensure(db)
	if expected, actual := "schema check gracefully aborted", err.Error(); expected != actual {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
	tx, err := db.Begin()
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	ids, err := query.SelectIntegers(tx, "SELECT n FROM test")
	if err != nil {
		t.Errorf("expected err to be nil: %v", err)
	}
	var want []int
	if expected, actual := want, ids; !reflect.DeepEqual(expected, actual) {
		t.Errorf("expected: %v, actual: %v", expected, actual)
	}
}