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  }