Files
backrest/internal/orchestrator/repo/repo_test.go

349 lines
7.7 KiB
Go

package repo
import (
"bytes"
"context"
"io/ioutil"
"os"
"path"
"runtime"
"slices"
"strings"
"testing"
v1 "github.com/garethgeorge/backrest/gen/go/v1"
"github.com/garethgeorge/backrest/test/helpers"
test "github.com/garethgeorge/backrest/test/helpers"
)
var configForTest = &v1.Config{
Instance: "test",
}
func TestBackup(t *testing.T) {
t.Parallel()
testData := test.CreateTestData(t)
tcs := []struct {
name string
repo *v1.Repo
plan *v1.Plan
unixOnly bool
}{
{
name: "backup",
repo: &v1.Repo{
Id: "test",
Uri: t.TempDir(),
Password: "test",
},
plan: &v1.Plan{
Id: "test",
Repo: "test",
Paths: []string{testData},
},
},
{
name: "backup with ionice",
repo: &v1.Repo{
Id: "test",
Uri: t.TempDir(),
Password: "test",
CommandPrefix: &v1.CommandPrefix{
IoNice: v1.CommandPrefix_IO_BEST_EFFORT_LOW,
CpuNice: v1.CommandPrefix_CPU_LOW,
},
},
plan: &v1.Plan{
Id: "test",
Repo: "test",
Paths: []string{testData},
},
unixOnly: true,
},
}
for _, tc := range tcs {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if tc.unixOnly && runtime.GOOS == "windows" {
t.Skip("skipping on windows")
}
orchestrator := initRepoHelper(t, configForTest, tc.repo)
summary, err := orchestrator.Backup(context.Background(), tc.plan, nil)
if err != nil {
t.Fatalf("backup error: %v", err)
}
if summary.SnapshotId == "" {
t.Fatal("expected snapshot id")
}
if summary.FilesNew != 100 {
t.Fatalf("expected 100 new files, got %d", summary.FilesNew)
}
})
}
}
func TestRestore(t *testing.T) {
t.Parallel()
testFile := path.Join(t.TempDir(), "test.txt")
if err := ioutil.WriteFile(testFile, []byte("lorum ipsum"), 0644); err != nil {
t.Fatalf("failed to create test file: %v", err)
}
r := &v1.Repo{
Id: "test",
Uri: t.TempDir(),
Password: "test",
Flags: []string{"--no-cache"},
}
plan := &v1.Plan{
Id: "test",
Repo: "test",
Paths: []string{testFile},
}
orchestrator := initRepoHelper(t, configForTest, r)
// Create a backup of the single file
summary, err := orchestrator.Backup(context.Background(), plan, nil)
if err != nil {
t.Fatalf("backup error: %v", err)
}
if summary.SnapshotId == "" {
t.Fatal("expected snapshot id")
}
if summary.FilesNew != 1 {
t.Fatalf("expected 1 new file, got %d", summary.FilesNew)
}
// Restore the file
restoreDir := t.TempDir()
snapshotPath := strings.ReplaceAll(testFile, ":", "") // remove the colon from the windows path e.g. C:\test.txt -> C\test.txt
restoreSummary, err := orchestrator.Restore(context.Background(), summary.SnapshotId, snapshotPath, restoreDir, nil)
if err != nil {
t.Fatalf("restore error: %v", err)
}
t.Logf("restore summary: %+v", restoreSummary)
if runtime.GOOS == "windows" {
return
}
if restoreSummary.FilesRestored != 1 {
t.Errorf("expected 1 new file, got %d", restoreSummary.FilesRestored)
}
if restoreSummary.TotalFiles != 1 {
t.Errorf("expected 1 total file, got %d", restoreSummary.TotalFiles)
}
// Check the restored file
restoredFile := path.Join(restoreDir, "test.txt")
if _, err := os.Stat(restoredFile); err != nil {
t.Fatalf("failed to stat restored file: %v", err)
}
restoredData, err := os.ReadFile(restoredFile)
if err != nil {
t.Fatalf("failed to read restored file: %v", err)
}
if string(restoredData) != "lorum ipsum" {
t.Fatalf("expected 'test', got '%s'", restoredData)
}
}
func TestSnapshotParenting(t *testing.T) {
t.Parallel()
repo := t.TempDir()
testData := test.CreateTestData(t)
// create a new repo with cache disabled for testing
r := &v1.Repo{
Id: "test",
Uri: repo,
Password: "test",
Flags: []string{"--no-cache"},
}
plans := []*v1.Plan{
{
Id: "test",
Repo: "test",
Paths: []string{testData},
},
{
Id: "test2",
Repo: "test",
Paths: []string{testData},
},
}
orchestrator := initRepoHelper(t, configForTest, r)
for i := 0; i < 4; i++ {
for _, plan := range plans {
summary, err := orchestrator.Backup(context.Background(), plan, nil)
if err != nil {
t.Fatalf("failed to backup plan %s: %v", plan.Id, err)
}
if summary.SnapshotId == "" {
t.Errorf("expected snapshot id")
}
if summary.TotalFilesProcessed != 100 {
t.Logf("summary is: %+v", summary)
t.Errorf("expected 100 done files, got %d", summary.TotalFilesProcessed)
}
}
}
for _, plan := range plans {
snapshots, err := orchestrator.SnapshotsForPlan(context.Background(), plan)
if err != nil {
t.Errorf("failed to get snapshots for plan %s: %v", plan.Id, err)
continue
}
if len(snapshots) != 4 {
t.Errorf("expected 4 snapshots, got %d", len(snapshots))
}
for i := 1; i < len(snapshots); i++ {
prev := snapshots[i-1]
curr := snapshots[i]
if prev.UnixTimeMs() >= curr.UnixTimeMs() {
t.Errorf("snapshots are out of order")
}
if prev.Id != curr.Parent {
t.Errorf("expected snapshot %s to have parent %s, got %s", curr.Id, prev.Id, curr.Parent)
}
if !slices.Contains(curr.Tags, TagForPlan(plan.Id)) {
t.Errorf("expected snapshot %s to have tag %s", curr.Id, TagForPlan(plan.Id))
}
}
}
snapshots, err := orchestrator.Snapshots(context.Background())
if err != nil {
t.Fatal(err)
}
if len(snapshots) != 8 {
t.Errorf("expected 8 snapshots, got %d", len(snapshots))
}
}
func TestEnvVarPropagation(t *testing.T) {
repo := t.TempDir()
// create a new repo with cache disabled for testing
r := &v1.Repo{
Id: "test",
Uri: repo,
Flags: []string{"--no-cache"},
Env: []string{"RESTIC_PASSWORD=${MY_FOO}"},
}
orchestrator, err := NewRepoOrchestrator(configForTest, r, helpers.ResticBinary(t))
if err != nil {
t.Fatalf("failed to create repo orchestrator: %v", err)
}
err = orchestrator.Init(context.Background())
if err == nil || !strings.Contains(err.Error(), "password") {
t.Fatalf("expected error about RESTIC_PASSWORD, got: %v", err)
}
// set the env var
os.Setenv("MY_FOO", "bar")
defer os.Unsetenv("MY_FOO")
orchestrator, err = NewRepoOrchestrator(configForTest, r, helpers.ResticBinary(t))
if err != nil {
t.Fatalf("failed to create repo orchestrator: %v", err)
}
err = orchestrator.Init(context.Background())
if err != nil {
t.Fatalf("backup error: %v", err)
}
}
func TestCheck(t *testing.T) {
t.Parallel()
tcs := []struct {
name string
repo *v1.Repo
}{
{
name: "check structure",
repo: &v1.Repo{
Id: "test",
Uri: t.TempDir(),
Password: "test",
CheckPolicy: &v1.CheckPolicy{
Mode: nil,
},
},
},
{
name: "read data percent",
repo: &v1.Repo{
Id: "test",
Uri: t.TempDir(),
Password: "test",
CheckPolicy: &v1.CheckPolicy{
Mode: &v1.CheckPolicy_ReadDataSubsetPercent{
ReadDataSubsetPercent: 50,
},
},
},
},
}
for _, tc := range tcs {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
orchestrator := initRepoHelper(t, configForTest, tc.repo)
buf := bytes.NewBuffer(nil)
err := orchestrator.Init(context.Background())
if err != nil {
t.Fatalf("init error: %v", err)
}
err = orchestrator.Check(context.Background(), buf)
if err != nil {
t.Errorf("check error: %v", err)
}
t.Logf("check output: %s", buf.String())
})
}
}
func initRepoHelper(t *testing.T, config *v1.Config, repo *v1.Repo) *RepoOrchestrator {
orchestrator, err := NewRepoOrchestrator(config, repo, helpers.ResticBinary(t))
if err != nil {
t.Fatalf("failed to create repo orchestrator: %v", err)
}
err = orchestrator.Init(context.Background())
if err != nil {
t.Fatalf("init error: %v", err)
}
return orchestrator
}