mirror of
https://github.com/restic/restic.git
synced 2026-05-14 06:25:22 +00:00
277 lines
10 KiB
Go
277 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/restic/restic/internal/global"
|
|
"github.com/restic/restic/internal/restic"
|
|
rtest "github.com/restic/restic/internal/test"
|
|
"github.com/restic/restic/internal/ui"
|
|
)
|
|
|
|
func testRunFind(t testing.TB, wantJSON bool, opts FindOptions, gopts global.Options, pattern string) []byte {
|
|
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
|
gopts.JSON = wantJSON
|
|
|
|
return runFind(ctx, opts, gopts, []string{pattern}, gopts.Term)
|
|
})
|
|
rtest.OK(t, err)
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func TestFind(t *testing.T) {
|
|
env, cleanup := withTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
datafile := testSetupBackupData(t, env)
|
|
opts := BackupOptions{}
|
|
|
|
testRunBackup(t, "", []string{env.testdata}, opts, env.gopts)
|
|
|
|
results := testRunFind(t, false, FindOptions{}, env.gopts, "unexistingfile")
|
|
rtest.Assert(t, len(results) == 0, "unexisting file found in repo (%v)", datafile)
|
|
|
|
results = testRunFind(t, false, FindOptions{}, env.gopts, "testfile")
|
|
lines := strings.Split(string(results), "\n")
|
|
rtest.Assert(t, len(lines) == 2, "expected one file found in repo (%v)", datafile)
|
|
|
|
results = testRunFind(t, false, FindOptions{}, env.gopts, "testfile*")
|
|
lines = strings.Split(string(results), "\n")
|
|
rtest.Assert(t, len(lines) == 4, "expected three files found in repo (%v)", datafile)
|
|
}
|
|
|
|
type testMatch struct {
|
|
Path string `json:"path,omitempty"`
|
|
Permissions string `json:"permissions,omitempty"`
|
|
Size uint64 `json:"size,omitempty"`
|
|
Date time.Time `json:"date,omitempty"`
|
|
UID uint32 `json:"uid,omitempty"`
|
|
GID uint32 `json:"gid,omitempty"`
|
|
}
|
|
|
|
type testMatches struct {
|
|
Hits int `json:"hits,omitempty"`
|
|
SnapshotID string `json:"snapshot,omitempty"`
|
|
Matches []testMatch `json:"matches,omitempty"`
|
|
}
|
|
|
|
func TestFindJSON(t *testing.T) {
|
|
env, cleanup := withTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
datafile := testSetupBackupData(t, env)
|
|
opts := BackupOptions{}
|
|
|
|
testRunBackup(t, "", []string{env.testdata}, opts, env.gopts)
|
|
testRunCheck(t, env.gopts)
|
|
snapshot, _ := testRunSnapshots(t, env.gopts)
|
|
|
|
results := testRunFind(t, true, FindOptions{}, env.gopts, "unexistingfile")
|
|
matches := []testMatches{}
|
|
rtest.OK(t, json.Unmarshal(results, &matches))
|
|
rtest.Assert(t, len(matches) == 0, "expected no match in repo (%v)", datafile)
|
|
|
|
results = testRunFind(t, true, FindOptions{}, env.gopts, "testfile")
|
|
rtest.OK(t, json.Unmarshal(results, &matches))
|
|
rtest.Assert(t, len(matches) == 1, "expected a single snapshot in repo (%v)", datafile)
|
|
rtest.Assert(t, len(matches[0].Matches) == 1, "expected a single file to match (%v)", datafile)
|
|
rtest.Assert(t, matches[0].Hits == 1, "expected hits to show 1 match (%v)", datafile)
|
|
|
|
results = testRunFind(t, true, FindOptions{}, env.gopts, "testfile*")
|
|
rtest.OK(t, json.Unmarshal(results, &matches))
|
|
rtest.Assert(t, len(matches) == 1, "expected a single snapshot in repo (%v)", datafile)
|
|
rtest.Assert(t, len(matches[0].Matches) == 3, "expected 3 files to match (%v)", datafile)
|
|
rtest.Assert(t, matches[0].Hits == 3, "expected hits to show 3 matches (%v)", datafile)
|
|
|
|
results = testRunFind(t, true, FindOptions{TreeID: true}, env.gopts, snapshot.Tree.String())
|
|
rtest.OK(t, json.Unmarshal(results, &matches))
|
|
rtest.Assert(t, len(matches) == 1, "expected a single snapshot in repo (%v)", matches)
|
|
rtest.Assert(t, len(matches[0].Matches) == 3, "expected 3 files to match (%v)", matches[0].Matches)
|
|
rtest.Assert(t, matches[0].Hits == 3, "expected hits to show 3 matches (%v)", datafile)
|
|
}
|
|
|
|
func TestFindSorting(t *testing.T) {
|
|
env, cleanup := withTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
testSetupBackupData(t, env)
|
|
opts := BackupOptions{}
|
|
|
|
// first backup
|
|
testRunBackup(t, "", []string{env.testdata}, opts, env.gopts)
|
|
sn1 := testListSnapshots(t, env.gopts, 1)[0]
|
|
|
|
// second backup
|
|
testRunBackup(t, "", []string{env.testdata}, opts, env.gopts)
|
|
snapshots := testListSnapshots(t, env.gopts, 2)
|
|
// get id of new snapshot without depending on file order returned by filesystem
|
|
sn2 := snapshots[0]
|
|
if sn1.Equal(sn2) {
|
|
sn2 = snapshots[1]
|
|
}
|
|
|
|
// first restic find - with default FindOptions{}
|
|
results := testRunFind(t, true, FindOptions{}, env.gopts, "testfile")
|
|
lines := strings.Split(string(results), "\n")
|
|
rtest.Assert(t, len(lines) == 2, "expected two lines of output, found %d", len(lines))
|
|
matches := []testMatches{}
|
|
rtest.OK(t, json.Unmarshal(results, &matches))
|
|
|
|
// run second restic find with --reverse, sort oldest to newest
|
|
resultsReverse := testRunFind(t, true, FindOptions{Reverse: true}, env.gopts, "testfile")
|
|
lines = strings.Split(string(resultsReverse), "\n")
|
|
rtest.Assert(t, len(lines) == 2, "expected two lines of output, found %d", len(lines))
|
|
matchesReverse := []testMatches{}
|
|
rtest.OK(t, json.Unmarshal(resultsReverse, &matchesReverse))
|
|
|
|
// compare result sets
|
|
rtest.Assert(t, sn1.String() == matchesReverse[0].SnapshotID, "snapshot[0] must match old snapshot")
|
|
rtest.Assert(t, sn2.String() == matchesReverse[1].SnapshotID, "snapshot[1] must match new snapshot")
|
|
rtest.Assert(t, matches[0].SnapshotID == matchesReverse[1].SnapshotID, "matches should be sorted 1")
|
|
rtest.Assert(t, matches[1].SnapshotID == matchesReverse[0].SnapshotID, "matches should be sorted 2")
|
|
}
|
|
|
|
func TestFindInvalidTimeRange(t *testing.T) {
|
|
env, cleanup := withTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
err := runFind(context.TODO(), FindOptions{Oldest: "2026-01-01", Newest: "2020-01-01"}, env.gopts, []string{"quack"}, env.gopts.Term)
|
|
rtest.Assert(t, err != nil && err.Error() == "Fatal: --oldest must specify a time before --newest",
|
|
"unexpected error message: %v", err)
|
|
}
|
|
|
|
// JsonOutput is the struct `restic find --json` produces
|
|
type JSONOutput struct {
|
|
ObjectType string `json:"object_type"`
|
|
ID string `json:"id"`
|
|
Path string `json:"path"`
|
|
ParentTree string `json:"parent_tree,omitempty"`
|
|
SnapshotID string `json:"snapshot"`
|
|
Time time.Time `json:"time,omitempty"`
|
|
}
|
|
|
|
func TestFindPackfile(t *testing.T) {
|
|
env, cleanup := withTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
testSetupBackupData(t, env)
|
|
|
|
// backup
|
|
backupPath := env.testdata + "/0/0/9"
|
|
testRunBackup(t, "", []string{backupPath}, BackupOptions{}, env.gopts)
|
|
sn1 := testListSnapshots(t, env.gopts, 1)[0]
|
|
|
|
// do all the testing wrapped inside withTermStatus()
|
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
|
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
|
rtest.OK(t, err)
|
|
defer unlock()
|
|
|
|
// load master index
|
|
rtest.OK(t, repo.LoadIndex(ctx, printer))
|
|
|
|
packID := restic.ID{}
|
|
done := false
|
|
err = repo.ListBlobs(ctx, func(pb restic.PackedBlob) {
|
|
if !done && pb.Type == restic.TreeBlob {
|
|
packID = pb.PackID
|
|
done = true
|
|
}
|
|
})
|
|
|
|
rtest.OK(t, err)
|
|
rtest.Assert(t, !packID.IsNull(), "expected a tree packfile ID")
|
|
findOptions := FindOptions{PackID: true}
|
|
results := testRunFind(t, true, findOptions, env.gopts, packID.String())
|
|
|
|
// get the json records
|
|
jsonResult := []JSONOutput{}
|
|
rtest.OK(t, json.Unmarshal(results, &jsonResult))
|
|
rtest.Assert(t, len(jsonResult) > 0, "expected at least one tree record in the packfile")
|
|
|
|
// look at the last record
|
|
lastIndex := len(jsonResult) - 1
|
|
record := jsonResult[lastIndex]
|
|
rtest.Assert(t, record.ObjectType == "tree" && record.SnapshotID == sn1.String(),
|
|
"expected a tree record with known snapshot id, but got type=%s and snapID=%s instead of %s",
|
|
record.ObjectType, record.SnapshotID, sn1.String())
|
|
backupPath = filepath.ToSlash(backupPath)[2:] // take the offending drive mapping away
|
|
rtest.Assert(t, strings.Contains(record.Path, backupPath), "expected %q as part of %q", backupPath, record.Path)
|
|
|
|
return nil
|
|
})
|
|
rtest.OK(t, err)
|
|
}
|
|
|
|
func TestFindPackID(t *testing.T) {
|
|
env, cleanup := withTestEnvironment(t)
|
|
defer cleanup()
|
|
testSetupBackupData(t, env)
|
|
|
|
dir009 := filepath.Join(env.testdata, "0", "0", "9")
|
|
dirEntries, err := os.ReadDir(dir009)
|
|
rtest.OK(t, err)
|
|
numberOfFiles := len(dirEntries)
|
|
|
|
// backup
|
|
testRunBackup(t, "", []string{dir009}, BackupOptions{}, env.gopts)
|
|
sn1 := testListSnapshots(t, env.gopts, 1)[0]
|
|
|
|
// extract packfile ID from repository index
|
|
dataPackID := restic.ID{}
|
|
treePackID := restic.ID{}
|
|
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
|
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
|
rtest.OK(t, err)
|
|
defer unlock()
|
|
|
|
// load Index
|
|
rtest.OK(t, repo.LoadIndex(ctx, nil))
|
|
// go through all index entries and collect data and tree packfile(s)
|
|
rtest.OK(t, repo.ListBlobs(ctx, func(blob restic.PackedBlob) {
|
|
switch blob.Type {
|
|
case restic.DataBlob:
|
|
dataPackID = blob.PackID
|
|
case restic.TreeBlob:
|
|
treePackID = blob.PackID
|
|
}
|
|
}))
|
|
return nil
|
|
})
|
|
rtest.OK(t, err)
|
|
|
|
// look for data packfile
|
|
rtest.Assert(t, !dataPackID.IsNull(), "expected to find data packfile in repo")
|
|
packID := dataPackID.String()
|
|
out := testRunFind(t, true, FindOptions{PackID: true}, env.gopts, packID)
|
|
|
|
findRes := []JSONOutput{}
|
|
rtest.OK(t, json.Unmarshal(out, &findRes))
|
|
rtest.Assert(t, len(findRes) == numberOfFiles, "expected %d entries for this packfile, got %d",
|
|
numberOfFiles, len(findRes))
|
|
|
|
// look for tree packfile
|
|
rtest.Assert(t, !treePackID.IsNull(), "expected to find tree packfile in repo")
|
|
packID = treePackID.String()
|
|
out = testRunFind(t, true, FindOptions{PackID: true}, env.gopts, packID)
|
|
|
|
findRes = []JSONOutput{}
|
|
rtest.OK(t, json.Unmarshal(out, &findRes))
|
|
record := findRes[len(findRes)-1]
|
|
|
|
rtest.Equals(t, record.ObjectType, "tree")
|
|
rtest.Equals(t, record.SnapshotID, sn1.String())
|
|
// windows path are messy, so we get rid of the messy bits at the start
|
|
// exp: "/C/Users/RUNNER~1/AppData/Local/Temp/restic-test-2921201257/testdata/0/0/9"
|
|
// got: "C:/Users/RUNNER~1/AppData/Local/Temp/restic-test-2921201257/testdata/0/0/9"
|
|
rtest.Equals(t, filepath.ToSlash(record.Path)[2:], filepath.ToSlash(dir009)[2:])
|
|
}
|