331 lines
13 KiB
Go
331 lines
13 KiB
Go
package main_test
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/benbjohnson/litestream/internal/testingutil"
|
|
)
|
|
|
|
func TestRestoreCommand(t *testing.T) {
|
|
t.Run("OK", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "ok")
|
|
defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)()
|
|
tempDir := t.TempDir()
|
|
|
|
m, _, stdout, stderr := newMain()
|
|
if err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), "-o", filepath.Join(tempDir, "db"), filepath.Join(testDir, "db")}); err != nil {
|
|
t.Fatal(err)
|
|
} else if got, want := stderr.String(), ""; got != want {
|
|
t.Fatalf("stderr=%q, want %q", got, want)
|
|
}
|
|
|
|
// STDOUT has timing info so we need to grep per line.
|
|
lines := strings.Split(stdout.String(), "\n")
|
|
for i, substr := range []string{
|
|
`restoring snapshot 0000000000000000/00000000 to ` + filepath.Join(tempDir, "db.tmp"),
|
|
`applied wal 0000000000000000/00000000 elapsed=`,
|
|
`applied wal 0000000000000000/00000001 elapsed=`,
|
|
`applied wal 0000000000000000/00000002 elapsed=`,
|
|
`renaming database from temporary location`,
|
|
} {
|
|
if !strings.Contains(lines[i], substr) {
|
|
t.Fatalf("stdout: unexpected line %d:\n%s", i+1, stdout)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("ReplicaName", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "replica-name")
|
|
defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)()
|
|
tempDir := t.TempDir()
|
|
|
|
m, _, stdout, stderr := newMain()
|
|
if err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), "-o", filepath.Join(tempDir, "db"), "-replica", "replica1", filepath.Join(testDir, "db")}); err != nil {
|
|
t.Fatal(err)
|
|
} else if got, want := stderr.String(), ""; got != want {
|
|
t.Fatalf("stderr=%q, want %q", got, want)
|
|
}
|
|
|
|
// STDOUT has timing info so we need to grep per line.
|
|
lines := strings.Split(stdout.String(), "\n")
|
|
for i, substr := range []string{
|
|
`restoring snapshot 0000000000000001/00000001 to ` + filepath.Join(tempDir, "db.tmp"),
|
|
`no wal files found, snapshot only`,
|
|
`renaming database from temporary location`,
|
|
} {
|
|
if !strings.Contains(lines[i], substr) {
|
|
t.Fatalf("stdout: unexpected line %d:\n%s", i+1, stdout)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("ReplicaURL", func(t *testing.T) {
|
|
testDir := filepath.Join(testingutil.Getwd(t), "testdata", "restore", "replica-url")
|
|
tempDir := t.TempDir()
|
|
replicaURL := "file://" + filepath.ToSlash(testDir) + "/replica"
|
|
|
|
m, _, stdout, stderr := newMain()
|
|
if err := m.Run(context.Background(), []string{"restore", "-o", filepath.Join(tempDir, "db"), replicaURL}); err != nil {
|
|
t.Fatal(err)
|
|
} else if got, want := stderr.String(), ""; got != want {
|
|
t.Fatalf("stderr=%q, want %q", got, want)
|
|
}
|
|
|
|
lines := strings.Split(stdout.String(), "\n")
|
|
for i, substr := range []string{
|
|
`restoring snapshot 0000000000000000/00000000 to ` + filepath.Join(tempDir, "db.tmp"),
|
|
`no wal files found, snapshot only`,
|
|
`renaming database from temporary location`,
|
|
} {
|
|
if !strings.Contains(lines[i], substr) {
|
|
t.Fatalf("stdout: unexpected line %d:\n%s", i+1, stdout)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("LatestReplica", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "latest-replica")
|
|
defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)()
|
|
tempDir := t.TempDir()
|
|
|
|
m, _, stdout, stderr := newMain()
|
|
if err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), "-o", filepath.Join(tempDir, "db"), filepath.Join(testDir, "db")}); err != nil {
|
|
t.Fatal(err)
|
|
} else if got, want := stderr.String(), ""; got != want {
|
|
t.Fatalf("stderr=%q, want %q", got, want)
|
|
}
|
|
|
|
lines := strings.Split(stdout.String(), "\n")
|
|
for i, substr := range []string{
|
|
`restoring snapshot 0000000000000001/00000000 to ` + filepath.Join(tempDir, "db.tmp"),
|
|
`no wal files found, snapshot only`,
|
|
`renaming database from temporary location`,
|
|
} {
|
|
if !strings.Contains(lines[i], substr) {
|
|
t.Fatalf("stdout: unexpected line %d:\n%s", i+1, stdout)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("IfDBNotExistsFlag", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "if-db-not-exists-flag")
|
|
defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)()
|
|
|
|
m, _, stdout, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), "-if-db-not-exists", filepath.Join(testDir, "db")})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
} else if got, want := stdout.String(), string(testingutil.ReadFile(t, filepath.Join(testDir, "stdout"))); got != want {
|
|
t.Fatalf("stdout=%q, want %q", got, want)
|
|
}
|
|
})
|
|
|
|
t.Run("IfReplicaExists", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "if-replica-exists-flag")
|
|
defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)()
|
|
|
|
m, _, stdout, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), "-if-replica-exists", filepath.Join(testDir, "db")})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
} else if got, want := stdout.String(), string(testingutil.ReadFile(t, filepath.Join(testDir, "stdout"))); got != want {
|
|
t.Fatalf("stdout=%q, want %q", got, want)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrNoBackups", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "no-backups")
|
|
defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)()
|
|
tempDir := t.TempDir()
|
|
|
|
m, _, stdout, stderr := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), "-o", filepath.Join(tempDir, "db"), filepath.Join(testDir, "db")})
|
|
if err == nil || err.Error() != `no matching backups found` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
} else if got, want := stdout.String(), string(testingutil.ReadFile(t, filepath.Join(testDir, "stdout"))); got != want {
|
|
t.Fatalf("stdout=%q, want %q", got, want)
|
|
} else if got, want := stderr.String(), string(testingutil.ReadFile(t, filepath.Join(testDir, "stderr"))); got != want {
|
|
t.Fatalf("stderr=%q, want %q", got, want)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrNoGeneration", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "no-generation")
|
|
defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)()
|
|
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), filepath.Join(testDir, "db")})
|
|
if err == nil || err.Error() != `no matching backups found` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrOutputPathExists", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "output-path-exists")
|
|
defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)()
|
|
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), filepath.Join(testDir, "db")})
|
|
if err == nil || err.Error() != `output file already exists: `+filepath.Join(testDir, "db") {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrDatabaseOrReplicaRequired", func(t *testing.T) {
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore"})
|
|
if err == nil || err.Error() != `database path or replica URL required` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrTooManyArguments", func(t *testing.T) {
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "abc", "123"})
|
|
if err == nil || err.Error() != `too many arguments` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrInvalidFlags", func(t *testing.T) {
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-no-such-flag"})
|
|
if err == nil || err.Error() != `flag provided but not defined: -no-such-flag` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrIndexFlagOnly", func(t *testing.T) {
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-index", "0", "/var/lib/db"})
|
|
if err == nil || err.Error() != `must specify -generation flag when using -index flag` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrConfigFileNotFound", func(t *testing.T) {
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-config", "/no/such/file", "/var/lib/db"})
|
|
if err == nil || err.Error() != `config file not found: /no/such/file` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrInvalidConfig", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "invalid-config")
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), "/var/lib/db"})
|
|
if err == nil || !strings.Contains(err.Error(), `replica path cannot be a url`) {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrMkdir", func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
if err := os.Mkdir(filepath.Join(tempDir, "noperm"), 0000); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-o", filepath.Join(tempDir, "noperm", "subdir", "db"), "/var/lib/db"})
|
|
if err == nil || !strings.Contains(err.Error(), `permission denied`) {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrNoOutputPathWithReplicaURL", func(t *testing.T) {
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "file://path/to/replica"})
|
|
if err == nil || err.Error() != `output path required when using a replica URL` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrReplicaNameWithReplicaURL", func(t *testing.T) {
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-replica", "replica0", "file://path/to/replica"})
|
|
if err == nil || err.Error() != `cannot specify both the replica URL and the -replica flag` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrInvalidReplicaURL", func(t *testing.T) {
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-o", "/tmp/db", "xyz://xyz"})
|
|
if err == nil || err.Error() != `unknown replica type in config: "xyz"` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrDatabaseNotFound", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "database-not-found")
|
|
defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)()
|
|
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), "/no/such/db"})
|
|
if err == nil || err.Error() != `database not found in config: /no/such/db` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrNoReplicas", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "no-replicas")
|
|
defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)()
|
|
tempDir := t.TempDir()
|
|
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), "-o", filepath.Join(tempDir, "db"), filepath.Join(testDir, "db")})
|
|
if err == nil || err.Error() != `database has no replicas: `+filepath.Join(testingutil.Getwd(t), testDir, "db") {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrReplicaNotFound", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "replica-not-found")
|
|
defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)()
|
|
tempDir := t.TempDir()
|
|
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), "-o", filepath.Join(tempDir, "db"), "-replica", "no_such_replica", filepath.Join(testDir, "db")})
|
|
if err == nil || err.Error() != `replica "no_such_replica" not found` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrGenerationWithNoReplicaName", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "generation-with-no-replica")
|
|
defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)()
|
|
tempDir := t.TempDir()
|
|
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), "-o", filepath.Join(tempDir, "db"), "-generation", "0000000000000000", filepath.Join(testDir, "db")})
|
|
if err == nil || err.Error() != `must specify -replica flag when restoring from a specific generation` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("ErrNoSnapshotsAvailable", func(t *testing.T) {
|
|
testDir := filepath.Join("testdata", "restore", "no-snapshots")
|
|
defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)()
|
|
tempDir := t.TempDir()
|
|
|
|
m, _, _, _ := newMain()
|
|
err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), "-o", filepath.Join(tempDir, "db"), "-generation", "0000000000000000", filepath.Join(testDir, "db")})
|
|
if err == nil || err.Error() != `cannot determine latest index in generation "0000000000000000": no snapshots available` {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Usage", func(t *testing.T) {
|
|
m, _, _, _ := newMain()
|
|
if err := m.Run(context.Background(), []string{"restore", "-h"}); err != flag.ErrHelp {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
}
|