github.com/advanderveer/restic@v0.8.1-0.20171209104529-42a8c19aaea6/cmd/restic/integration_test.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "crypto/rand" 7 "encoding/json" 8 "fmt" 9 "io" 10 "io/ioutil" 11 mrand "math/rand" 12 "os" 13 "path/filepath" 14 "regexp" 15 "strings" 16 "syscall" 17 "testing" 18 "time" 19 20 "github.com/restic/restic/internal/debug" 21 "github.com/restic/restic/internal/errors" 22 "github.com/restic/restic/internal/filter" 23 "github.com/restic/restic/internal/repository" 24 "github.com/restic/restic/internal/restic" 25 rtest "github.com/restic/restic/internal/test" 26 ) 27 28 func parseIDsFromReader(t testing.TB, rd io.Reader) restic.IDs { 29 IDs := restic.IDs{} 30 sc := bufio.NewScanner(rd) 31 32 for sc.Scan() { 33 id, err := restic.ParseID(sc.Text()) 34 if err != nil { 35 t.Logf("parse id %v: %v", sc.Text(), err) 36 continue 37 } 38 39 IDs = append(IDs, id) 40 } 41 42 return IDs 43 } 44 45 func testRunInit(t testing.TB, opts GlobalOptions) { 46 repository.TestUseLowSecurityKDFParameters(t) 47 restic.TestSetLockTimeout(t, 0) 48 49 rtest.OK(t, runInit(opts, nil)) 50 t.Logf("repository initialized at %v", opts.Repo) 51 } 52 53 func testRunBackup(t testing.TB, target []string, opts BackupOptions, gopts GlobalOptions) { 54 t.Logf("backing up %v", target) 55 rtest.OK(t, runBackup(opts, gopts, target)) 56 } 57 58 func testRunList(t testing.TB, tpe string, opts GlobalOptions) restic.IDs { 59 buf := bytes.NewBuffer(nil) 60 globalOptions.stdout = buf 61 defer func() { 62 globalOptions.stdout = os.Stdout 63 }() 64 65 rtest.OK(t, runList(opts, []string{tpe})) 66 return parseIDsFromReader(t, buf) 67 } 68 69 func testRunRestore(t testing.TB, opts GlobalOptions, dir string, snapshotID restic.ID) { 70 testRunRestoreExcludes(t, opts, dir, snapshotID, nil) 71 } 72 73 func testRunRestoreLatest(t testing.TB, gopts GlobalOptions, dir string, paths []string, host string) { 74 opts := RestoreOptions{ 75 Target: dir, 76 Host: host, 77 Paths: paths, 78 } 79 80 rtest.OK(t, runRestore(opts, gopts, []string{"latest"})) 81 } 82 83 func testRunRestoreExcludes(t testing.TB, gopts GlobalOptions, dir string, snapshotID restic.ID, excludes []string) { 84 opts := RestoreOptions{ 85 Target: dir, 86 Exclude: excludes, 87 } 88 89 rtest.OK(t, runRestore(opts, gopts, []string{snapshotID.String()})) 90 } 91 92 func testRunRestoreIncludes(t testing.TB, gopts GlobalOptions, dir string, snapshotID restic.ID, includes []string) { 93 opts := RestoreOptions{ 94 Target: dir, 95 Include: includes, 96 } 97 98 rtest.OK(t, runRestore(opts, gopts, []string{snapshotID.String()})) 99 } 100 101 func testRunCheck(t testing.TB, gopts GlobalOptions) { 102 opts := CheckOptions{ 103 ReadData: true, 104 CheckUnused: true, 105 } 106 rtest.OK(t, runCheck(opts, gopts, nil)) 107 } 108 109 func testRunCheckOutput(gopts GlobalOptions) (string, error) { 110 buf := bytes.NewBuffer(nil) 111 112 globalOptions.stdout = buf 113 defer func() { 114 globalOptions.stdout = os.Stdout 115 }() 116 117 opts := CheckOptions{ 118 ReadData: true, 119 } 120 121 err := runCheck(opts, gopts, nil) 122 return string(buf.Bytes()), err 123 } 124 125 func testRunRebuildIndex(t testing.TB, gopts GlobalOptions) { 126 globalOptions.stdout = ioutil.Discard 127 defer func() { 128 globalOptions.stdout = os.Stdout 129 }() 130 131 rtest.OK(t, runRebuildIndex(gopts)) 132 } 133 134 func testRunLs(t testing.TB, gopts GlobalOptions, snapshotID string) []string { 135 buf := bytes.NewBuffer(nil) 136 globalOptions.stdout = buf 137 quiet := globalOptions.Quiet 138 globalOptions.Quiet = true 139 defer func() { 140 globalOptions.stdout = os.Stdout 141 globalOptions.Quiet = quiet 142 }() 143 144 opts := LsOptions{} 145 146 rtest.OK(t, runLs(opts, gopts, []string{snapshotID})) 147 148 return strings.Split(string(buf.Bytes()), "\n") 149 } 150 151 func testRunFind(t testing.TB, wantJSON bool, gopts GlobalOptions, pattern string) []byte { 152 buf := bytes.NewBuffer(nil) 153 globalOptions.stdout = buf 154 globalOptions.JSON = wantJSON 155 defer func() { 156 globalOptions.stdout = os.Stdout 157 globalOptions.JSON = false 158 }() 159 160 opts := FindOptions{} 161 162 rtest.OK(t, runFind(opts, gopts, []string{pattern})) 163 164 return buf.Bytes() 165 } 166 167 func testRunSnapshots(t testing.TB, gopts GlobalOptions) (newest *Snapshot, snapmap map[restic.ID]Snapshot) { 168 buf := bytes.NewBuffer(nil) 169 globalOptions.stdout = buf 170 globalOptions.JSON = true 171 defer func() { 172 globalOptions.stdout = os.Stdout 173 globalOptions.JSON = gopts.JSON 174 }() 175 176 opts := SnapshotOptions{} 177 178 rtest.OK(t, runSnapshots(opts, globalOptions, []string{})) 179 180 snapshots := []Snapshot{} 181 rtest.OK(t, json.Unmarshal(buf.Bytes(), &snapshots)) 182 183 snapmap = make(map[restic.ID]Snapshot, len(snapshots)) 184 for _, sn := range snapshots { 185 snapmap[*sn.ID] = sn 186 if newest == nil || sn.Time.After(newest.Time) { 187 newest = &sn 188 } 189 } 190 return 191 } 192 193 func testRunForget(t testing.TB, gopts GlobalOptions, args ...string) { 194 opts := ForgetOptions{} 195 rtest.OK(t, runForget(opts, gopts, args)) 196 } 197 198 func testRunPrune(t testing.TB, gopts GlobalOptions) { 199 rtest.OK(t, runPrune(gopts)) 200 } 201 202 func TestBackup(t *testing.T) { 203 env, cleanup := withTestEnvironment(t) 204 defer cleanup() 205 206 datafile := filepath.Join("testdata", "backup-data.tar.gz") 207 fd, err := os.Open(datafile) 208 if os.IsNotExist(errors.Cause(err)) { 209 t.Skipf("unable to find data file %q, skipping", datafile) 210 return 211 } 212 rtest.OK(t, err) 213 rtest.OK(t, fd.Close()) 214 215 testRunInit(t, env.gopts) 216 217 rtest.SetupTarTestFixture(t, env.testdata, datafile) 218 opts := BackupOptions{} 219 220 // first backup 221 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 222 snapshotIDs := testRunList(t, "snapshots", env.gopts) 223 rtest.Assert(t, len(snapshotIDs) == 1, 224 "expected one snapshot, got %v", snapshotIDs) 225 226 testRunCheck(t, env.gopts) 227 stat1 := dirStats(env.repo) 228 229 // second backup, implicit incremental 230 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 231 snapshotIDs = testRunList(t, "snapshots", env.gopts) 232 rtest.Assert(t, len(snapshotIDs) == 2, 233 "expected two snapshots, got %v", snapshotIDs) 234 235 stat2 := dirStats(env.repo) 236 if stat2.size > stat1.size+stat1.size/10 { 237 t.Error("repository size has grown by more than 10 percent") 238 } 239 t.Logf("repository grown by %d bytes", stat2.size-stat1.size) 240 241 testRunCheck(t, env.gopts) 242 // third backup, explicit incremental 243 opts.Parent = snapshotIDs[0].String() 244 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 245 snapshotIDs = testRunList(t, "snapshots", env.gopts) 246 rtest.Assert(t, len(snapshotIDs) == 3, 247 "expected three snapshots, got %v", snapshotIDs) 248 249 stat3 := dirStats(env.repo) 250 if stat3.size > stat1.size+stat1.size/10 { 251 t.Error("repository size has grown by more than 10 percent") 252 } 253 t.Logf("repository grown by %d bytes", stat3.size-stat2.size) 254 255 // restore all backups and compare 256 for i, snapshotID := range snapshotIDs { 257 restoredir := filepath.Join(env.base, fmt.Sprintf("restore%d", i)) 258 t.Logf("restoring snapshot %v to %v", snapshotID.Str(), restoredir) 259 testRunRestore(t, env.gopts, restoredir, snapshotIDs[0]) 260 rtest.Assert(t, directoriesEqualContents(env.testdata, filepath.Join(restoredir, "testdata")), 261 "directories are not equal") 262 } 263 264 testRunCheck(t, env.gopts) 265 } 266 267 func TestBackupNonExistingFile(t *testing.T) { 268 env, cleanup := withTestEnvironment(t) 269 defer cleanup() 270 271 datafile := filepath.Join("testdata", "backup-data.tar.gz") 272 fd, err := os.Open(datafile) 273 if os.IsNotExist(errors.Cause(err)) { 274 t.Skipf("unable to find data file %q, skipping", datafile) 275 return 276 } 277 rtest.OK(t, err) 278 rtest.OK(t, fd.Close()) 279 280 rtest.SetupTarTestFixture(t, env.testdata, datafile) 281 282 testRunInit(t, env.gopts) 283 globalOptions.stderr = ioutil.Discard 284 defer func() { 285 globalOptions.stderr = os.Stderr 286 }() 287 288 p := filepath.Join(env.testdata, "0", "0") 289 dirs := []string{ 290 filepath.Join(p, "0"), 291 filepath.Join(p, "1"), 292 filepath.Join(p, "nonexisting"), 293 filepath.Join(p, "5"), 294 } 295 296 opts := BackupOptions{} 297 298 testRunBackup(t, dirs, opts, env.gopts) 299 } 300 301 func TestBackupMissingFile1(t *testing.T) { 302 env, cleanup := withTestEnvironment(t) 303 defer cleanup() 304 305 datafile := filepath.Join("testdata", "backup-data.tar.gz") 306 fd, err := os.Open(datafile) 307 if os.IsNotExist(errors.Cause(err)) { 308 t.Skipf("unable to find data file %q, skipping", datafile) 309 return 310 } 311 rtest.OK(t, err) 312 rtest.OK(t, fd.Close()) 313 314 rtest.SetupTarTestFixture(t, env.testdata, datafile) 315 316 testRunInit(t, env.gopts) 317 globalOptions.stderr = ioutil.Discard 318 defer func() { 319 globalOptions.stderr = os.Stderr 320 }() 321 322 ranHook := false 323 debug.Hook("pipe.walk1", func(context interface{}) { 324 pathname := context.(string) 325 326 if pathname != filepath.Join("testdata", "0", "0", "9") { 327 return 328 } 329 330 t.Logf("in hook, removing test file testdata/0/0/9/37") 331 ranHook = true 332 333 rtest.OK(t, os.Remove(filepath.Join(env.testdata, "0", "0", "9", "37"))) 334 }) 335 336 opts := BackupOptions{} 337 338 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 339 testRunCheck(t, env.gopts) 340 341 rtest.Assert(t, ranHook, "hook did not run") 342 debug.RemoveHook("pipe.walk1") 343 } 344 345 func TestBackupMissingFile2(t *testing.T) { 346 env, cleanup := withTestEnvironment(t) 347 defer cleanup() 348 349 datafile := filepath.Join("testdata", "backup-data.tar.gz") 350 fd, err := os.Open(datafile) 351 if os.IsNotExist(errors.Cause(err)) { 352 t.Skipf("unable to find data file %q, skipping", datafile) 353 return 354 } 355 rtest.OK(t, err) 356 rtest.OK(t, fd.Close()) 357 358 rtest.SetupTarTestFixture(t, env.testdata, datafile) 359 360 testRunInit(t, env.gopts) 361 362 globalOptions.stderr = ioutil.Discard 363 defer func() { 364 globalOptions.stderr = os.Stderr 365 }() 366 367 ranHook := false 368 debug.Hook("pipe.walk2", func(context interface{}) { 369 pathname := context.(string) 370 371 if pathname != filepath.Join("testdata", "0", "0", "9", "37") { 372 return 373 } 374 375 t.Logf("in hook, removing test file testdata/0/0/9/37") 376 ranHook = true 377 378 rtest.OK(t, os.Remove(filepath.Join(env.testdata, "0", "0", "9", "37"))) 379 }) 380 381 opts := BackupOptions{} 382 383 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 384 testRunCheck(t, env.gopts) 385 386 rtest.Assert(t, ranHook, "hook did not run") 387 debug.RemoveHook("pipe.walk2") 388 } 389 390 func TestBackupChangedFile(t *testing.T) { 391 env, cleanup := withTestEnvironment(t) 392 defer cleanup() 393 394 datafile := filepath.Join("testdata", "backup-data.tar.gz") 395 fd, err := os.Open(datafile) 396 if os.IsNotExist(errors.Cause(err)) { 397 t.Skipf("unable to find data file %q, skipping", datafile) 398 return 399 } 400 rtest.OK(t, err) 401 rtest.OK(t, fd.Close()) 402 403 rtest.SetupTarTestFixture(t, env.testdata, datafile) 404 405 testRunInit(t, env.gopts) 406 407 globalOptions.stderr = ioutil.Discard 408 defer func() { 409 globalOptions.stderr = os.Stderr 410 }() 411 412 modFile := filepath.Join(env.testdata, "0", "0", "6", "18") 413 414 ranHook := false 415 debug.Hook("archiver.SaveFile", func(context interface{}) { 416 pathname := context.(string) 417 418 if pathname != modFile { 419 return 420 } 421 422 t.Logf("in hook, modifying test file %v", modFile) 423 ranHook = true 424 425 rtest.OK(t, ioutil.WriteFile(modFile, []byte("modified"), 0600)) 426 }) 427 428 opts := BackupOptions{} 429 430 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 431 testRunCheck(t, env.gopts) 432 433 rtest.Assert(t, ranHook, "hook did not run") 434 debug.RemoveHook("archiver.SaveFile") 435 } 436 437 func TestBackupDirectoryError(t *testing.T) { 438 env, cleanup := withTestEnvironment(t) 439 defer cleanup() 440 441 datafile := filepath.Join("testdata", "backup-data.tar.gz") 442 fd, err := os.Open(datafile) 443 if os.IsNotExist(errors.Cause(err)) { 444 t.Skipf("unable to find data file %q, skipping", datafile) 445 return 446 } 447 rtest.OK(t, err) 448 rtest.OK(t, fd.Close()) 449 450 rtest.SetupTarTestFixture(t, env.testdata, datafile) 451 452 testRunInit(t, env.gopts) 453 454 globalOptions.stderr = ioutil.Discard 455 defer func() { 456 globalOptions.stderr = os.Stderr 457 }() 458 459 ranHook := false 460 461 testdir := filepath.Join(env.testdata, "0", "0", "9") 462 463 // install hook that removes the dir right before readdirnames() 464 debug.Hook("pipe.readdirnames", func(context interface{}) { 465 path := context.(string) 466 467 if path != testdir { 468 return 469 } 470 471 t.Logf("in hook, removing test file %v", testdir) 472 ranHook = true 473 474 rtest.OK(t, os.RemoveAll(testdir)) 475 }) 476 477 testRunBackup(t, []string{filepath.Join(env.testdata, "0", "0")}, BackupOptions{}, env.gopts) 478 testRunCheck(t, env.gopts) 479 480 rtest.Assert(t, ranHook, "hook did not run") 481 debug.RemoveHook("pipe.walk2") 482 483 snapshots := testRunList(t, "snapshots", env.gopts) 484 rtest.Assert(t, len(snapshots) > 0, 485 "no snapshots found in repo (%v)", datafile) 486 487 files := testRunLs(t, env.gopts, snapshots[0].String()) 488 489 rtest.Assert(t, len(files) > 1, "snapshot is empty") 490 } 491 492 func includes(haystack []string, needle string) bool { 493 for _, s := range haystack { 494 if s == needle { 495 return true 496 } 497 } 498 499 return false 500 } 501 502 func loadSnapshotMap(t testing.TB, gopts GlobalOptions) map[string]struct{} { 503 snapshotIDs := testRunList(t, "snapshots", gopts) 504 505 m := make(map[string]struct{}) 506 for _, id := range snapshotIDs { 507 m[id.String()] = struct{}{} 508 } 509 510 return m 511 } 512 513 func lastSnapshot(old, new map[string]struct{}) (map[string]struct{}, string) { 514 for k := range new { 515 if _, ok := old[k]; !ok { 516 old[k] = struct{}{} 517 return old, k 518 } 519 } 520 521 return old, "" 522 } 523 524 var backupExcludeFilenames = []string{ 525 "testfile1", 526 "foo.tar.gz", 527 "private/secret/passwords.txt", 528 "work/source/test.c", 529 } 530 531 func TestBackupExclude(t *testing.T) { 532 env, cleanup := withTestEnvironment(t) 533 defer cleanup() 534 535 testRunInit(t, env.gopts) 536 537 datadir := filepath.Join(env.base, "testdata") 538 539 for _, filename := range backupExcludeFilenames { 540 fp := filepath.Join(datadir, filename) 541 rtest.OK(t, os.MkdirAll(filepath.Dir(fp), 0755)) 542 543 f, err := os.Create(fp) 544 rtest.OK(t, err) 545 546 fmt.Fprintf(f, filename) 547 rtest.OK(t, f.Close()) 548 } 549 550 snapshots := make(map[string]struct{}) 551 552 opts := BackupOptions{} 553 554 testRunBackup(t, []string{datadir}, opts, env.gopts) 555 snapshots, snapshotID := lastSnapshot(snapshots, loadSnapshotMap(t, env.gopts)) 556 files := testRunLs(t, env.gopts, snapshotID) 557 rtest.Assert(t, includes(files, filepath.Join(string(filepath.Separator), "testdata", "foo.tar.gz")), 558 "expected file %q in first snapshot, but it's not included", "foo.tar.gz") 559 560 opts.Excludes = []string{"*.tar.gz"} 561 testRunBackup(t, []string{datadir}, opts, env.gopts) 562 snapshots, snapshotID = lastSnapshot(snapshots, loadSnapshotMap(t, env.gopts)) 563 files = testRunLs(t, env.gopts, snapshotID) 564 rtest.Assert(t, !includes(files, filepath.Join(string(filepath.Separator), "testdata", "foo.tar.gz")), 565 "expected file %q not in first snapshot, but it's included", "foo.tar.gz") 566 567 opts.Excludes = []string{"*.tar.gz", "private/secret"} 568 testRunBackup(t, []string{datadir}, opts, env.gopts) 569 _, snapshotID = lastSnapshot(snapshots, loadSnapshotMap(t, env.gopts)) 570 files = testRunLs(t, env.gopts, snapshotID) 571 rtest.Assert(t, !includes(files, filepath.Join(string(filepath.Separator), "testdata", "foo.tar.gz")), 572 "expected file %q not in first snapshot, but it's included", "foo.tar.gz") 573 rtest.Assert(t, !includes(files, filepath.Join(string(filepath.Separator), "testdata", "private", "secret", "passwords.txt")), 574 "expected file %q not in first snapshot, but it's included", "passwords.txt") 575 } 576 577 const ( 578 incrementalFirstWrite = 20 * 1042 * 1024 579 incrementalSecondWrite = 12 * 1042 * 1024 580 incrementalThirdWrite = 4 * 1042 * 1024 581 ) 582 583 func appendRandomData(filename string, bytes uint) error { 584 f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0666) 585 if err != nil { 586 fmt.Fprint(os.Stderr, err) 587 return err 588 } 589 590 _, err = f.Seek(0, 2) 591 if err != nil { 592 fmt.Fprint(os.Stderr, err) 593 return err 594 } 595 596 _, err = io.Copy(f, io.LimitReader(rand.Reader, int64(bytes))) 597 if err != nil { 598 fmt.Fprint(os.Stderr, err) 599 return err 600 } 601 602 return f.Close() 603 } 604 605 func TestIncrementalBackup(t *testing.T) { 606 env, cleanup := withTestEnvironment(t) 607 defer cleanup() 608 609 testRunInit(t, env.gopts) 610 611 datadir := filepath.Join(env.base, "testdata") 612 testfile := filepath.Join(datadir, "testfile") 613 614 rtest.OK(t, appendRandomData(testfile, incrementalFirstWrite)) 615 616 opts := BackupOptions{} 617 618 testRunBackup(t, []string{datadir}, opts, env.gopts) 619 testRunCheck(t, env.gopts) 620 stat1 := dirStats(env.repo) 621 622 rtest.OK(t, appendRandomData(testfile, incrementalSecondWrite)) 623 624 testRunBackup(t, []string{datadir}, opts, env.gopts) 625 testRunCheck(t, env.gopts) 626 stat2 := dirStats(env.repo) 627 if stat2.size-stat1.size > incrementalFirstWrite { 628 t.Errorf("repository size has grown by more than %d bytes", incrementalFirstWrite) 629 } 630 t.Logf("repository grown by %d bytes", stat2.size-stat1.size) 631 632 rtest.OK(t, appendRandomData(testfile, incrementalThirdWrite)) 633 634 testRunBackup(t, []string{datadir}, opts, env.gopts) 635 testRunCheck(t, env.gopts) 636 stat3 := dirStats(env.repo) 637 if stat3.size-stat2.size > incrementalFirstWrite { 638 t.Errorf("repository size has grown by more than %d bytes", incrementalFirstWrite) 639 } 640 t.Logf("repository grown by %d bytes", stat3.size-stat2.size) 641 } 642 643 func TestBackupTags(t *testing.T) { 644 env, cleanup := withTestEnvironment(t) 645 defer cleanup() 646 647 datafile := filepath.Join("testdata", "backup-data.tar.gz") 648 testRunInit(t, env.gopts) 649 rtest.SetupTarTestFixture(t, env.testdata, datafile) 650 651 opts := BackupOptions{} 652 653 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 654 testRunCheck(t, env.gopts) 655 newest, _ := testRunSnapshots(t, env.gopts) 656 rtest.Assert(t, newest != nil, "expected a new backup, got nil") 657 rtest.Assert(t, len(newest.Tags) == 0, 658 "expected no tags, got %v", newest.Tags) 659 parent := newest 660 661 opts.Tags = []string{"NL"} 662 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 663 testRunCheck(t, env.gopts) 664 newest, _ = testRunSnapshots(t, env.gopts) 665 rtest.Assert(t, newest != nil, "expected a new backup, got nil") 666 rtest.Assert(t, len(newest.Tags) == 1 && newest.Tags[0] == "NL", 667 "expected one NL tag, got %v", newest.Tags) 668 // Tagged backup should have untagged backup as parent. 669 rtest.Assert(t, parent.ID.Equal(*newest.Parent), 670 "expected parent to be %v, got %v", parent.ID, newest.Parent) 671 } 672 673 func testRunTag(t testing.TB, opts TagOptions, gopts GlobalOptions) { 674 rtest.OK(t, runTag(opts, gopts, []string{})) 675 } 676 677 func TestTag(t *testing.T) { 678 env, cleanup := withTestEnvironment(t) 679 defer cleanup() 680 681 datafile := filepath.Join("testdata", "backup-data.tar.gz") 682 testRunInit(t, env.gopts) 683 rtest.SetupTarTestFixture(t, env.testdata, datafile) 684 685 testRunBackup(t, []string{env.testdata}, BackupOptions{}, env.gopts) 686 testRunCheck(t, env.gopts) 687 newest, _ := testRunSnapshots(t, env.gopts) 688 rtest.Assert(t, newest != nil, "expected a new backup, got nil") 689 rtest.Assert(t, len(newest.Tags) == 0, 690 "expected no tags, got %v", newest.Tags) 691 rtest.Assert(t, newest.Original == nil, 692 "expected original ID to be nil, got %v", newest.Original) 693 originalID := *newest.ID 694 695 testRunTag(t, TagOptions{SetTags: []string{"NL"}}, env.gopts) 696 testRunCheck(t, env.gopts) 697 newest, _ = testRunSnapshots(t, env.gopts) 698 rtest.Assert(t, newest != nil, "expected a new backup, got nil") 699 rtest.Assert(t, len(newest.Tags) == 1 && newest.Tags[0] == "NL", 700 "set failed, expected one NL tag, got %v", newest.Tags) 701 rtest.Assert(t, newest.Original != nil, "expected original snapshot id, got nil") 702 rtest.Assert(t, *newest.Original == originalID, 703 "expected original ID to be set to the first snapshot id") 704 705 testRunTag(t, TagOptions{AddTags: []string{"CH"}}, env.gopts) 706 testRunCheck(t, env.gopts) 707 newest, _ = testRunSnapshots(t, env.gopts) 708 rtest.Assert(t, newest != nil, "expected a new backup, got nil") 709 rtest.Assert(t, len(newest.Tags) == 2 && newest.Tags[0] == "NL" && newest.Tags[1] == "CH", 710 "add failed, expected CH,NL tags, got %v", newest.Tags) 711 rtest.Assert(t, newest.Original != nil, "expected original snapshot id, got nil") 712 rtest.Assert(t, *newest.Original == originalID, 713 "expected original ID to be set to the first snapshot id") 714 715 testRunTag(t, TagOptions{RemoveTags: []string{"NL"}}, env.gopts) 716 testRunCheck(t, env.gopts) 717 newest, _ = testRunSnapshots(t, env.gopts) 718 rtest.Assert(t, newest != nil, "expected a new backup, got nil") 719 rtest.Assert(t, len(newest.Tags) == 1 && newest.Tags[0] == "CH", 720 "remove failed, expected one CH tag, got %v", newest.Tags) 721 rtest.Assert(t, newest.Original != nil, "expected original snapshot id, got nil") 722 rtest.Assert(t, *newest.Original == originalID, 723 "expected original ID to be set to the first snapshot id") 724 725 testRunTag(t, TagOptions{AddTags: []string{"US", "RU"}}, env.gopts) 726 testRunTag(t, TagOptions{RemoveTags: []string{"CH", "US", "RU"}}, env.gopts) 727 testRunCheck(t, env.gopts) 728 newest, _ = testRunSnapshots(t, env.gopts) 729 rtest.Assert(t, newest != nil, "expected a new backup, got nil") 730 rtest.Assert(t, len(newest.Tags) == 0, 731 "expected no tags, got %v", newest.Tags) 732 rtest.Assert(t, newest.Original != nil, "expected original snapshot id, got nil") 733 rtest.Assert(t, *newest.Original == originalID, 734 "expected original ID to be set to the first snapshot id") 735 736 // Check special case of removing all tags. 737 testRunTag(t, TagOptions{SetTags: []string{""}}, env.gopts) 738 testRunCheck(t, env.gopts) 739 newest, _ = testRunSnapshots(t, env.gopts) 740 rtest.Assert(t, newest != nil, "expected a new backup, got nil") 741 rtest.Assert(t, len(newest.Tags) == 0, 742 "expected no tags, got %v", newest.Tags) 743 rtest.Assert(t, newest.Original != nil, "expected original snapshot id, got nil") 744 rtest.Assert(t, *newest.Original == originalID, 745 "expected original ID to be set to the first snapshot id") 746 } 747 748 func testRunKeyListOtherIDs(t testing.TB, gopts GlobalOptions) []string { 749 buf := bytes.NewBuffer(nil) 750 751 globalOptions.stdout = buf 752 defer func() { 753 globalOptions.stdout = os.Stdout 754 }() 755 756 rtest.OK(t, runKey(gopts, []string{"list"})) 757 758 scanner := bufio.NewScanner(buf) 759 exp := regexp.MustCompile(`^ ([a-f0-9]+) `) 760 761 IDs := []string{} 762 for scanner.Scan() { 763 if id := exp.FindStringSubmatch(scanner.Text()); id != nil { 764 IDs = append(IDs, id[1]) 765 } 766 } 767 768 return IDs 769 } 770 771 func testRunKeyAddNewKey(t testing.TB, newPassword string, gopts GlobalOptions) { 772 testKeyNewPassword = newPassword 773 defer func() { 774 testKeyNewPassword = "" 775 }() 776 777 rtest.OK(t, runKey(gopts, []string{"add"})) 778 } 779 780 func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) { 781 testKeyNewPassword = newPassword 782 defer func() { 783 testKeyNewPassword = "" 784 }() 785 786 rtest.OK(t, runKey(gopts, []string{"passwd"})) 787 } 788 789 func testRunKeyRemove(t testing.TB, gopts GlobalOptions, IDs []string) { 790 t.Logf("remove %d keys: %q\n", len(IDs), IDs) 791 for _, id := range IDs { 792 rtest.OK(t, runKey(gopts, []string{"remove", id})) 793 } 794 } 795 796 func TestKeyAddRemove(t *testing.T) { 797 passwordList := []string{ 798 "OnnyiasyatvodsEvVodyawit", 799 "raicneirvOjEfEigonOmLasOd", 800 } 801 802 env, cleanup := withTestEnvironment(t) 803 defer cleanup() 804 805 testRunInit(t, env.gopts) 806 807 testRunKeyPasswd(t, "geheim2", env.gopts) 808 env.gopts.password = "geheim2" 809 t.Logf("changed password to %q", env.gopts.password) 810 811 for _, newPassword := range passwordList { 812 testRunKeyAddNewKey(t, newPassword, env.gopts) 813 t.Logf("added new password %q", newPassword) 814 env.gopts.password = newPassword 815 testRunKeyRemove(t, env.gopts, testRunKeyListOtherIDs(t, env.gopts)) 816 } 817 818 env.gopts.password = passwordList[len(passwordList)-1] 819 t.Logf("testing access with last password %q\n", env.gopts.password) 820 rtest.OK(t, runKey(env.gopts, []string{"list"})) 821 testRunCheck(t, env.gopts) 822 } 823 824 func testFileSize(filename string, size int64) error { 825 fi, err := os.Stat(filename) 826 if err != nil { 827 return err 828 } 829 830 if fi.Size() != size { 831 return errors.Fatalf("wrong file size for %v: expected %v, got %v", filename, size, fi.Size()) 832 } 833 834 return nil 835 } 836 837 func TestRestoreFilter(t *testing.T) { 838 testfiles := []struct { 839 name string 840 size uint 841 }{ 842 {"testfile1.c", 100}, 843 {"testfile2.exe", 101}, 844 {"subdir1/subdir2/testfile3.docx", 102}, 845 {"subdir1/subdir2/testfile4.c", 102}, 846 } 847 848 env, cleanup := withTestEnvironment(t) 849 defer cleanup() 850 851 testRunInit(t, env.gopts) 852 853 for _, testFile := range testfiles { 854 p := filepath.Join(env.testdata, testFile.name) 855 rtest.OK(t, os.MkdirAll(filepath.Dir(p), 0755)) 856 rtest.OK(t, appendRandomData(p, testFile.size)) 857 } 858 859 opts := BackupOptions{} 860 861 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 862 testRunCheck(t, env.gopts) 863 864 snapshotID := testRunList(t, "snapshots", env.gopts)[0] 865 866 // no restore filter should restore all files 867 testRunRestore(t, env.gopts, filepath.Join(env.base, "restore0"), snapshotID) 868 for _, testFile := range testfiles { 869 rtest.OK(t, testFileSize(filepath.Join(env.base, "restore0", "testdata", testFile.name), int64(testFile.size))) 870 } 871 872 for i, pat := range []string{"*.c", "*.exe", "*", "*file3*"} { 873 base := filepath.Join(env.base, fmt.Sprintf("restore%d", i+1)) 874 testRunRestoreExcludes(t, env.gopts, base, snapshotID, []string{pat}) 875 for _, testFile := range testfiles { 876 err := testFileSize(filepath.Join(base, "testdata", testFile.name), int64(testFile.size)) 877 if ok, _ := filter.Match(pat, filepath.Base(testFile.name)); !ok { 878 rtest.OK(t, err) 879 } else { 880 rtest.Assert(t, os.IsNotExist(errors.Cause(err)), 881 "expected %v to not exist in restore step %v, but it exists, err %v", testFile.name, i+1, err) 882 } 883 } 884 } 885 } 886 887 func TestRestore(t *testing.T) { 888 env, cleanup := withTestEnvironment(t) 889 defer cleanup() 890 891 testRunInit(t, env.gopts) 892 893 for i := 0; i < 10; i++ { 894 p := filepath.Join(env.testdata, fmt.Sprintf("foo/bar/testfile%v", i)) 895 rtest.OK(t, os.MkdirAll(filepath.Dir(p), 0755)) 896 rtest.OK(t, appendRandomData(p, uint(mrand.Intn(5<<21)))) 897 } 898 899 opts := BackupOptions{} 900 901 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 902 testRunCheck(t, env.gopts) 903 904 // Restore latest without any filters 905 restoredir := filepath.Join(env.base, "restore") 906 testRunRestoreLatest(t, env.gopts, restoredir, nil, "") 907 908 rtest.Assert(t, directoriesEqualContents(env.testdata, filepath.Join(restoredir, filepath.Base(env.testdata))), 909 "directories are not equal") 910 } 911 912 func TestRestoreLatest(t *testing.T) { 913 env, cleanup := withTestEnvironment(t) 914 defer cleanup() 915 916 testRunInit(t, env.gopts) 917 918 p := filepath.Join(env.testdata, "testfile.c") 919 rtest.OK(t, os.MkdirAll(filepath.Dir(p), 0755)) 920 rtest.OK(t, appendRandomData(p, 100)) 921 922 opts := BackupOptions{} 923 924 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 925 testRunCheck(t, env.gopts) 926 927 os.Remove(p) 928 rtest.OK(t, appendRandomData(p, 101)) 929 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 930 testRunCheck(t, env.gopts) 931 932 // Restore latest without any filters 933 testRunRestoreLatest(t, env.gopts, filepath.Join(env.base, "restore0"), nil, "") 934 rtest.OK(t, testFileSize(filepath.Join(env.base, "restore0", "testdata", "testfile.c"), int64(101))) 935 936 // Setup test files in different directories backed up in different snapshots 937 p1 := filepath.Join(env.testdata, "p1/testfile.c") 938 rtest.OK(t, os.MkdirAll(filepath.Dir(p1), 0755)) 939 rtest.OK(t, appendRandomData(p1, 102)) 940 testRunBackup(t, []string{filepath.Dir(p1)}, opts, env.gopts) 941 testRunCheck(t, env.gopts) 942 943 p2 := filepath.Join(env.testdata, "p2/testfile.c") 944 rtest.OK(t, os.MkdirAll(filepath.Dir(p2), 0755)) 945 rtest.OK(t, appendRandomData(p2, 103)) 946 testRunBackup(t, []string{filepath.Dir(p2)}, opts, env.gopts) 947 testRunCheck(t, env.gopts) 948 949 p1rAbs := filepath.Join(env.base, "restore1", "p1/testfile.c") 950 p2rAbs := filepath.Join(env.base, "restore2", "p2/testfile.c") 951 952 testRunRestoreLatest(t, env.gopts, filepath.Join(env.base, "restore1"), []string{filepath.Dir(p1)}, "") 953 rtest.OK(t, testFileSize(p1rAbs, int64(102))) 954 if _, err := os.Stat(p2rAbs); os.IsNotExist(errors.Cause(err)) { 955 rtest.Assert(t, os.IsNotExist(errors.Cause(err)), 956 "expected %v to not exist in restore, but it exists, err %v", p2rAbs, err) 957 } 958 959 testRunRestoreLatest(t, env.gopts, filepath.Join(env.base, "restore2"), []string{filepath.Dir(p2)}, "") 960 rtest.OK(t, testFileSize(p2rAbs, int64(103))) 961 if _, err := os.Stat(p1rAbs); os.IsNotExist(errors.Cause(err)) { 962 rtest.Assert(t, os.IsNotExist(errors.Cause(err)), 963 "expected %v to not exist in restore, but it exists, err %v", p1rAbs, err) 964 } 965 } 966 967 func TestRestoreWithPermissionFailure(t *testing.T) { 968 env, cleanup := withTestEnvironment(t) 969 defer cleanup() 970 971 datafile := filepath.Join("testdata", "repo-restore-permissions-test.tar.gz") 972 rtest.SetupTarTestFixture(t, env.base, datafile) 973 974 snapshots := testRunList(t, "snapshots", env.gopts) 975 rtest.Assert(t, len(snapshots) > 0, 976 "no snapshots found in repo (%v)", datafile) 977 978 globalOptions.stderr = ioutil.Discard 979 defer func() { 980 globalOptions.stderr = os.Stderr 981 }() 982 983 testRunRestore(t, env.gopts, filepath.Join(env.base, "restore"), snapshots[0]) 984 985 // make sure that all files have been restored, regardless of any 986 // permission errors 987 files := testRunLs(t, env.gopts, snapshots[0].String()) 988 for _, filename := range files { 989 fi, err := os.Lstat(filepath.Join(env.base, "restore", filename)) 990 rtest.OK(t, err) 991 992 rtest.Assert(t, !isFile(fi) || fi.Size() > 0, 993 "file %v restored, but filesize is 0", filename) 994 } 995 } 996 997 func setZeroModTime(filename string) error { 998 var utimes = []syscall.Timespec{ 999 syscall.NsecToTimespec(0), 1000 syscall.NsecToTimespec(0), 1001 } 1002 1003 return syscall.UtimesNano(filename, utimes) 1004 } 1005 1006 func TestRestoreNoMetadataOnIgnoredIntermediateDirs(t *testing.T) { 1007 env, cleanup := withTestEnvironment(t) 1008 defer cleanup() 1009 1010 testRunInit(t, env.gopts) 1011 1012 p := filepath.Join(env.testdata, "subdir1", "subdir2", "subdir3", "file.ext") 1013 rtest.OK(t, os.MkdirAll(filepath.Dir(p), 0755)) 1014 rtest.OK(t, appendRandomData(p, 200)) 1015 rtest.OK(t, setZeroModTime(filepath.Join(env.testdata, "subdir1", "subdir2"))) 1016 1017 opts := BackupOptions{} 1018 1019 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 1020 testRunCheck(t, env.gopts) 1021 1022 snapshotID := testRunList(t, "snapshots", env.gopts)[0] 1023 1024 // restore with filter "*.ext", this should restore "file.ext", but 1025 // since the directories are ignored and only created because of 1026 // "file.ext", no meta data should be restored for them. 1027 testRunRestoreIncludes(t, env.gopts, filepath.Join(env.base, "restore0"), snapshotID, []string{"*.ext"}) 1028 1029 f1 := filepath.Join(env.base, "restore0", "testdata", "subdir1", "subdir2") 1030 fi, err := os.Stat(f1) 1031 rtest.OK(t, err) 1032 1033 rtest.Assert(t, fi.ModTime() != time.Unix(0, 0), 1034 "meta data of intermediate directory has been restore although it was ignored") 1035 1036 // restore with filter "*", this should restore meta data on everything. 1037 testRunRestoreIncludes(t, env.gopts, filepath.Join(env.base, "restore1"), snapshotID, []string{"*"}) 1038 1039 f2 := filepath.Join(env.base, "restore1", "testdata", "subdir1", "subdir2") 1040 fi, err = os.Stat(f2) 1041 rtest.OK(t, err) 1042 1043 rtest.Assert(t, fi.ModTime() == time.Unix(0, 0), 1044 "meta data of intermediate directory hasn't been restore") 1045 } 1046 1047 func TestFind(t *testing.T) { 1048 env, cleanup := withTestEnvironment(t) 1049 defer cleanup() 1050 1051 datafile := filepath.Join("testdata", "backup-data.tar.gz") 1052 testRunInit(t, env.gopts) 1053 rtest.SetupTarTestFixture(t, env.testdata, datafile) 1054 1055 opts := BackupOptions{} 1056 1057 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 1058 testRunCheck(t, env.gopts) 1059 1060 results := testRunFind(t, false, env.gopts, "unexistingfile") 1061 rtest.Assert(t, len(results) == 0, "unexisting file found in repo (%v)", datafile) 1062 1063 results = testRunFind(t, false, env.gopts, "testfile") 1064 lines := strings.Split(string(results), "\n") 1065 rtest.Assert(t, len(lines) == 2, "expected one file found in repo (%v)", datafile) 1066 1067 results = testRunFind(t, false, env.gopts, "testfile*") 1068 lines = strings.Split(string(results), "\n") 1069 rtest.Assert(t, len(lines) == 4, "expected three files found in repo (%v)", datafile) 1070 } 1071 1072 type testMatch struct { 1073 Path string `json:"path,omitempty"` 1074 Permissions string `json:"permissions,omitempty"` 1075 Size uint64 `json:"size,omitempty"` 1076 Date time.Time `json:"date,omitempty"` 1077 UID uint32 `json:"uid,omitempty"` 1078 GID uint32 `json:"gid,omitempty"` 1079 } 1080 1081 type testMatches struct { 1082 Hits int `json:"hits,omitempty"` 1083 SnapshotID string `json:"snapshot,omitempty"` 1084 Matches []testMatch `json:"matches,omitempty"` 1085 } 1086 1087 func TestFindJSON(t *testing.T) { 1088 env, cleanup := withTestEnvironment(t) 1089 defer cleanup() 1090 1091 datafile := filepath.Join("testdata", "backup-data.tar.gz") 1092 testRunInit(t, env.gopts) 1093 rtest.SetupTarTestFixture(t, env.testdata, datafile) 1094 1095 opts := BackupOptions{} 1096 1097 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 1098 testRunCheck(t, env.gopts) 1099 1100 results := testRunFind(t, true, env.gopts, "unexistingfile") 1101 matches := []testMatches{} 1102 rtest.OK(t, json.Unmarshal(results, &matches)) 1103 rtest.Assert(t, len(matches) == 0, "expected no match in repo (%v)", datafile) 1104 1105 results = testRunFind(t, true, env.gopts, "testfile") 1106 rtest.OK(t, json.Unmarshal(results, &matches)) 1107 rtest.Assert(t, len(matches) == 1, "expected a single snapshot in repo (%v)", datafile) 1108 rtest.Assert(t, len(matches[0].Matches) == 1, "expected a single file to match (%v)", datafile) 1109 rtest.Assert(t, matches[0].Hits == 1, "expected hits to show 1 match (%v)", datafile) 1110 1111 results = testRunFind(t, true, env.gopts, "testfile*") 1112 rtest.OK(t, json.Unmarshal(results, &matches)) 1113 rtest.Assert(t, len(matches) == 1, "expected a single snapshot in repo (%v)", datafile) 1114 rtest.Assert(t, len(matches[0].Matches) == 3, "expected 3 files to match (%v)", datafile) 1115 rtest.Assert(t, matches[0].Hits == 3, "expected hits to show 3 matches (%v)", datafile) 1116 } 1117 1118 func TestRebuildIndex(t *testing.T) { 1119 env, cleanup := withTestEnvironment(t) 1120 defer cleanup() 1121 1122 datafile := filepath.Join("..", "..", "internal", "checker", "testdata", "duplicate-packs-in-index-test-repo.tar.gz") 1123 rtest.SetupTarTestFixture(t, env.base, datafile) 1124 1125 out, err := testRunCheckOutput(env.gopts) 1126 if !strings.Contains(out, "contained in several indexes") { 1127 t.Fatalf("did not find checker hint for packs in several indexes") 1128 } 1129 1130 if err != nil { 1131 t.Fatalf("expected no error from checker for test repository, got %v", err) 1132 } 1133 1134 if !strings.Contains(out, "restic rebuild-index") { 1135 t.Fatalf("did not find hint for rebuild-index command") 1136 } 1137 1138 testRunRebuildIndex(t, env.gopts) 1139 1140 out, err = testRunCheckOutput(env.gopts) 1141 if len(out) != 0 { 1142 t.Fatalf("expected no output from the checker, got: %v", out) 1143 } 1144 1145 if err != nil { 1146 t.Fatalf("expected no error from checker after rebuild-index, got: %v", err) 1147 } 1148 } 1149 1150 func TestRebuildIndexAlwaysFull(t *testing.T) { 1151 repository.IndexFull = func(*repository.Index) bool { return true } 1152 TestRebuildIndex(t) 1153 } 1154 1155 func TestCheckRestoreNoLock(t *testing.T) { 1156 env, cleanup := withTestEnvironment(t) 1157 defer cleanup() 1158 1159 datafile := filepath.Join("testdata", "small-repo.tar.gz") 1160 rtest.SetupTarTestFixture(t, env.base, datafile) 1161 1162 err := filepath.Walk(env.repo, func(p string, fi os.FileInfo, e error) error { 1163 if e != nil { 1164 return e 1165 } 1166 return os.Chmod(p, fi.Mode() & ^(os.FileMode(0222))) 1167 }) 1168 rtest.OK(t, err) 1169 1170 env.gopts.NoLock = true 1171 1172 testRunCheck(t, env.gopts) 1173 1174 snapshotIDs := testRunList(t, "snapshots", env.gopts) 1175 if len(snapshotIDs) == 0 { 1176 t.Fatalf("found no snapshots") 1177 } 1178 1179 testRunRestore(t, env.gopts, filepath.Join(env.base, "restore"), snapshotIDs[0]) 1180 } 1181 1182 func TestPrune(t *testing.T) { 1183 env, cleanup := withTestEnvironment(t) 1184 defer cleanup() 1185 1186 datafile := filepath.Join("testdata", "backup-data.tar.gz") 1187 fd, err := os.Open(datafile) 1188 if os.IsNotExist(errors.Cause(err)) { 1189 t.Skipf("unable to find data file %q, skipping", datafile) 1190 return 1191 } 1192 rtest.OK(t, err) 1193 rtest.OK(t, fd.Close()) 1194 1195 testRunInit(t, env.gopts) 1196 1197 rtest.SetupTarTestFixture(t, env.testdata, datafile) 1198 opts := BackupOptions{} 1199 1200 testRunBackup(t, []string{filepath.Join(env.testdata, "0", "0")}, opts, env.gopts) 1201 firstSnapshot := testRunList(t, "snapshots", env.gopts) 1202 rtest.Assert(t, len(firstSnapshot) == 1, 1203 "expected one snapshot, got %v", firstSnapshot) 1204 1205 testRunBackup(t, []string{filepath.Join(env.testdata, "0", "0", "2")}, opts, env.gopts) 1206 testRunBackup(t, []string{filepath.Join(env.testdata, "0", "0", "3")}, opts, env.gopts) 1207 1208 snapshotIDs := testRunList(t, "snapshots", env.gopts) 1209 rtest.Assert(t, len(snapshotIDs) == 3, 1210 "expected 3 snapshot, got %v", snapshotIDs) 1211 1212 testRunForget(t, env.gopts, firstSnapshot[0].String()) 1213 testRunPrune(t, env.gopts) 1214 testRunCheck(t, env.gopts) 1215 } 1216 1217 func TestHardLink(t *testing.T) { 1218 // this test assumes a test set with a single directory containing hard linked files 1219 env, cleanup := withTestEnvironment(t) 1220 defer cleanup() 1221 1222 datafile := filepath.Join("testdata", "test.hl.tar.gz") 1223 fd, err := os.Open(datafile) 1224 if os.IsNotExist(errors.Cause(err)) { 1225 t.Skipf("unable to find data file %q, skipping", datafile) 1226 return 1227 } 1228 rtest.OK(t, err) 1229 rtest.OK(t, fd.Close()) 1230 1231 testRunInit(t, env.gopts) 1232 1233 rtest.SetupTarTestFixture(t, env.testdata, datafile) 1234 1235 linkTests := createFileSetPerHardlink(env.testdata) 1236 1237 opts := BackupOptions{} 1238 1239 // first backup 1240 testRunBackup(t, []string{env.testdata}, opts, env.gopts) 1241 snapshotIDs := testRunList(t, "snapshots", env.gopts) 1242 rtest.Assert(t, len(snapshotIDs) == 1, 1243 "expected one snapshot, got %v", snapshotIDs) 1244 1245 testRunCheck(t, env.gopts) 1246 1247 // restore all backups and compare 1248 for i, snapshotID := range snapshotIDs { 1249 restoredir := filepath.Join(env.base, fmt.Sprintf("restore%d", i)) 1250 t.Logf("restoring snapshot %v to %v", snapshotID.Str(), restoredir) 1251 testRunRestore(t, env.gopts, restoredir, snapshotIDs[0]) 1252 rtest.Assert(t, directoriesEqualContents(env.testdata, filepath.Join(restoredir, "testdata")), 1253 "directories are not equal") 1254 1255 linkResults := createFileSetPerHardlink(filepath.Join(restoredir, "testdata")) 1256 rtest.Assert(t, linksEqual(linkTests, linkResults), 1257 "links are not equal") 1258 } 1259 1260 testRunCheck(t, env.gopts) 1261 } 1262 1263 func linksEqual(source, dest map[uint64][]string) bool { 1264 for _, vs := range source { 1265 found := false 1266 for kd, vd := range dest { 1267 if linkEqual(vs, vd) { 1268 delete(dest, kd) 1269 found = true 1270 break 1271 } 1272 } 1273 if !found { 1274 return false 1275 } 1276 } 1277 1278 if len(dest) != 0 { 1279 return false 1280 } 1281 1282 return true 1283 } 1284 1285 func linkEqual(source, dest []string) bool { 1286 // equal if sliced are equal without considering order 1287 if source == nil && dest == nil { 1288 return true 1289 } 1290 1291 if source == nil || dest == nil { 1292 return false 1293 } 1294 1295 if len(source) != len(dest) { 1296 return false 1297 } 1298 1299 for i := range source { 1300 found := false 1301 for j := range dest { 1302 if source[i] == dest[j] { 1303 found = true 1304 break 1305 } 1306 } 1307 if !found { 1308 return false 1309 } 1310 } 1311 1312 return true 1313 }