mirror of
				https://github.com/garethgeorge/backrest.git
				synced 2025-10-30 20:27:05 +00:00 
			
		
		
		
	 4357295a17
			
		
	
	4357295a17
	
	
		
			
	
		
	
	
		
			Some checks failed
		
		
	
	Release Please / release-please (push) Has been cancelled
				
			Release Preview / call-reusable-release (push) Has been cancelled
				
			Test / test-nix (push) Has been cancelled
				
			Test / test-win (push) Has been cancelled
				
			Update Restic / update-restic-version (push) Has been cancelled
				
			
		
			
				
	
	
		
			452 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			452 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package tasks
 | |
| 
 | |
| import (
 | |
| 	"os"
 | |
| 	"runtime"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	v1 "github.com/garethgeorge/backrest/gen/go/v1"
 | |
| 	"github.com/garethgeorge/backrest/internal/config"
 | |
| 	"github.com/garethgeorge/backrest/internal/cryptoutil"
 | |
| 	"github.com/garethgeorge/backrest/internal/oplog"
 | |
| 	"github.com/garethgeorge/backrest/internal/oplog/sqlitestore"
 | |
| )
 | |
| 
 | |
| func TestScheduling(t *testing.T) {
 | |
| 	if runtime.GOOS == "windows" {
 | |
| 		t.Skip("skipping test on windows")
 | |
| 	}
 | |
| 
 | |
| 	os.Setenv("TZ", "America/Los_Angeles")
 | |
| 	defer os.Unsetenv("TZ")
 | |
| 
 | |
| 	cfg := &v1.Config{
 | |
| 		Instance: "instance1",
 | |
| 		Repos: []*v1.Repo{
 | |
| 			{
 | |
| 				Id:   "repo1",
 | |
| 				Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits),
 | |
| 			},
 | |
| 			{
 | |
| 				Id:   "repo-absolute",
 | |
| 				Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits),
 | |
| 				CheckPolicy: &v1.CheckPolicy{
 | |
| 					Schedule: &v1.Schedule{
 | |
| 						Schedule: &v1.Schedule_MaxFrequencyHours{
 | |
| 							MaxFrequencyHours: 1,
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 				PrunePolicy: &v1.PrunePolicy{
 | |
| 					Schedule: &v1.Schedule{
 | |
| 						Schedule: &v1.Schedule_MaxFrequencyHours{
 | |
| 							MaxFrequencyHours: 1,
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				Id:   "repo-relative",
 | |
| 				Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits),
 | |
| 				CheckPolicy: &v1.CheckPolicy{
 | |
| 					Schedule: &v1.Schedule{
 | |
| 						Schedule: &v1.Schedule_MaxFrequencyHours{
 | |
| 							MaxFrequencyHours: 1,
 | |
| 						},
 | |
| 						Clock: v1.Schedule_CLOCK_LAST_RUN_TIME,
 | |
| 					},
 | |
| 				},
 | |
| 				PrunePolicy: &v1.PrunePolicy{
 | |
| 					Schedule: &v1.Schedule{
 | |
| 						Schedule: &v1.Schedule_MaxFrequencyHours{
 | |
| 							MaxFrequencyHours: 1,
 | |
| 						},
 | |
| 						Clock: v1.Schedule_CLOCK_LAST_RUN_TIME,
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		Plans: []*v1.Plan{
 | |
| 			{
 | |
| 				Id: "plan-cron",
 | |
| 				Schedule: &v1.Schedule{
 | |
| 					Schedule: &v1.Schedule_Cron{
 | |
| 						Cron: "0 0 * * *", // every day at midnight
 | |
| 					},
 | |
| 					Clock: v1.Schedule_CLOCK_LOCAL,
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				Id: "plan-cron-utc",
 | |
| 				Schedule: &v1.Schedule{
 | |
| 					Schedule: &v1.Schedule_Cron{
 | |
| 						Cron: "0 0 * * *", // every day at midnight
 | |
| 					},
 | |
| 					Clock: v1.Schedule_CLOCK_UTC,
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				Id: "plan-cron-since-last-run",
 | |
| 				Schedule: &v1.Schedule{
 | |
| 					Schedule: &v1.Schedule_Cron{
 | |
| 						Cron: "0 0 * * *", // every day at midnight
 | |
| 					},
 | |
| 					Clock: v1.Schedule_CLOCK_LAST_RUN_TIME,
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				Id: "plan-max-frequency-days",
 | |
| 				Schedule: &v1.Schedule{
 | |
| 					Schedule: &v1.Schedule_MaxFrequencyDays{
 | |
| 						MaxFrequencyDays: 1,
 | |
| 					},
 | |
| 					Clock: v1.Schedule_CLOCK_LOCAL,
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				Id: "plan-min-days-since-last-run",
 | |
| 				Schedule: &v1.Schedule{
 | |
| 					Schedule: &v1.Schedule_MaxFrequencyDays{
 | |
| 						MaxFrequencyDays: 1,
 | |
| 					},
 | |
| 					Clock: v1.Schedule_CLOCK_LAST_RUN_TIME,
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				Id: "plan-max-frequency-hours",
 | |
| 				Schedule: &v1.Schedule{
 | |
| 					Schedule: &v1.Schedule_MaxFrequencyHours{
 | |
| 						MaxFrequencyHours: 1,
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				Id: "plan-min-hours-since-last-run",
 | |
| 				Schedule: &v1.Schedule{
 | |
| 					Schedule: &v1.Schedule_MaxFrequencyHours{
 | |
| 						MaxFrequencyHours: 1,
 | |
| 					},
 | |
| 					Clock: v1.Schedule_CLOCK_LAST_RUN_TIME,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	repo1 := config.FindRepo(cfg, "repo1")
 | |
| 	repoAbsolute := config.FindRepo(cfg, "repo-absolute")
 | |
| 	repoRelative := config.FindRepo(cfg, "repo-relative")
 | |
| 	if repoAbsolute == nil || repoRelative == nil || repo1 == nil {
 | |
| 		t.Fatalf("test config declaration error")
 | |
| 	}
 | |
| 
 | |
| 	now := time.Unix(100000, 0) // 1000 seconds after the epoch as an arbitrary time for the test
 | |
| 	farFuture := time.Unix(999999, 0)
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		name     string
 | |
| 		task     Task
 | |
| 		ops      []*v1.Operation // operations in the log
 | |
| 		wantTime time.Time       // time to run the next task
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "backup schedule max frequency days",
 | |
| 			task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-max-frequency-days")),
 | |
| 			ops: []*v1.Operation{
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo1",
 | |
| 					RepoGuid:   repo1.Guid,
 | |
| 					PlanId:     "plan-max-frequency-days",
 | |
| 					Op: &v1.Operation_OperationBackup{
 | |
| 						OperationBackup: &v1.OperationBackup{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 			},
 | |
| 			wantTime: now.Add(time.Hour * 24),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "backup schedule min days since last run",
 | |
| 			task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-min-days-since-last-run")),
 | |
| 			ops: []*v1.Operation{
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo1",
 | |
| 					RepoGuid:   repo1.Guid,
 | |
| 					PlanId:     "plan-min-days-since-last-run",
 | |
| 					Op: &v1.Operation_OperationBackup{
 | |
| 						OperationBackup: &v1.OperationBackup{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 			},
 | |
| 			wantTime: farFuture.Add(time.Hour * 24),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "backup schedule max frequency hours",
 | |
| 			task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-max-frequency-hours")),
 | |
| 			ops: []*v1.Operation{
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo1",
 | |
| 					RepoGuid:   repo1.Guid,
 | |
| 					PlanId:     "plan-max-frequency-hours",
 | |
| 					Op: &v1.Operation_OperationBackup{
 | |
| 						OperationBackup: &v1.OperationBackup{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 			},
 | |
| 			wantTime: now.Add(time.Hour),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "backup schedule min hours since last run",
 | |
| 			task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-min-hours-since-last-run")),
 | |
| 			ops: []*v1.Operation{
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo1",
 | |
| 					RepoGuid:   repo1.Guid,
 | |
| 					PlanId:     "plan-min-hours-since-last-run",
 | |
| 					Op: &v1.Operation_OperationBackup{
 | |
| 						OperationBackup: &v1.OperationBackup{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 			},
 | |
| 			wantTime: farFuture.Add(time.Hour),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "backup schedule cron",
 | |
| 			task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-cron")),
 | |
| 			ops: []*v1.Operation{
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo1",
 | |
| 					RepoGuid:   repo1.Guid,
 | |
| 					PlanId:     "plan-cron",
 | |
| 					Op: &v1.Operation_OperationBackup{
 | |
| 						OperationBackup: &v1.OperationBackup{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 			},
 | |
| 			wantTime: mustParseTime(t, "1970-01-02T00:00:00-08:00"),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "backup schedule cron utc",
 | |
| 			task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-cron-utc")),
 | |
| 			ops: []*v1.Operation{
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo1",
 | |
| 					RepoGuid:   repo1.Guid,
 | |
| 					PlanId:     "plan-cron-utc",
 | |
| 					Op: &v1.Operation_OperationBackup{
 | |
| 						OperationBackup: &v1.OperationBackup{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 			},
 | |
| 			wantTime: mustParseTime(t, "1970-01-02T08:00:00Z"),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "backup schedule cron since last run",
 | |
| 			task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-cron-since-last-run")),
 | |
| 			ops: []*v1.Operation{
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo1",
 | |
| 					RepoGuid:   repo1.Guid,
 | |
| 					PlanId:     "plan-cron-since-last-run",
 | |
| 					Op: &v1.Operation_OperationBackup{
 | |
| 						OperationBackup: &v1.OperationBackup{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 			},
 | |
| 			wantTime: mustParseTime(t, "1970-01-13T00:00:00-08:00"),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "check schedule absolute",
 | |
| 			task: NewCheckTask(repoAbsolute, "_system_", false),
 | |
| 			ops: []*v1.Operation{
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo-absolute",
 | |
| 					RepoGuid:   repoAbsolute.Guid,
 | |
| 					PlanId:     "_system_",
 | |
| 					Op: &v1.Operation_OperationCheck{
 | |
| 						OperationCheck: &v1.OperationCheck{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 			},
 | |
| 			wantTime: now.Add(time.Hour),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "check schedule relative no backup yet",
 | |
| 			task: NewCheckTask(repoRelative, "_system_", false),
 | |
| 			ops: []*v1.Operation{
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo-relative",
 | |
| 					RepoGuid:   repoRelative.Guid,
 | |
| 					PlanId:     "_system_",
 | |
| 					Op: &v1.Operation_OperationCheck{
 | |
| 						OperationCheck: &v1.OperationCheck{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 			},
 | |
| 			wantTime: now.Add(time.Hour),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "check schedule relative",
 | |
| 			task: NewCheckTask(repoRelative, "_system_", false),
 | |
| 			ops: []*v1.Operation{
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo-relative",
 | |
| 					RepoGuid:   repoRelative.Guid,
 | |
| 					PlanId:     "_system_",
 | |
| 					Op: &v1.Operation_OperationCheck{
 | |
| 						OperationCheck: &v1.OperationCheck{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo-relative",
 | |
| 					RepoGuid:   repoRelative.Guid,
 | |
| 					PlanId:     "_system_",
 | |
| 					Op: &v1.Operation_OperationBackup{
 | |
| 						OperationBackup: &v1.OperationBackup{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 			},
 | |
| 			wantTime: farFuture.Add(time.Hour),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "prune schedule absolute",
 | |
| 			task: NewPruneTask(repoAbsolute, "_system_", false),
 | |
| 			ops: []*v1.Operation{
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo-absolute",
 | |
| 					RepoGuid:   repoAbsolute.Guid,
 | |
| 					PlanId:     "_system_",
 | |
| 					Op: &v1.Operation_OperationPrune{
 | |
| 						OperationPrune: &v1.OperationPrune{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 			},
 | |
| 			wantTime: now.Add(time.Hour),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "prune schedule relative no backup yet",
 | |
| 			task: NewPruneTask(repoRelative, "_system_", false),
 | |
| 			ops: []*v1.Operation{
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo-relative",
 | |
| 					RepoGuid:   repoRelative.Guid,
 | |
| 					PlanId:     "_system_",
 | |
| 					Op: &v1.Operation_OperationPrune{
 | |
| 						OperationPrune: &v1.OperationPrune{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 			},
 | |
| 			wantTime: now.Add(time.Hour),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "prune schedule relative",
 | |
| 			task: NewPruneTask(repoRelative, "_system_", false),
 | |
| 			ops: []*v1.Operation{
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo-relative",
 | |
| 					RepoGuid:   repoRelative.Guid,
 | |
| 					PlanId:     "_system_",
 | |
| 					Op: &v1.Operation_OperationPrune{
 | |
| 						OperationPrune: &v1.OperationPrune{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 				{
 | |
| 					InstanceId: "instance1",
 | |
| 					RepoId:     "repo-relative",
 | |
| 					RepoGuid:   repoRelative.Guid,
 | |
| 					PlanId:     "_system_",
 | |
| 					Op: &v1.Operation_OperationBackup{
 | |
| 						OperationBackup: &v1.OperationBackup{},
 | |
| 					},
 | |
| 					UnixTimeStartMs: 1000,
 | |
| 					UnixTimeEndMs:   farFuture.UnixMilli(),
 | |
| 				},
 | |
| 			},
 | |
| 			wantTime: farFuture.Add(time.Hour),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range tests {
 | |
| 		tc := tc
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			t.Parallel()
 | |
| 
 | |
| 			opstore, err := sqlitestore.NewMemorySqliteStore(t)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("failed to create opstore: %v", err)
 | |
| 			}
 | |
| 			for _, op := range tc.ops {
 | |
| 				if err := opstore.Add(op); err != nil {
 | |
| 					t.Fatalf("failed to add operation to opstore: %v", err)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			log, err := oplog.NewOpLog(opstore)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("failed to create oplog: %v", err)
 | |
| 			}
 | |
| 
 | |
| 			runner := newTestTaskRunner(t, cfg, log)
 | |
| 
 | |
| 			st, err := tc.task.Next(now, runner)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("failed to get next task: %v", err)
 | |
| 			}
 | |
| 
 | |
| 			if !st.RunAt.Equal(tc.wantTime) {
 | |
| 				t.Errorf("got run at %v, want %v", st.RunAt.Format(time.RFC3339), tc.wantTime.Format(time.RFC3339))
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func mustParseTime(t *testing.T, s string) time.Time {
 | |
| 	t.Helper()
 | |
| 	tm, err := time.Parse(time.RFC3339, s)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("failed to parse time: %v", err)
 | |
| 	}
 | |
| 	return tm
 | |
| }
 |