1package restic_test 2 3import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "path/filepath" 8 "testing" 9 "time" 10 11 "github.com/google/go-cmp/cmp" 12 "github.com/google/go-cmp/cmp/cmpopts" 13 "github.com/restic/restic/internal/restic" 14) 15 16func parseTimeUTC(s string) time.Time { 17 t, err := time.Parse("2006-01-02 15:04:05", s) 18 if err != nil { 19 panic(err) 20 } 21 22 return t.UTC() 23} 24 25func parseDuration(s string) restic.Duration { 26 d, err := restic.ParseDuration(s) 27 if err != nil { 28 panic(err) 29 } 30 31 return d 32} 33 34func TestExpireSnapshotOps(t *testing.T) { 35 data := []struct { 36 expectEmpty bool 37 expectSum int 38 p *restic.ExpirePolicy 39 }{ 40 {true, 0, &restic.ExpirePolicy{}}, 41 {true, 0, &restic.ExpirePolicy{Tags: []restic.TagList{}}}, 42 {false, 22, &restic.ExpirePolicy{Daily: 7, Weekly: 2, Monthly: 3, Yearly: 10}}, 43 } 44 for i, d := range data { 45 isEmpty := d.p.Empty() 46 if isEmpty != d.expectEmpty { 47 t.Errorf("empty test %v: wrong result, want:\n %#v\ngot:\n %#v", i, d.expectEmpty, isEmpty) 48 } 49 hasSum := d.p.Sum() 50 if hasSum != d.expectSum { 51 t.Errorf("sum test %v: wrong result, want:\n %#v\ngot:\n %#v", i, d.expectSum, hasSum) 52 } 53 } 54} 55 56// ApplyPolicyResult is used to marshal/unmarshal the golden files for 57// TestApplyPolicy. 58type ApplyPolicyResult struct { 59 Keep restic.Snapshots `json:"keep"` 60 Reasons []restic.KeepReason `json:"reasons,omitempty"` 61} 62 63func loadGoldenFile(t testing.TB, filename string) (res ApplyPolicyResult) { 64 buf, err := ioutil.ReadFile(filename) 65 if err != nil { 66 t.Fatalf("error loading golden file %v: %v", filename, err) 67 } 68 69 err = json.Unmarshal(buf, &res) 70 if err != nil { 71 t.Fatalf("error unmarshalling golden file %v: %v", filename, err) 72 } 73 74 return res 75} 76 77func saveGoldenFile(t testing.TB, filename string, keep restic.Snapshots, reasons []restic.KeepReason) { 78 res := ApplyPolicyResult{ 79 Keep: keep, 80 Reasons: reasons, 81 } 82 83 buf, err := json.MarshalIndent(res, "", " ") 84 if err != nil { 85 t.Fatalf("error marshaling result: %v", err) 86 } 87 88 if err = ioutil.WriteFile(filename, buf, 0644); err != nil { 89 t.Fatalf("unable to update golden file: %v", err) 90 } 91} 92 93func TestApplyPolicy(t *testing.T) { 94 var testExpireSnapshots = restic.Snapshots{ 95 {Time: parseTimeUTC("2014-09-01 10:20:30")}, 96 {Time: parseTimeUTC("2014-09-02 10:20:30")}, 97 {Time: parseTimeUTC("2014-09-05 10:20:30")}, 98 {Time: parseTimeUTC("2014-09-06 10:20:30")}, 99 {Time: parseTimeUTC("2014-09-08 10:20:30")}, 100 {Time: parseTimeUTC("2014-09-09 10:20:30")}, 101 {Time: parseTimeUTC("2014-09-10 10:20:30")}, 102 {Time: parseTimeUTC("2014-09-11 10:20:30")}, 103 {Time: parseTimeUTC("2014-09-20 10:20:30")}, 104 {Time: parseTimeUTC("2014-09-22 10:20:30")}, 105 {Time: parseTimeUTC("2014-08-08 10:20:30")}, 106 {Time: parseTimeUTC("2014-08-10 10:20:30")}, 107 {Time: parseTimeUTC("2014-08-12 10:20:30")}, 108 {Time: parseTimeUTC("2014-08-13 10:20:30")}, 109 {Time: parseTimeUTC("2014-08-13 10:20:30.1")}, 110 {Time: parseTimeUTC("2014-08-15 10:20:30")}, 111 {Time: parseTimeUTC("2014-08-18 10:20:30")}, 112 {Time: parseTimeUTC("2014-08-20 10:20:30")}, 113 {Time: parseTimeUTC("2014-08-21 10:20:30")}, 114 {Time: parseTimeUTC("2014-08-22 10:20:30")}, 115 {Time: parseTimeUTC("2014-10-01 10:20:30"), Tags: []string{"foo"}}, 116 {Time: parseTimeUTC("2014-10-02 10:20:30"), Tags: []string{"foo"}}, 117 {Time: parseTimeUTC("2014-10-05 10:20:30"), Tags: []string{"foo"}}, 118 {Time: parseTimeUTC("2014-10-06 10:20:30"), Tags: []string{"foo"}}, 119 {Time: parseTimeUTC("2014-10-08 10:20:30"), Tags: []string{"foo"}}, 120 {Time: parseTimeUTC("2014-10-09 10:20:30"), Tags: []string{"foo"}}, 121 {Time: parseTimeUTC("2014-10-10 10:20:30"), Tags: []string{"foo"}}, 122 {Time: parseTimeUTC("2014-10-11 10:20:30"), Tags: []string{"foo"}}, 123 {Time: parseTimeUTC("2014-10-20 10:20:30"), Tags: []string{"foo"}}, 124 {Time: parseTimeUTC("2014-10-22 10:20:30"), Tags: []string{"foo"}}, 125 {Time: parseTimeUTC("2014-11-08 10:20:30"), Tags: []string{"foo"}}, 126 {Time: parseTimeUTC("2014-11-10 10:20:30"), Tags: []string{"foo"}}, 127 {Time: parseTimeUTC("2014-11-12 10:20:30"), Tags: []string{"foo"}}, 128 {Time: parseTimeUTC("2014-11-13 10:20:30"), Tags: []string{"foo"}}, 129 {Time: parseTimeUTC("2014-11-13 10:20:30.1"), Tags: []string{"bar"}}, 130 {Time: parseTimeUTC("2014-11-15 10:20:30"), Tags: []string{"foo", "bar"}}, 131 {Time: parseTimeUTC("2014-11-18 10:20:30")}, 132 {Time: parseTimeUTC("2014-11-20 10:20:30")}, 133 {Time: parseTimeUTC("2014-11-21 10:20:30")}, 134 {Time: parseTimeUTC("2014-11-22 10:20:30")}, 135 {Time: parseTimeUTC("2015-09-01 10:20:30")}, 136 {Time: parseTimeUTC("2015-09-02 10:20:30")}, 137 {Time: parseTimeUTC("2015-09-05 10:20:30")}, 138 {Time: parseTimeUTC("2015-09-06 10:20:30")}, 139 {Time: parseTimeUTC("2015-09-08 10:20:30")}, 140 {Time: parseTimeUTC("2015-09-09 10:20:30")}, 141 {Time: parseTimeUTC("2015-09-10 10:20:30")}, 142 {Time: parseTimeUTC("2015-09-11 10:20:30")}, 143 {Time: parseTimeUTC("2015-09-20 10:20:30")}, 144 {Time: parseTimeUTC("2015-09-22 10:20:30")}, 145 {Time: parseTimeUTC("2015-08-08 10:20:30")}, 146 {Time: parseTimeUTC("2015-08-10 10:20:30")}, 147 {Time: parseTimeUTC("2015-08-12 10:20:30")}, 148 {Time: parseTimeUTC("2015-08-13 10:20:30")}, 149 {Time: parseTimeUTC("2015-08-13 10:20:30.1")}, 150 {Time: parseTimeUTC("2015-08-15 10:20:30")}, 151 {Time: parseTimeUTC("2015-08-18 10:20:30")}, 152 {Time: parseTimeUTC("2015-08-20 10:20:30")}, 153 {Time: parseTimeUTC("2015-08-21 10:20:30")}, 154 {Time: parseTimeUTC("2015-08-22 10:20:30")}, 155 {Time: parseTimeUTC("2015-10-01 10:20:30")}, 156 {Time: parseTimeUTC("2015-10-02 10:20:30")}, 157 {Time: parseTimeUTC("2015-10-05 10:20:30")}, 158 {Time: parseTimeUTC("2015-10-06 10:20:30")}, 159 {Time: parseTimeUTC("2015-10-08 10:20:30")}, 160 {Time: parseTimeUTC("2015-10-09 10:20:30")}, 161 {Time: parseTimeUTC("2015-10-10 10:20:30")}, 162 {Time: parseTimeUTC("2015-10-11 10:20:30")}, 163 {Time: parseTimeUTC("2015-10-20 10:20:30")}, 164 {Time: parseTimeUTC("2015-10-22 10:20:30")}, 165 {Time: parseTimeUTC("2015-10-22 10:20:30")}, 166 {Time: parseTimeUTC("2015-10-22 10:20:30"), Tags: []string{"foo", "bar"}}, 167 {Time: parseTimeUTC("2015-10-22 10:20:30"), Tags: []string{"foo", "bar"}}, 168 {Time: parseTimeUTC("2015-10-22 10:20:30"), Tags: []string{"foo", "bar"}, Paths: []string{"path1", "path2"}}, 169 {Time: parseTimeUTC("2015-11-08 10:20:30")}, 170 {Time: parseTimeUTC("2015-11-10 10:20:30")}, 171 {Time: parseTimeUTC("2015-11-12 10:20:30")}, 172 {Time: parseTimeUTC("2015-11-13 10:20:30")}, 173 {Time: parseTimeUTC("2015-11-13 10:20:30.1")}, 174 {Time: parseTimeUTC("2015-11-15 10:20:30")}, 175 {Time: parseTimeUTC("2015-11-18 10:20:30")}, 176 {Time: parseTimeUTC("2015-11-20 10:20:30")}, 177 {Time: parseTimeUTC("2015-11-21 10:20:30")}, 178 {Time: parseTimeUTC("2015-11-22 10:20:30")}, 179 {Time: parseTimeUTC("2016-01-01 01:02:03")}, 180 {Time: parseTimeUTC("2016-01-01 01:03:03")}, 181 {Time: parseTimeUTC("2016-01-01 07:08:03")}, 182 {Time: parseTimeUTC("2016-01-03 07:02:03")}, 183 {Time: parseTimeUTC("2016-01-04 10:23:03")}, 184 {Time: parseTimeUTC("2016-01-04 11:23:03")}, 185 {Time: parseTimeUTC("2016-01-04 12:23:03")}, 186 {Time: parseTimeUTC("2016-01-04 12:24:03")}, 187 {Time: parseTimeUTC("2016-01-04 12:28:03")}, 188 {Time: parseTimeUTC("2016-01-04 12:30:03")}, 189 {Time: parseTimeUTC("2016-01-04 16:23:03")}, 190 {Time: parseTimeUTC("2016-01-05 09:02:03")}, 191 {Time: parseTimeUTC("2016-01-06 08:02:03")}, 192 {Time: parseTimeUTC("2016-01-07 10:02:03")}, 193 {Time: parseTimeUTC("2016-01-08 20:02:03")}, 194 {Time: parseTimeUTC("2016-01-09 21:02:03")}, 195 {Time: parseTimeUTC("2016-01-12 21:02:03")}, 196 {Time: parseTimeUTC("2016-01-12 21:08:03")}, 197 {Time: parseTimeUTC("2016-01-18 12:02:03")}, 198 } 199 200 var tests = []restic.ExpirePolicy{ 201 {}, 202 {Last: 10}, 203 {Last: 15}, 204 {Last: 99}, 205 {Last: 200}, 206 {Hourly: 20}, 207 {Daily: 3}, 208 {Daily: 10}, 209 {Daily: 30}, 210 {Last: 5, Daily: 5}, 211 {Last: 2, Daily: 10}, 212 {Weekly: 2}, 213 {Weekly: 4}, 214 {Daily: 3, Weekly: 4}, 215 {Monthly: 6}, 216 {Daily: 2, Weekly: 2, Monthly: 6}, 217 {Yearly: 10}, 218 {Daily: 7, Weekly: 2, Monthly: 3, Yearly: 10}, 219 {Tags: []restic.TagList{{"foo"}}}, 220 {Tags: []restic.TagList{{"foo", "bar"}}}, 221 {Tags: []restic.TagList{{"foo"}, {"bar"}}}, 222 {Within: parseDuration("1d")}, 223 {Within: parseDuration("2d")}, 224 {Within: parseDuration("7d")}, 225 {Within: parseDuration("1m")}, 226 {Within: parseDuration("1m14d")}, 227 {Within: parseDuration("1y1d1m")}, 228 {Within: parseDuration("13d23h")}, 229 {Within: parseDuration("2m2h")}, 230 {Within: parseDuration("1y2m3d3h")}, 231 {WithinHourly: parseDuration("1y2m3d3h")}, 232 {WithinDaily: parseDuration("1y2m3d3h")}, 233 {WithinWeekly: parseDuration("1y2m3d3h")}, 234 {WithinMonthly: parseDuration("1y2m3d3h")}, 235 {WithinYearly: parseDuration("1y2m3d3h")}, 236 {Within: parseDuration("1h"), 237 WithinHourly: parseDuration("1d"), 238 WithinDaily: parseDuration("7d"), 239 WithinWeekly: parseDuration("1m"), 240 WithinMonthly: parseDuration("1y"), 241 WithinYearly: parseDuration("9999y")}, 242 } 243 244 for i, p := range tests { 245 t.Run("", func(t *testing.T) { 246 247 keep, remove, reasons := restic.ApplyPolicy(testExpireSnapshots, p) 248 249 if len(keep)+len(remove) != len(testExpireSnapshots) { 250 t.Errorf("len(keep)+len(remove) = %d != len(testExpireSnapshots) = %d", 251 len(keep)+len(remove), len(testExpireSnapshots)) 252 } 253 254 if p.Sum() > 0 && len(keep) > p.Sum() { 255 t.Errorf("not enough snapshots removed: policy allows %v snapshots to remain, but ended up with %v", 256 p.Sum(), len(keep)) 257 } 258 259 if len(keep) != len(reasons) { 260 t.Errorf("got %d keep reasons for %d snapshots to keep, these must be equal", len(reasons), len(keep)) 261 } 262 263 goldenFilename := filepath.Join("testdata", fmt.Sprintf("policy_keep_snapshots_%d", i)) 264 265 if *updateGoldenFiles { 266 saveGoldenFile(t, goldenFilename, keep, reasons) 267 } 268 269 want := loadGoldenFile(t, goldenFilename) 270 271 cmpOpts := cmpopts.IgnoreUnexported(restic.Snapshot{}) 272 273 if !cmp.Equal(want.Keep, keep, cmpOpts) { 274 t.Error(cmp.Diff(want.Keep, keep, cmpOpts)) 275 } 276 277 if !cmp.Equal(want.Reasons, reasons, cmpOpts) { 278 t.Error(cmp.Diff(want.Reasons, reasons, cmpOpts)) 279 } 280 }) 281 } 282} 283