| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- package backup
- import (
- "errors"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
- "testing"
- )
- func TestRestoreFailureRollsBackFiles(t *testing.T) {
- root := t.TempDir()
- current := filepath.Join(root, "debtik")
- network := filepath.Join(root, "network")
- stage := filepath.Join(root, "staged")
- for _, dir := range []string{current, network, filepath.Join(stage, "debtik"), filepath.Join(stage, "network")} {
- if err := os.MkdirAll(dir, 0700); err != nil {
- t.Fatal(err)
- }
- }
- write := func(path, content string) {
- t.Helper()
- if err := os.WriteFile(path, []byte(content), 0600); err != nil {
- t.Fatal(err)
- }
- }
- write(filepath.Join(current, "old.json"), "old config")
- write(filepath.Join(network, "a.cfg"), "old network")
- write(filepath.Join(stage, "debtik", "new.json"), "new config")
- write(filepath.Join(stage, "network", "a.cfg"), "new network")
- write(filepath.Join(stage, "network", "b.cfg"), "new file")
- mgr := NewManager(current, network, filepath.Join(root, "backups"), nil)
- count := 0
- err := mgr.applyStagedRestore(stage, func(step string) error {
- if step == "network" {
- count++
- if count == 2 {
- return errors.New("simulated write failure")
- }
- }
- return nil
- })
- if err == nil || !strings.Contains(err.Error(), "previous files restored") {
- t.Fatalf("unexpected error %v", err)
- }
- for path, want := range map[string]string{filepath.Join(current, "old.json"): "old config", filepath.Join(network, "a.cfg"): "old network"} {
- data, err := os.ReadFile(path)
- if err != nil || string(data) != want {
- t.Fatalf("%s: %q %v", path, data, err)
- }
- }
- for _, path := range []string{filepath.Join(current, "new.json"), filepath.Join(network, "b.cfg")} {
- if _, err := os.Stat(path); !os.IsNotExist(err) {
- t.Fatalf("unexpected remaining %s: %v", path, err)
- }
- }
- }
- func TestRestoreCrashHelper(t *testing.T) {
- root := os.Getenv("DEBTIK_CRASH_TEST_ROOT")
- if root == "" {
- return
- }
- point := os.Getenv("DEBTIK_CRASH_TEST_POINT")
- mgr := NewManager(filepath.Join(root, "debtik"), filepath.Join(root, "network"), filepath.Join(root, "backups"), nil)
- err := mgr.applyStagedRestore(filepath.Join(root, "stage"), func(step string) error {
- if step == point {
- os.Exit(42)
- }
- return nil
- })
- t.Fatalf("failed to reach %s: %v", point, err)
- }
- func TestRecoveryAfterProcessCrash(t *testing.T) {
- for _, point := range []string{"old-renamed", "debtik", "network", "committed"} {
- t.Run(point, func(t *testing.T) {
- root := t.TempDir()
- current := filepath.Join(root, "debtik")
- network := filepath.Join(root, "network")
- stage := filepath.Join(root, "stage")
- for _, dir := range []string{current, network, filepath.Join(stage, "debtik"), filepath.Join(stage, "network")} {
- if err := os.MkdirAll(dir, 0700); err != nil {
- t.Fatal(err)
- }
- }
- for path, content := range map[string]string{filepath.Join(current, "config.json"): "old", filepath.Join(network, "a.cfg"): "old network", filepath.Join(stage, "debtik", "config.json"): "new", filepath.Join(stage, "network", "a.cfg"): "new network"} {
- if err := os.WriteFile(path, []byte(content), 0600); err != nil {
- t.Fatal(err)
- }
- }
- cmd := exec.Command(os.Args[0], "-test.run=^TestRestoreCrashHelper$")
- cmd.Env = append(os.Environ(), "DEBTIK_CRASH_TEST_ROOT="+root, "DEBTIK_CRASH_TEST_POINT="+point)
- if err := cmd.Run(); err == nil {
- t.Fatal("expected process to terminate")
- } else if ex, ok := err.(*exec.ExitError); !ok || ex.ExitCode() != 42 {
- t.Fatalf("unexpected subprocess failure: %v", err)
- }
- mgr := NewManager(current, network, filepath.Join(root, "backups"), nil)
- recovered, err := mgr.RecoverInterruptedRestore()
- if err != nil || !recovered {
- t.Fatalf("recover: %v %v", recovered, err)
- }
- config := "old"
- netConfig := "old network"
- if point == "committed" {
- config = "new"
- netConfig = "new network"
- }
- for path, want := range map[string]string{filepath.Join(current, "config.json"): config, filepath.Join(network, "a.cfg"): netConfig} {
- data, err := os.ReadFile(path)
- if err != nil || string(data) != want {
- t.Fatalf("%s: %q %v", path, data, err)
- }
- }
- if again, err := mgr.RecoverInterruptedRestore(); err != nil || again {
- t.Fatalf("recovery not idempotent: %v %v", again, err)
- }
- })
- }
- }
- func TestRecoveryAfterCrashWithNoPreviousConfig(t *testing.T) {
- root := t.TempDir()
- stage := filepath.Join(root, "stage")
- network := filepath.Join(root, "network")
- if err := os.MkdirAll(filepath.Join(stage, "debtik"), 0700); err != nil {
- t.Fatal(err)
- }
- if err := os.MkdirAll(network, 0700); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(stage, "debtik", "new.json"), []byte("new"), 0600); err != nil {
- t.Fatal(err)
- }
- cmd := exec.Command(os.Args[0], "-test.run=^TestRestoreCrashHelper$")
- cmd.Env = append(os.Environ(), "DEBTIK_CRASH_TEST_ROOT="+root, "DEBTIK_CRASH_TEST_POINT=debtik")
- if err := cmd.Run(); err == nil {
- t.Fatal("expected process to terminate")
- } else if ex, ok := err.(*exec.ExitError); !ok || ex.ExitCode() != 42 {
- t.Fatal(err)
- }
- mgr := NewManager(filepath.Join(root, "debtik"), network, filepath.Join(root, "backups"), nil)
- if recovered, err := mgr.RecoverInterruptedRestore(); err != nil || !recovered {
- t.Fatalf("recovery: %v %v", recovered, err)
- }
- if _, err := os.Stat(filepath.Join(root, "debtik")); !os.IsNotExist(err) {
- t.Fatalf("new directory still present: %v", err)
- }
- }
- func TestRecoveryRejectsJournalWithUnrelatedPaths(t *testing.T) {
- root := t.TempDir()
- mgr := NewManager(filepath.Join(root, "debtik"), filepath.Join(root, "network"), filepath.Join(root, "backups"), nil)
- journal := &restoreJournal{DebtikDir: mgr.debtikDir, NetworkDir: mgr.networkDir, Files: []journalFile{{Path: filepath.Join(root, "outside.cfg")}}}
- if err := mgr.writeJournal(journal); err != nil {
- t.Fatal(err)
- }
- if _, err := mgr.RecoverInterruptedRestore(); err == nil {
- t.Fatal("accepted unrelated file")
- }
- if _, err := os.Stat(mgr.journalPath()); err != nil {
- t.Fatalf("journal lost after failed recovery: %v", err)
- }
- }
- func TestRecoveryRejectsIncompleteJournal(t *testing.T) {
- root := t.TempDir()
- current := filepath.Join(root, "debtik")
- if err := os.MkdirAll(current, 0700); err != nil {
- t.Fatal(err)
- }
- marker := filepath.Join(current, "config")
- if err := os.WriteFile(marker, []byte("preserve"), 0600); err != nil {
- t.Fatal(err)
- }
- mgr := NewManager(current, filepath.Join(root, "network"), filepath.Join(root, "backups"), nil)
- if err := mgr.writeJournal(&restoreJournal{DebtikDir: current, NetworkDir: mgr.networkDir, HasDebtik: true}); err != nil {
- t.Fatal(err)
- }
- if _, err := mgr.RecoverInterruptedRestore(); err == nil {
- t.Fatal("accepted incomplete journal")
- }
- if data, err := os.ReadFile(marker); err != nil || string(data) != "preserve" {
- t.Fatalf("config damaged: %q %v", data, err)
- }
- }
|