package restic import ( "bytes" "encoding/json" "fmt" "github.com/rs/zerolog/log" "github.com/stretchr/testify/assert" "os" "os/exec" "testing" "time" ) func TestWrapper_LastSnapshot(t *testing.T) { shot1 := ResticSnapshot{ Id: "shot1", Time: time.Date(2020, 10, 10, 10, 10, 10, 10, time.UTC), } shot2 := ResticSnapshot{ Id: "shot2", Time: time.Date(2022, 11, 11, 11, 11, 11, 11, time.UTC), } cases := []struct { label string snapshots []ResticSnapshot expected ResticSnapshot }{ { "None yet", []ResticSnapshot{}, ResticSnapshot{ Id: "(no snapshots yet)", Time: time.Time{}, }, }, { "take the last from the array", []ResticSnapshot{shot1, shot2}, shot2, }, } for _, tc := range cases { t.Run(tc.label, func(t *testing.T) { wrapper := Wrapper{ LatestSnapshots: tc.snapshots, } last := wrapper.LastSnapshot() assert.Equal(t, tc.expected.Id, last.Id) assert.Equal(t, tc.expected.Time.Hour(), last.Time.Hour()) assert.Equal(t, tc.expected.Time.Minute(), last.Time.Minute()) }) } } // a very clever way to test exec.Command in go: https://jamiethompson.me/posts/Unit-Testing-Exec-Command-In-Golang/ func fakeExecCommand(whichTest string) func(args ...string) *exec.Cmd { return func(args ...string) *exec.Cmd { cs := []string{"-test.run=" + whichTest, "--", "restic"} cs = append(cs, args...) cmd := exec.Command(os.Args[0], cs...) cmd.Env = []string{"GO_TEST_PROCESS=1"} return cmd } } func TestCmdnapshotSuccessWithinTime(t *testing.T) { if os.Getenv("GO_TEST_PROCESS") != "1" { return } snapshots := []ResticSnapshot{ { Id: "id1", Time: timeNow(), Tree: "tree", Paths: []string{ "/path/one", "/path/two", }, }, { Id: "id2", Time: timeNow(), Tree: "tree2", Paths: []string{ "/path2/one", "/path2/two", }, }, } bytes, _ := json.Marshal(snapshots) fmt.Fprintf(os.Stdout, string(bytes)) os.Exit(0) } func TestCmdSuccessWithinTime(t *testing.T) { if os.Getenv("GO_TEST_PROCESS") != "1" { return } fmt.Fprintf(os.Stdout, "some output\n") os.Exit(0) } func TestCmdExitCode1WithinTime(t *testing.T) { if os.Getenv("GO_TEST_PROCESS") != "1" { return } fmt.Fprintf(os.Stdout, "some output\n") os.Exit(1) } func TestCmdExitCode3WithinTime(t *testing.T) { if os.Getenv("GO_TEST_PROCESS") != "1" { return } fmt.Fprintf(os.Stdout, "some output\n") os.Exit(3) } func TestCmdTakesLongerThan2sButOutputsFaster(t *testing.T) { if os.Getenv("GO_TEST_PROCESS") != "1" { return } // don't forget \n as the scanner splits on ScanLines time.Sleep(1 * time.Second) fmt.Fprintf(os.Stdout, "some output after 1s\n") time.Sleep(2 * time.Second) fmt.Fprintf(os.Stdout, "some output after 3s\n") os.Exit(0) } func TestCmdTakesLongerThan2sToOutputAnything(t *testing.T) { if os.Getenv("GO_TEST_PROCESS") != "1" { return } time.Sleep(3 * time.Second) fmt.Fprintf(os.Stdout, "some output, too late!\n") os.Exit(0) } func TestBackup_ExitCode1_ReturnsError(t *testing.T) { resticCmd = fakeExecCommand("TestCmdExitCode1WithinTime") wrapper := Wrapper{} err := wrapper.Backup(&Config{}) assert.Error(t, err) } func TestBackup_ExitCode3_IssuesWarning(t *testing.T) { resticCmd = fakeExecCommand("TestCmdExitCode3WithinTime") var logBuffer bytes.Buffer log.Logger = log.Output(&logBuffer) wrapper := Wrapper{} err := wrapper.Backup(&Config{}) assert.NoError(t, err) assert.Contains(t, logBuffer.String(), "permission problem?") } func TestBackup_OutputsAndFinishesAsExpected(t *testing.T) { resticCmd = fakeExecCommand("TestCmdSuccessWithinTime") var logBuffer bytes.Buffer log.Logger = log.Output(&logBuffer) wrapper := Wrapper{} err := wrapper.Backup(&Config{}) assert.NoError(t, err) assert.Contains(t, logBuffer.String(), "some output") } func TestBackup_OutputsBefore2sButStillTakesLonger(t *testing.T) { resticCmd = fakeExecCommand("TestCmdTakesLongerThan2sButOutputsFaster") cmdTimeout = 2 * time.Second wrapper := Wrapper{} err := wrapper.Backup(&Config{}) assert.NoError(t, err) } func TestBackup_TakesLongerThanNeededToOutput_TimesOut(t *testing.T) { resticCmd = fakeExecCommand("TestCmdTakesLongerThan2sToOutputAnything") cmdTimeout = 2 * time.Second wrapper := Wrapper{} err := wrapper.Backup(&Config{}) assert.ErrorIs(t, err, ResticCmdTimeoutError) } func TestUpdateLatestSnapshot_TakesLongerThanNeededToOutput_TimesOut(t *testing.T) { resticCmd = fakeExecCommand("TestCmdTakesLongerThan2sToOutputAnything") cmdTimeout = 2 * time.Second wrapper := Wrapper{} err := wrapper.UpdateLatestSnapshots(&Config{}) assert.Empty(t, wrapper.LatestSnapshots) assert.ErrorIs(t, err, ResticCmdTimeoutError) } func TestUpdateLatestSnapshots_FromJSONOutputOfRestic(t *testing.T) { resticCmd = fakeExecCommand("TestCmdnapshotSuccessWithinTime") wrapper := Wrapper{} err := wrapper.UpdateLatestSnapshots(&Config{}) assert.NoError(t, err) assert.Equal(t, "id1", wrapper.LatestSnapshots[0].Id) assert.Equal(t, "/path/two", wrapper.LatestSnapshots[0].Paths[1]) assert.Equal(t, "id2", wrapper.LatestSnapshots[1].Id) assert.Equal(t, "/path2/two", wrapper.LatestSnapshots[1].Paths[1]) }