Skip to content

Commit

Permalink
test: improve goose cli tests (#589)
Browse files Browse the repository at this point in the history
  • Loading branch information
mfridman committed Aug 27, 2023
1 parent 6c1d92b commit 958c950
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 331 deletions.
214 changes: 214 additions & 0 deletions goose_cli_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package goose_test

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"testing"
"time"

"github.com/pressly/goose/v3/internal/check"
_ "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")
check.NoError(t, err)
check.Equal(t, out, "goose version: "+gooseTestBinaryVersion+"\n")
}

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")
check.NoError(t, err)
check.Equal(t, out, "goose version: "+gooseTestBinaryVersion+"\n")
})
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)
check.NoError(t, err)
check.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")
check.NoError(t, err)
out, err := cli.run("-dir=testdata/migrations", "sqlite3", filepath.Join(dir, "sql.db"), "up")
check.NoError(t, err)
check.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")
check.NoError(t, err)
check.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)
check.NoError(t, err)
check.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")
check.HasError(t, err)
check.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)
check.NoError(t, err)
check.Number(t, len(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...)
check.NoError(t, err)
check.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)
check.NoError(t, err)
check.Number(t, len(total), 7)
out, err := cli.run("-dir="+dir, "fix")
check.NoError(t, err)
check.Contains(t, out, "RENAMED")
files, err := os.ReadDir(dir)
check.NoError(t, err)
check.Number(t, len(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 {
check.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 {
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"))
check.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)
check.NoError(t, err)
defer f.Close()
}
62 changes: 62 additions & 0 deletions goose_embed_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package goose_test

import (
"database/sql"
"embed"
"io/fs"
"os"
"path/filepath"
"testing"

"github.com/pressly/goose/v3"
"github.com/pressly/goose/v3/internal/check"
_ "modernc.org/sqlite"
)

//go:embed testdata/migrations/*.sql
var embedMigrations embed.FS

func TestEmbeddedMigrations(t *testing.T) {
dir := t.TempDir()
// not using t.Parallel here to avoid races
db, err := sql.Open("sqlite", filepath.Join(dir, "sql_embed.db"))
check.NoError(t, err)

db.SetMaxOpenConns(1)

migrationFiles, err := fs.ReadDir(embedMigrations, "testdata/migrations")
check.NoError(t, err)
total := len(migrationFiles)

// decouple from existing structure
fsys, err := fs.Sub(embedMigrations, "testdata/migrations")
check.NoError(t, err)

goose.SetBaseFS(fsys)
t.Cleanup(func() { goose.SetBaseFS(nil) })
check.NoError(t, goose.SetDialect("sqlite3"))

t.Run("migration_cycle", func(t *testing.T) {
err := goose.Up(db, ".")
check.NoError(t, err)
ver, err := goose.GetDBVersion(db)
check.NoError(t, err)
check.Number(t, ver, total)
err = goose.Reset(db, ".")
check.NoError(t, err)
ver, err = goose.GetDBVersion(db)
check.NoError(t, err)
check.Number(t, ver, 0)
})
t.Run("create_uses_os_fs", func(t *testing.T) {
dir := t.TempDir()
err := goose.Create(db, dir, "test", "sql")
check.NoError(t, err)
paths, _ := filepath.Glob(filepath.Join(dir, "*test.sql"))
check.NumberNotZero(t, len(paths))
err = goose.Fix(dir)
check.NoError(t, err)
_, err = os.Stat(filepath.Join(dir, "00001_test.sql"))
check.NoError(t, err)
})
}

0 comments on commit 958c950

Please sign in to comment.