package goose_test import ( "fmt" "os" "os/exec" "path/filepath" "strconv" "testing" "time" "github.com/stretchr/testify/require" _ "modernc.org/sqlite" ) const ( // gooseTestBinaryVersion is utilized in conjunction with a linker variable to set the version // of a binary created solely for testing purposes. It is used to test the --version flag. gooseTestBinaryVersion = "v0.0.0" ) func TestFullBinary(t *testing.T) { t.Parallel() cli := buildGooseCLI(t, false) out, err := cli.run("--version") require.NoError(t, err) require.Equal(t, "goose version: "+gooseTestBinaryVersion+"\n", out) } func TestLiteBinary(t *testing.T) { t.Parallel() cli := buildGooseCLI(t, true) t.Run("binary_version", func(t *testing.T) { t.Parallel() out, err := cli.run("--version") require.NoError(t, err) require.Equal(t, "goose version: "+gooseTestBinaryVersion+"\n", out) }) t.Run("default_binary", func(t *testing.T) { t.Parallel() dir := t.TempDir() total := countSQLFiles(t, "testdata/migrations") commands := []struct { cmd string out string }{ {"up", "goose: successfully migrated database to version: " + strconv.Itoa(total)}, {"version", "goose: version " + strconv.Itoa(total)}, {"down", "OK"}, {"version", "goose: version " + strconv.Itoa(total-1)}, {"status", ""}, {"reset", "OK"}, {"version", "goose: version 0"}, } for _, c := range commands { out, err := cli.run("-dir=testdata/migrations", "sqlite3", filepath.Join(dir, "sql.db"), c.cmd) require.NoError(t, err) require.Contains(t, out, c.out) } }) t.Run("gh_issue_532", func(t *testing.T) { // https://github.com/pressly/goose/issues/532 t.Parallel() dir := t.TempDir() total := countSQLFiles(t, "testdata/migrations") _, err := cli.run("-dir=testdata/migrations", "sqlite3", filepath.Join(dir, "sql.db"), "up") require.NoError(t, err) out, err := cli.run("-dir=testdata/migrations", "sqlite3", filepath.Join(dir, "sql.db"), "up") require.NoError(t, err) require.Contains(t, out, "goose: no migrations to run. current version: "+strconv.Itoa(total)) out, err = cli.run("-dir=testdata/migrations", "sqlite3", filepath.Join(dir, "sql.db"), "version") require.NoError(t, err) require.Contains(t, out, "goose: version "+strconv.Itoa(total)) }) t.Run("gh_issue_293", func(t *testing.T) { // https://github.com/pressly/goose/issues/293 t.Parallel() dir := t.TempDir() total := countSQLFiles(t, "testdata/migrations") commands := []struct { cmd string out string }{ {"up", "goose: successfully migrated database to version: " + strconv.Itoa(total)}, {"version", "goose: version " + strconv.Itoa(total)}, {"down", "OK"}, {"down", "OK"}, {"version", "goose: version " + strconv.Itoa(total-2)}, {"up", "goose: successfully migrated database to version: " + strconv.Itoa(total)}, {"status", ""}, } for _, c := range commands { out, err := cli.run("-dir=testdata/migrations", "sqlite3", filepath.Join(dir, "sql.db"), c.cmd) require.NoError(t, err) require.Contains(t, out, c.out) } }) t.Run("gh_issue_336", func(t *testing.T) { // https://github.com/pressly/goose/issues/336 t.Parallel() dir := t.TempDir() _, err := cli.run("-dir="+dir, "sqlite3", filepath.Join(dir, "sql.db"), "up") require.Error(t, err) require.Contains(t, err.Error(), "goose run: no migration files found") }) t.Run("create_and_fix", func(t *testing.T) { t.Parallel() dir := t.TempDir() createEmptyFile(t, dir, "00001_alpha.sql") createEmptyFile(t, dir, "00003_bravo.sql") createEmptyFile(t, dir, "20230826163141_charlie.sql") createEmptyFile(t, dir, "20230826163151_delta.go") total, err := os.ReadDir(dir) require.NoError(t, err) require.Len(t, total, 4) migrationFiles := []struct { name string fileType string }{ {"echo", "sql"}, {"foxtrot", "go"}, {"golf", ""}, } for i, f := range migrationFiles { args := []string{"-dir=" + dir, "create", f.name} if f.fileType != "" { args = append(args, f.fileType) } out, err := cli.run(args...) require.NoError(t, err) require.Contains(t, out, "Created new file") // ensure different timestamps, granularity is 1 second if i < len(migrationFiles)-1 { time.Sleep(1100 * time.Millisecond) } } total, err = os.ReadDir(dir) require.NoError(t, err) require.Len(t, total, 7) out, err := cli.run("-dir="+dir, "fix") require.NoError(t, err) require.Contains(t, out, "RENAMED") files, err := os.ReadDir(dir) require.NoError(t, err) require.Len(t, files, 7) expected := []string{ "00001_alpha.sql", "00003_bravo.sql", "00004_charlie.sql", "00005_delta.go", "00006_echo.sql", "00007_foxtrot.go", "00008_golf.go", } for i, f := range files { require.Equal(t, f.Name(), expected[i]) } }) } type gooseBinary struct { binaryPath string } func (g gooseBinary) run(params ...string) (string, error) { cmd := exec.Command(g.binaryPath, params...) out, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("failed to run goose command: %v\nout: %v", err, string(out)) } return string(out), nil } // buildGooseCLI builds goose test binary, which is used for testing goose CLI. It is built with all // drivers enabled, unless lite is true, in which case all drivers are disabled except sqlite3 func buildGooseCLI(t *testing.T, lite bool) gooseBinary { t.Helper() binName := "goose-test" dir := t.TempDir() output := filepath.Join(dir, binName) // usage: go build [-o output] [build flags] [packages] args := []string{ "build", "-o", output, "-ldflags=-s -w -X main.version=" + gooseTestBinaryVersion, } if lite { args = append(args, "-tags=no_clickhouse no_mssql no_mysql no_vertica no_postgres") } args = append(args, "./cmd/goose") build := exec.Command("go", args...) out, err := build.CombinedOutput() if err != nil { t.Fatalf("failed to build %s binary: %v: %s", binName, err, string(out)) } return gooseBinary{ binaryPath: output, } } func countSQLFiles(t *testing.T, dir string) int { t.Helper() files, err := filepath.Glob(filepath.Join(dir, "*.sql")) require.NoError(t, err) return len(files) } func createEmptyFile(t *testing.T, dir, name string) { t.Helper() path := filepath.Join(dir, name) f, err := os.Create(path) require.NoError(t, err) defer f.Close() }