2023-05-06 08:37:17 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
rtest "github.com/restic/restic/internal/test"
|
|
|
|
)
|
|
|
|
|
|
|
|
func testRunDiffOutput(gopts GlobalOptions, firstSnapshotID string, secondSnapshotID string) (string, error) {
|
2023-05-07 20:06:39 +00:00
|
|
|
buf, err := withCaptureStdout(func() error {
|
|
|
|
opts := DiffOptions{
|
|
|
|
ShowMetadata: false,
|
|
|
|
}
|
|
|
|
return runDiff(context.TODO(), opts, gopts, []string{firstSnapshotID, secondSnapshotID})
|
|
|
|
})
|
2023-05-06 08:37:17 +00:00
|
|
|
return buf.String(), err
|
|
|
|
}
|
|
|
|
|
|
|
|
func copyFile(dst string, src string) error {
|
|
|
|
srcFile, err := os.Open(src)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
dstFile, err := os.Create(dst)
|
|
|
|
if err != nil {
|
|
|
|
// ignore subsequent errors
|
|
|
|
_ = srcFile.Close()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = io.Copy(dstFile, srcFile)
|
|
|
|
if err != nil {
|
|
|
|
// ignore subsequent errors
|
|
|
|
_ = srcFile.Close()
|
|
|
|
_ = dstFile.Close()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = srcFile.Close()
|
|
|
|
if err != nil {
|
|
|
|
// ignore subsequent errors
|
|
|
|
_ = dstFile.Close()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = dstFile.Close()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var diffOutputRegexPatterns = []string{
|
|
|
|
"-.+modfile",
|
|
|
|
"M.+modfile1",
|
|
|
|
"\\+.+modfile2",
|
|
|
|
"\\+.+modfile3",
|
|
|
|
"\\+.+modfile4",
|
|
|
|
"-.+submoddir",
|
|
|
|
"-.+submoddir.subsubmoddir",
|
|
|
|
"\\+.+submoddir2",
|
|
|
|
"\\+.+submoddir2.subsubmoddir",
|
|
|
|
"Files: +2 new, +1 removed, +1 changed",
|
|
|
|
"Dirs: +3 new, +2 removed",
|
|
|
|
"Data Blobs: +2 new, +1 removed",
|
|
|
|
"Added: +7[0-9]{2}\\.[0-9]{3} KiB",
|
|
|
|
"Removed: +2[0-9]{2}\\.[0-9]{3} KiB",
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupDiffRepo(t *testing.T) (*testEnvironment, func(), string, string) {
|
|
|
|
env, cleanup := withTestEnvironment(t)
|
|
|
|
testRunInit(t, env.gopts)
|
|
|
|
|
|
|
|
datadir := filepath.Join(env.base, "testdata")
|
|
|
|
testdir := filepath.Join(datadir, "testdir")
|
|
|
|
subtestdir := filepath.Join(testdir, "subtestdir")
|
|
|
|
testfile := filepath.Join(testdir, "testfile")
|
|
|
|
|
|
|
|
rtest.OK(t, os.Mkdir(testdir, 0755))
|
|
|
|
rtest.OK(t, os.Mkdir(subtestdir, 0755))
|
|
|
|
rtest.OK(t, appendRandomData(testfile, 256*1024))
|
|
|
|
|
|
|
|
moddir := filepath.Join(datadir, "moddir")
|
|
|
|
submoddir := filepath.Join(moddir, "submoddir")
|
|
|
|
subsubmoddir := filepath.Join(submoddir, "subsubmoddir")
|
|
|
|
modfile := filepath.Join(moddir, "modfile")
|
|
|
|
rtest.OK(t, os.Mkdir(moddir, 0755))
|
|
|
|
rtest.OK(t, os.Mkdir(submoddir, 0755))
|
|
|
|
rtest.OK(t, os.Mkdir(subsubmoddir, 0755))
|
|
|
|
rtest.OK(t, copyFile(modfile, testfile))
|
|
|
|
rtest.OK(t, appendRandomData(modfile+"1", 256*1024))
|
|
|
|
|
|
|
|
snapshots := make(map[string]struct{})
|
|
|
|
opts := BackupOptions{}
|
|
|
|
testRunBackup(t, "", []string{datadir}, opts, env.gopts)
|
|
|
|
snapshots, firstSnapshotID := lastSnapshot(snapshots, loadSnapshotMap(t, env.gopts))
|
|
|
|
|
|
|
|
rtest.OK(t, os.Rename(modfile, modfile+"3"))
|
|
|
|
rtest.OK(t, os.Rename(submoddir, submoddir+"2"))
|
|
|
|
rtest.OK(t, appendRandomData(modfile+"1", 256*1024))
|
|
|
|
rtest.OK(t, appendRandomData(modfile+"2", 256*1024))
|
|
|
|
rtest.OK(t, os.Mkdir(modfile+"4", 0755))
|
|
|
|
|
|
|
|
testRunBackup(t, "", []string{datadir}, opts, env.gopts)
|
|
|
|
_, secondSnapshotID := lastSnapshot(snapshots, loadSnapshotMap(t, env.gopts))
|
|
|
|
|
|
|
|
return env, cleanup, firstSnapshotID, secondSnapshotID
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDiff(t *testing.T) {
|
|
|
|
env, cleanup, firstSnapshotID, secondSnapshotID := setupDiffRepo(t)
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
// quiet suppresses the diff output except for the summary
|
|
|
|
env.gopts.Quiet = false
|
|
|
|
_, err := testRunDiffOutput(env.gopts, "", secondSnapshotID)
|
|
|
|
rtest.Assert(t, err != nil, "expected error on invalid snapshot id")
|
|
|
|
|
|
|
|
out, err := testRunDiffOutput(env.gopts, firstSnapshotID, secondSnapshotID)
|
|
|
|
rtest.OK(t, err)
|
|
|
|
|
|
|
|
for _, pattern := range diffOutputRegexPatterns {
|
|
|
|
r, err := regexp.Compile(pattern)
|
|
|
|
rtest.Assert(t, err == nil, "failed to compile regexp %v", pattern)
|
|
|
|
rtest.Assert(t, r.MatchString(out), "expected pattern %v in output, got\n%v", pattern, out)
|
|
|
|
}
|
|
|
|
|
|
|
|
// check quiet output
|
|
|
|
env.gopts.Quiet = true
|
|
|
|
outQuiet, err := testRunDiffOutput(env.gopts, firstSnapshotID, secondSnapshotID)
|
|
|
|
rtest.OK(t, err)
|
|
|
|
|
|
|
|
rtest.Assert(t, len(outQuiet) < len(out), "expected shorter output on quiet mode %v vs. %v", len(outQuiet), len(out))
|
|
|
|
}
|
|
|
|
|
|
|
|
type typeSniffer struct {
|
|
|
|
MessageType string `json:"message_type"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDiffJSON(t *testing.T) {
|
|
|
|
env, cleanup, firstSnapshotID, secondSnapshotID := setupDiffRepo(t)
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
// quiet suppresses the diff output except for the summary
|
|
|
|
env.gopts.Quiet = false
|
|
|
|
env.gopts.JSON = true
|
|
|
|
out, err := testRunDiffOutput(env.gopts, firstSnapshotID, secondSnapshotID)
|
|
|
|
rtest.OK(t, err)
|
|
|
|
|
|
|
|
var stat DiffStatsContainer
|
|
|
|
var changes int
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(strings.NewReader(out))
|
|
|
|
for scanner.Scan() {
|
|
|
|
line := scanner.Text()
|
|
|
|
var sniffer typeSniffer
|
|
|
|
rtest.OK(t, json.Unmarshal([]byte(line), &sniffer))
|
|
|
|
switch sniffer.MessageType {
|
|
|
|
case "change":
|
|
|
|
changes++
|
|
|
|
case "statistics":
|
|
|
|
rtest.OK(t, json.Unmarshal([]byte(line), &stat))
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected message type %v", sniffer.MessageType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rtest.Equals(t, 9, changes)
|
|
|
|
rtest.Assert(t, stat.Added.Files == 2 && stat.Added.Dirs == 3 && stat.Added.DataBlobs == 2 &&
|
|
|
|
stat.Removed.Files == 1 && stat.Removed.Dirs == 2 && stat.Removed.DataBlobs == 1 &&
|
|
|
|
stat.ChangedFiles == 1, "unexpected statistics")
|
|
|
|
|
|
|
|
// check quiet output
|
|
|
|
env.gopts.Quiet = true
|
|
|
|
outQuiet, err := testRunDiffOutput(env.gopts, firstSnapshotID, secondSnapshotID)
|
|
|
|
rtest.OK(t, err)
|
|
|
|
|
|
|
|
stat = DiffStatsContainer{}
|
|
|
|
rtest.OK(t, json.Unmarshal([]byte(outQuiet), &stat))
|
|
|
|
rtest.Assert(t, stat.Added.Files == 2 && stat.Added.Dirs == 3 && stat.Added.DataBlobs == 2 &&
|
|
|
|
stat.Removed.Files == 1 && stat.Removed.Dirs == 2 && stat.Removed.DataBlobs == 1 &&
|
|
|
|
stat.ChangedFiles == 1, "unexpected statistics")
|
|
|
|
rtest.Assert(t, stat.SourceSnapshot == firstSnapshotID && stat.TargetSnapshot == secondSnapshotID, "unexpected snapshot ids")
|
|
|
|
}
|