Files
URBM/internal/rsync/rsync_test.go
T

173 lines
6.3 KiB
Go

package rsync
import (
"context"
"os"
"path/filepath"
"slices"
"strings"
"testing"
"git.casaderoll.de/michael/urbm/internal/model"
)
func TestArgumentsUseExplicitSafeOptions(t *testing.T) {
job := model.Job{Sources: []model.Source{{Path: "/mnt/user/data"}}, Excludes: []string{"*.tmp"}, Rsync: model.RsyncOptions{Target: "/mnt/remotes/backup", Delete: true, PreservePermissions: true}}
args := Arguments(job)
for _, expected := range []string{"--recursive", "--delete-delay", "--perms", "--exclude", "*.tmp", "--", "/mnt/user/data", "/mnt/remotes/backup/"} {
if !slices.Contains(args, expected) {
t.Fatalf("missing %q in %#v", expected, args)
}
}
}
func TestParseProgress(t *testing.T) {
progress, ok := parseProgress(" 1,234,567 42% 12.34MB/s 0:00:05")
if !ok || progress.Bytes != 1234567 || progress.Percent != 42 {
t.Fatalf("progress = %#v, %v", progress, ok)
}
}
func TestArrayDiskMirrorKeepsDiskDirectory(t *testing.T) {
job := model.Job{Sources: []model.Source{{Path: "/mnt/disk1"}}, Rsync: model.RsyncOptions{Target: "/mnt/disks/USB Backup", Overwrite: true, Delete: true}}
args := Arguments(job)
if !slices.Equal(args[len(args)-3:], []string{"--", "/mnt/disk1", "/mnt/disks/USB Backup/"}) || !slices.Contains(args, "--delete-delay") {
t.Fatalf("mirror arguments = %v", args)
}
}
func TestMachineReadableArgumentsAndSummary(t *testing.T) {
args := Arguments(model.Job{})
if slices.Contains(args, "--human-readable") {
t.Fatal("human readable sizes break byte parsing")
}
if !slices.Contains(args, "--out-format=CHANGE|%i|%n%L") {
t.Fatal(args)
}
var summary Summary
for _, line := range []string{"Number of regular files transferred: 1,376", "Number of deleted files: 1,214 (reg: 1,200, dir: 14)", "Total transferred file size: 1,234,567,890 bytes"} {
summary.parseLine(line)
}
if summary.Files != 1376 || summary.Deleted != 1214 || summary.Bytes != 1234567890 {
t.Fatalf("summary = %+v", summary)
}
}
func TestParseChangePreservesNames(t *testing.T) {
for _, code := range []string{">f+++++++++", ">f.st......", ".d..t......", "*deleting "} {
got, ok := parseChange("CHANGE|" + code + "|disk1/a | b.txt")
if !ok || got.Change != code || got.CurrentFile != "disk1/a | b.txt" {
t.Fatalf("change = %+v, %v", got, ok)
}
}
}
func TestRunnerReadsFinalStats(t *testing.T) {
binary := filepath.Join(t.TempDir(), "rsync")
script := "#!/bin/sh\nprintf '%s\\n' 'CHANGE|>f+++++++++|disk1/new.txt' 'Number of regular files transferred: 1,376' 'Number of deleted files: 1,214 (reg: 1214)' 'Total transferred file size: 1,234,567,890 bytes'\n"
if err := os.WriteFile(binary, []byte(script), 0700); err != nil {
t.Fatal(err)
}
runner := Runner{Binary: binary}
for _, estimate := range []bool{false, true} {
var summary Summary
var err error
var changes []string
if estimate {
summary, err = runner.Estimate(context.Background(), "test", model.Job{})
} else {
summary, err = runner.Run(context.Background(), "test", model.Job{}, func(p Progress) {
if p.CurrentFile != "" {
changes = append(changes, p.CurrentFile)
}
})
}
if err != nil || summary.Files != 1376 || summary.Deleted != 1214 || summary.Bytes != 1234567890 {
t.Fatalf("estimate=%v summary=%+v err=%v", estimate, summary, err)
}
if !estimate && strings.Join(changes, ",") != "disk1/new.txt" {
t.Fatal(changes)
}
}
}
// Opt in with URBM_TEST_RSYNC pointing to rsync 3.x (Unraid's supported version).
func TestRealRsyncDryRunAndCopy(t *testing.T) {
binary := os.Getenv("URBM_TEST_RSYNC")
if binary == "" {
t.Skip("set URBM_TEST_RSYNC to test with rsync 3.x")
}
dir := t.TempDir()
source, target := filepath.Join(dir, "disk1"), filepath.Join(dir, "USB")
destination := filepath.Join(target, "disk1")
for _, path := range []string{source, filepath.Join(destination, "old-dir")} {
if err := os.MkdirAll(path, 0700); err != nil {
t.Fatal(err)
}
}
for path, data := range map[string]string{
filepath.Join(source, "new"): "new", filepath.Join(source, "changed"): "updated",
filepath.Join(source, "same"): "same", filepath.Join(destination, "same"): "same",
filepath.Join(destination, "changed"): "old", filepath.Join(destination, "obsolete"): "delete",
filepath.Join(destination, "old-dir", "file"): "delete",
} {
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
t.Fatal(err)
}
}
if err := os.Symlink("obsolete", filepath.Join(destination, "old-link")); err != nil {
t.Fatal(err)
}
runner := Runner{Binary: binary}
job := model.Job{Sources: []model.Source{{Path: source}}, Rsync: model.RsyncOptions{Target: target, DryRun: true, Overwrite: true, Delete: true, Checksum: true, PreserveLinks: true}}
var changes []Progress
summary, err := runner.Run(context.Background(), "test", job, func(p Progress) {
if p.Change != "" {
changes = append(changes, p)
}
})
if err != nil || summary.Files != 2 || summary.Bytes != 10 || summary.Deleted != 4 {
t.Fatalf("dry run: %+v %v", summary, err)
}
if len(changes) < 6 {
t.Fatalf("missing change details: %+v", changes)
}
if _, err := os.Stat(filepath.Join(destination, "new")); !os.IsNotExist(err) {
t.Fatal("dry run wrote a file")
}
if _, err := os.Stat(filepath.Join(destination, "obsolete")); err != nil {
t.Fatal("dry run deleted a file")
}
estimate, err := runner.Estimate(context.Background(), "test", job)
if err != nil || estimate != summary {
t.Fatalf("estimate differs: %+v %v", estimate, err)
}
job.Rsync.DryRun = false
copied, err := runner.Run(context.Background(), "test", job, nil)
if err != nil || copied != summary {
t.Fatalf("copy differs: %+v %v", copied, err)
}
for _, name := range []string{"new", "changed", "same"} {
got, err := os.ReadFile(filepath.Join(destination, name))
if err != nil {
t.Fatal(err)
}
want, err := os.ReadFile(filepath.Join(source, name))
if err != nil {
t.Fatal(err)
}
if string(got) != string(want) {
t.Fatalf("different contents: %s", name)
}
}
for _, name := range []string{"obsolete", "old-dir", "old-link"} {
if _, err := os.Lstat(filepath.Join(destination, name)); !os.IsNotExist(err) {
t.Fatalf("not deleted: %s", name)
}
}
identical, err := runner.Estimate(context.Background(), "test", job)
if err != nil || identical.Files != 0 || identical.Bytes != 0 || identical.Deleted != 0 {
t.Fatalf("identical: %+v %v", identical, err)
}
}