]> ruderich.org/simon Gitweb - safcm/safcm.git/blobdiff - cmd/safcm-remote/sync/files_test.go
sync: shorten log messages by removing "info/sync remote:" prefix
[safcm/safcm.git] / cmd / safcm-remote / sync / files_test.go
index 32130e928abbad99c77ed139fbc48373c28c1f32..88a5b51f85cd3fa4a4d36f1422ecbe37424565de 100644 (file)
@@ -21,14 +21,12 @@ import (
        "math/rand"
        "os"
        "path/filepath"
-       "reflect"
        "regexp"
        "testing"
 
-       "github.com/google/go-cmp/cmp"
-
        "ruderich.org/simon/safcm"
        ft "ruderich.org/simon/safcm/cmd/safcm-remote/sync/filetest"
+       "ruderich.org/simon/safcm/testutil"
 )
 
 var randFilesRegexp = regexp.MustCompile(`\d+"$`)
@@ -55,27 +53,31 @@ func TestSyncFiles(t *testing.T) {
        }
        user, uid, group, gid := ft.CurrentUserAndGroup()
 
+       skipUnlessCiRun := len(os.Getenv("SAFCM_CI_RUN")) == 0
+
        tmpTestFilePath := "/tmp/safcm-sync-files-test-file"
 
        tests := []struct {
-               name     string
-               req      safcm.MsgSyncReq
-               prepare  func()
-               triggers []string
-               expFiles []ft.File
-               expResp  safcm.MsgSyncResp
-               expDbg   []string
-               expErr   error
+               name        string
+               skip        bool
+               req         safcm.MsgSyncReq
+               prepare     func()
+               expTriggers []string
+               expFiles    []ft.File
+               expResp     safcm.MsgSyncResp
+               expDbg      []string
+               expErr      error
        }{
 
                // NOTE: Also update MsgSyncResp in safcm test cases when
-               // changing anything here!
+               // changing the MsgSyncResp struct!
 
                // See TestSyncFile() for most file related tests. This
                // function only tests the overall results and triggers.
 
                {
                        "basic: create",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -143,22 +145,23 @@ func TestSyncFiles(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "." (group): unchanged`,
-                               `4: sync remote: files: "dir" (group): will create`,
-                               `3: sync remote: files: "dir" (group): creating`,
-                               `4: sync remote: files: "dir" (group): creating directory`,
-                               `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
-                               fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
-                               `4: sync remote: files: "dir/file" (group): will create`,
-                               `3: sync remote: files: "dir/file" (group): creating`,
-                               `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
-                               `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
+                               `4: files: "." (group): unchanged`,
+                               `4: files: "dir" (group): will create`,
+                               `3: files: "dir" (group): creating`,
+                               `4: files: "dir" (group): creating directory`,
+                               `4: files: "dir" (group): chmodding drwxr-xr-x`,
+                               fmt.Sprintf(`4: files: "dir" (group): chowning %d/%d`, uid, gid),
+                               `4: files: "dir/file" (group): will create`,
+                               `3: files: "dir/file" (group): creating`,
+                               `4: files: "dir/file" (group): creating temporary file "dir/.file*"`,
+                               `4: files: "dir/file" (group): renaming "dir/.fileRND"`,
                        },
                        nil,
                },
 
                {
                        "basic: no change",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -204,15 +207,16 @@ func TestSyncFiles(t *testing.T) {
                        },
                        safcm.MsgSyncResp{},
                        []string{
-                               `4: sync remote: files: "." (group): unchanged`,
-                               `4: sync remote: files: "dir" (group): unchanged`,
-                               `4: sync remote: files: "dir/file" (group): unchanged`,
+                               `4: files: "." (group): unchanged`,
+                               `4: files: "dir" (group): unchanged`,
+                               `4: files: "dir/file" (group): unchanged`,
                        },
                        nil,
                },
 
                {
                        "invalid File: user",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -236,6 +240,7 @@ func TestSyncFiles(t *testing.T) {
                },
                {
                        "invalid File: group",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -263,43 +268,32 @@ func TestSyncFiles(t *testing.T) {
                        // don't want to modify the running system. Use this
                        // test (and the one below for triggers) as a basic
                        // check that absolute paths work.
+                       //
+                       // Use numeric IDs as not all systems use root/root;
+                       // for example BSDs use root/wheel.
                        "absolute paths: no change",
+                       skipUnlessCiRun,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        "/": {
                                                Path:      "/",
                                                Mode:      fs.ModeDir | 0755,
-                                               User:      "root",
-                                               Uid:       -1,
-                                               Group:     "root",
-                                               Gid:       -1,
+                                               Uid:       0,
+                                               Gid:       0,
                                                OrigGroup: "group",
                                        },
                                        "/etc": {
                                                Path:      "/etc",
                                                Mode:      fs.ModeDir | 0755,
-                                               User:      "root",
-                                               Uid:       -1,
-                                               Group:     "root",
-                                               Gid:       -1,
+                                               Uid:       0,
+                                               Gid:       0,
                                                OrigGroup: "group",
                                        },
                                        "/tmp": {
                                                Path:      "/tmp",
                                                Mode:      fs.ModeDir | 0777 | fs.ModeSticky,
-                                               User:      "root",
-                                               Uid:       -1,
-                                               Group:     "root",
-                                               Gid:       -1,
-                                               OrigGroup: "group",
-                                       },
-                                       "/var/tmp": {
-                                               Path:      "/var/tmp",
-                                               Mode:      fs.ModeDir | 0777 | fs.ModeSticky,
-                                               User:      "root",
-                                               Uid:       -1,
-                                               Group:     "root",
-                                               Gid:       -1,
+                                               Uid:       0,
+                                               Gid:       0,
                                                OrigGroup: "group",
                                        },
                                },
@@ -311,16 +305,16 @@ func TestSyncFiles(t *testing.T) {
                        },
                        safcm.MsgSyncResp{},
                        []string{
-                               `4: sync remote: files: "/" (group): unchanged`,
-                               `4: sync remote: files: "/etc" (group): unchanged`,
-                               `4: sync remote: files: "/tmp" (group): unchanged`,
-                               `4: sync remote: files: "/var/tmp" (group): unchanged`,
+                               `4: files: "/" (group): unchanged`,
+                               `4: files: "/etc" (group): unchanged`,
+                               `4: files: "/tmp" (group): unchanged`,
                        },
                        nil,
                },
 
                {
                        "triggers: no change",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -375,15 +369,16 @@ func TestSyncFiles(t *testing.T) {
                        },
                        safcm.MsgSyncResp{},
                        []string{
-                               `4: sync remote: files: "." (group): unchanged`,
-                               `4: sync remote: files: "dir" (group): unchanged`,
-                               `4: sync remote: files: "dir/file" (group): unchanged`,
+                               `4: files: "." (group): unchanged`,
+                               `4: files: "dir" (group): unchanged`,
+                               `4: files: "dir/file" (group): unchanged`,
                        },
                        nil,
                },
 
                {
                        "triggers: change root",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -420,10 +415,7 @@ func TestSyncFiles(t *testing.T) {
                                },
                        },
                        func() {
-                               err = os.Chmod(".", 0750)
-                               if err != nil {
-                                       panic(err)
-                               }
+                               ft.CreateDirectoryExists(".", 0750)
                                ft.CreateDirectory("dir", 0755)
                                ft.CreateFile("dir/file", "content\n", 0644)
                        },
@@ -464,18 +456,19 @@ func TestSyncFiles(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "." (group): permission differs drwxr-x--- -> drwx------`,
-                               `3: sync remote: files: "." (group): updating`,
-                               `4: sync remote: files: "." (group): chmodding drwx------`,
-                               `3: sync remote: files: ".": queuing trigger on "."`,
-                               `4: sync remote: files: "dir" (group): unchanged`,
-                               `4: sync remote: files: "dir/file" (group): unchanged`,
+                               `4: files: "." (group): permission differs drwxr-x--- -> drwx------`,
+                               `3: files: "." (group): updating`,
+                               `4: files: "." (group): chmodding drwx------`,
+                               `3: files: ".": queuing trigger on "."`,
+                               `4: files: "dir" (group): unchanged`,
+                               `4: files: "dir/file" (group): unchanged`,
                        },
                        nil,
                },
 
                {
                        "triggers: change middle",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -553,19 +546,20 @@ func TestSyncFiles(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "." (group): unchanged`,
-                               `4: sync remote: files: "dir" (group): permission differs drwxr-x--- -> drwxr-xr-x`,
-                               `3: sync remote: files: "dir" (group): updating`,
-                               `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
-                               `3: sync remote: files: "dir": queuing trigger on "."`,
-                               `3: sync remote: files: "dir": queuing trigger on "dir"`,
-                               `4: sync remote: files: "dir/file" (group): unchanged`,
+                               `4: files: "." (group): unchanged`,
+                               `4: files: "dir" (group): permission differs drwxr-x--- -> drwxr-xr-x`,
+                               `3: files: "dir" (group): updating`,
+                               `4: files: "dir" (group): chmodding drwxr-xr-x`,
+                               `3: files: "dir": queuing trigger on "."`,
+                               `3: files: "dir": queuing trigger on "dir"`,
+                               `4: files: "dir/file" (group): unchanged`,
                        },
                        nil,
                },
 
                {
                        "triggers: change leaf",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -637,21 +631,22 @@ func TestSyncFiles(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "." (group): unchanged`,
-                               `4: sync remote: files: "dir" (group): unchanged`,
-                               `4: sync remote: files: "dir/file" (group): will create`,
-                               `3: sync remote: files: "dir/file" (group): creating`,
-                               `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
-                               `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
-                               `3: sync remote: files: "dir/file": queuing trigger on "."`,
-                               `3: sync remote: files: "dir/file": queuing trigger on "dir"`,
-                               `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
+                               `4: files: "." (group): unchanged`,
+                               `4: files: "dir" (group): unchanged`,
+                               `4: files: "dir/file" (group): will create`,
+                               `3: files: "dir/file" (group): creating`,
+                               `4: files: "dir/file" (group): creating temporary file "dir/.file*"`,
+                               `4: files: "dir/file" (group): renaming "dir/.fileRND"`,
+                               `3: files: "dir/file": queuing trigger on "."`,
+                               `3: files: "dir/file": queuing trigger on "dir"`,
+                               `3: files: "dir/file": queuing trigger on "dir/file"`,
                        },
                        nil,
                },
 
                {
                        "triggers: multiple changes",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -732,36 +727,35 @@ func TestSyncFiles(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "." (group): unchanged`,
-                               `4: sync remote: files: "dir" (group): will create`,
-                               `3: sync remote: files: "dir" (group): creating`,
-                               `4: sync remote: files: "dir" (group): creating directory`,
-                               `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
-                               fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
-                               `3: sync remote: files: "dir": queuing trigger on "."`,
-                               `3: sync remote: files: "dir": queuing trigger on "dir"`,
-                               `4: sync remote: files: "dir/file" (group): will create`,
-                               `3: sync remote: files: "dir/file" (group): creating`,
-                               `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
-                               `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
-                               `4: sync remote: files: "dir/file": skipping trigger on ".", already active`,
-                               `4: sync remote: files: "dir/file": skipping trigger on "dir", already active`,
-                               `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
+                               `4: files: "." (group): unchanged`,
+                               `4: files: "dir" (group): will create`,
+                               `3: files: "dir" (group): creating`,
+                               `4: files: "dir" (group): creating directory`,
+                               `4: files: "dir" (group): chmodding drwxr-xr-x`,
+                               fmt.Sprintf(`4: files: "dir" (group): chowning %d/%d`, uid, gid),
+                               `3: files: "dir": queuing trigger on "."`,
+                               `3: files: "dir": queuing trigger on "dir"`,
+                               `4: files: "dir/file" (group): will create`,
+                               `3: files: "dir/file" (group): creating`,
+                               `4: files: "dir/file" (group): creating temporary file "dir/.file*"`,
+                               `4: files: "dir/file" (group): renaming "dir/.fileRND"`,
+                               `4: files: "dir/file": skipping trigger on ".", already active`,
+                               `4: files: "dir/file": skipping trigger on "dir", already active`,
+                               `3: files: "dir/file": queuing trigger on "dir/file"`,
                        },
                        nil,
                },
 
                {
                        "triggers: absolute paths",
+                       skipUnlessCiRun,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        "/": {
                                                Path:      "/",
                                                Mode:      fs.ModeDir | 0755,
-                                               User:      "root",
-                                               Uid:       -1,
-                                               Group:     "root",
-                                               Gid:       -1,
+                                               Uid:       0,
+                                               Gid:       0,
                                                OrigGroup: "group",
                                                TriggerCommands: []string{
                                                        "echo trigger /",
@@ -770,10 +764,8 @@ func TestSyncFiles(t *testing.T) {
                                        "/tmp": {
                                                Path:      "/tmp",
                                                Mode:      fs.ModeDir | 0777 | fs.ModeSticky,
-                                               User:      "root",
-                                               Uid:       -1,
-                                               Group:     "root",
-                                               Gid:       -1,
+                                               Uid:       0,
+                                               Gid:       0,
                                                OrigGroup: "group",
                                                TriggerCommands: []string{
                                                        "echo trigger /tmp",
@@ -791,16 +783,7 @@ func TestSyncFiles(t *testing.T) {
                                        },
                                },
                        },
-                       func() {
-                               // This is slightly racy but the file name
-                               // should be rare enough that this isn't an
-                               // issue
-                               _, err := os.Stat(tmpTestFilePath)
-                               if err == nil {
-                                       t.Fatalf("%q exists, aborting",
-                                               tmpTestFilePath)
-                               }
-                       },
+                       nil,
                        []string{
                                "/",
                                "/tmp",
@@ -826,15 +809,15 @@ func TestSyncFiles(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "/" (group): unchanged`,
-                               `4: sync remote: files: "/tmp" (group): unchanged`,
-                               `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): will create`,
-                               `3: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating`,
-                               `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating temporary file "/tmp/.safcm-sync-files-test-file*"`,
-                               `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): renaming "/tmp/.safcm-sync-files-test-fileRND"`,
-                               `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/"`,
-                               `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp"`,
-                               `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp/safcm-sync-files-test-file"`,
+                               `4: files: "/" (group): unchanged`,
+                               `4: files: "/tmp" (group): unchanged`,
+                               `4: files: "/tmp/safcm-sync-files-test-file" (group): will create`,
+                               `3: files: "/tmp/safcm-sync-files-test-file" (group): creating`,
+                               `4: files: "/tmp/safcm-sync-files-test-file" (group): creating temporary file "/tmp/.safcm-sync-files-test-file*"`,
+                               `4: files: "/tmp/safcm-sync-files-test-file" (group): renaming "/tmp/.safcm-sync-files-test-fileRND"`,
+                               `3: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/"`,
+                               `3: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp"`,
+                               `3: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp/safcm-sync-files-test-file"`,
                        },
                        nil,
                },
@@ -842,59 +825,51 @@ func TestSyncFiles(t *testing.T) {
 
        for _, tc := range tests {
                t.Run(tc.name, func(t *testing.T) {
-               // Create separate test directory for each test case
-               path := filepath.Join(cwd, "testdata", "files-"+tc.name)
-               err = os.Mkdir(path, 0700)
-               if err != nil {
-                       t.Fatal(err)
-               }
-               err = os.Chdir(path)
-               if err != nil {
-                       t.Fatal(err)
-               }
-
-               if tc.prepare != nil {
-                       tc.prepare()
-               }
-
-               s, res := prepareSync(tc.req, &testRunner{
-                       t:    t,
-               })
-               s.setDefaults()
-
-               err := s.syncFiles()
-               // Ugly but the simplest way to compare errors (including nil)
-               if fmt.Sprintf("%s", err) != fmt.Sprintf("%s", tc.expErr) {
-                       t.Errorf("err = %#v, want %#v",
-                               err, tc.expErr)
-               }
-               dbg := res.Wait()
-               // Remove random file names from result
-               for i, x := range dbg {
-                       dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
-               }
-               if !reflect.DeepEqual(tc.expDbg, dbg) {
-                       t.Errorf("dbg: %s",
-                               cmp.Diff(tc.expDbg, dbg))
-               }
-
-               files, err := ft.WalkDir(path)
-               if err != nil {
-                       t.Fatal(err)
-               }
-               if !reflect.DeepEqual(tc.expFiles, files) {
-                       t.Errorf("files: %s",
-                               cmp.Diff(tc.expFiles, files))
-               }
-
-               if !reflect.DeepEqual(tc.expResp, s.resp) {
-                       t.Errorf("resp: %s",
-                               cmp.Diff(tc.expResp, s.resp))
-               }
-               if !reflect.DeepEqual(tc.triggers, s.triggers) {
-                       t.Errorf("triggers: %s",
-                               cmp.Diff(tc.triggers, s.triggers))
-               }
+                       if tc.skip {
+                               t.SkipNow()
+                       }
+
+                       // Create separate test directory for each test case
+                       path := filepath.Join(cwd, "testdata", "files-"+tc.name)
+                       err := os.Mkdir(path, 0700)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       err = os.Chdir(path)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+
+                       if tc.prepare != nil {
+                               tc.prepare()
+                       }
+
+                       s, res := prepareSync(tc.req, &testRunner{
+                               t: t,
+                       })
+                       err = s.setDefaults()
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+
+                       err = s.syncFiles()
+                       testutil.AssertErrorEqual(t, "err", err, tc.expErr)
+                       dbg := res.Wait()
+                       // Remove random file names from result
+                       for i, x := range dbg {
+                               dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
+                       }
+                       testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
+
+                       files, err := ft.WalkDir(path)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       testutil.AssertEqual(t, "files", files, tc.expFiles)
+
+                       testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
+                       testutil.AssertEqual(t, "triggers",
+                               s.triggers, tc.expTriggers)
                })
        }
 
@@ -942,7 +917,7 @@ func TestSyncFile(t *testing.T) {
        }{
 
                // NOTE: Also update MsgSyncResp in safcm test cases when
-               // changing anything here!
+               // changing the MsgSyncResp struct!
 
                // TODO: Add tests for chown and run them only as root
 
@@ -985,10 +960,10 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "file" (group): will create`,
-                               `3: sync remote: files: "file" (group): creating`,
-                               `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
-                               `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
+                               `4: files: "file" (group): will create`,
+                               `3: files: "file" (group): creating`,
+                               `4: files: "file" (group): creating temporary file ".file*"`,
+                               `4: files: "file" (group): renaming "./.fileRND"`,
                        },
                        nil,
                },
@@ -1024,9 +999,9 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "file" (group): will create`,
-                               `3: sync remote: files: "file" (group): creating`,
-                               `4: sync remote: files: "file" (group): dry-run, skipping changes`,
+                               `4: files: "file" (group): will create`,
+                               `3: files: "file" (group): creating`,
+                               `4: files: "file" (group): dry-run, skipping changes`,
                        },
                        nil,
                },
@@ -1056,7 +1031,7 @@ func TestSyncFile(t *testing.T) {
                        },
                        safcm.MsgSyncResp{},
                        []string{
-                               `4: sync remote: files: "file" (group): unchanged`,
+                               `4: files: "file" (group): unchanged`,
                        },
                        nil,
                },
@@ -1088,7 +1063,7 @@ func TestSyncFile(t *testing.T) {
                        },
                        safcm.MsgSyncResp{},
                        []string{
-                               `4: sync remote: files: "file" (group): unchanged`,
+                               `4: files: "file" (group): unchanged`,
                        },
                        nil,
                },
@@ -1138,10 +1113,10 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
-                               `3: sync remote: files: "file" (group): updating`,
-                               `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
-                               `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
+                               `4: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
+                               `3: files: "file" (group): updating`,
+                               `4: files: "file" (group): creating temporary file ".file*"`,
+                               `4: files: "file" (group): renaming "./.fileRND"`,
                        },
                        nil,
                },
@@ -1196,10 +1171,10 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "file" (group): content differs`,
-                               `3: sync remote: files: "file" (group): updating`,
-                               `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
-                               `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
+                               `4: files: "file" (group): content differs`,
+                               `3: files: "file" (group): updating`,
+                               `4: files: "file" (group): creating temporary file ".file*"`,
+                               `4: files: "file" (group): renaming "./.fileRND"`,
                        },
                        nil,
                },
@@ -1243,10 +1218,10 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "link" (group): will create`,
-                               `3: sync remote: files: "link" (group): creating`,
-                               `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
-                               `4: sync remote: files: "link" (group): renaming ".linkRND"`,
+                               `4: files: "link" (group): will create`,
+                               `3: files: "link" (group): creating`,
+                               `4: files: "link" (group): creating temporary symlink ".linkRND"`,
+                               `4: files: "link" (group): renaming ".linkRND"`,
                        },
                        nil,
                },
@@ -1294,11 +1269,11 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "link" (group): will create`,
-                               `3: sync remote: files: "link" (group): creating`,
-                               `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
-                               `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
-                               `4: sync remote: files: "link" (group): renaming ".linkRND"`,
+                               `4: files: "link" (group): will create`,
+                               `3: files: "link" (group): creating`,
+                               `4: files: "link" (group): creating temporary symlink ".linkRND"`,
+                               `4: files: "link" (group): creating temporary symlink ".linkRND"`,
+                               `4: files: "link" (group): renaming ".linkRND"`,
                        },
                        nil,
                },
@@ -1334,9 +1309,9 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "link" (group): will create`,
-                               `3: sync remote: files: "link" (group): creating`,
-                               `4: sync remote: files: "link" (group): dry-run, skipping changes`,
+                               `4: files: "link" (group): will create`,
+                               `3: files: "link" (group): creating`,
+                               `4: files: "link" (group): dry-run, skipping changes`,
                        },
                        nil,
                },
@@ -1366,7 +1341,7 @@ func TestSyncFile(t *testing.T) {
                        },
                        safcm.MsgSyncResp{},
                        []string{
-                               `4: sync remote: files: "link" (group): unchanged`,
+                               `4: files: "link" (group): unchanged`,
                        },
                        nil,
                },
@@ -1420,10 +1395,10 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "link" (group): content differs`,
-                               `3: sync remote: files: "link" (group): updating`,
-                               `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
-                               `4: sync remote: files: "link" (group): renaming ".linkRND"`,
+                               `4: files: "link" (group): content differs`,
+                               `3: files: "link" (group): updating`,
+                               `4: files: "link" (group): creating temporary symlink ".linkRND"`,
+                               `4: files: "link" (group): renaming ".linkRND"`,
                        },
                        nil,
                },
@@ -1465,11 +1440,11 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "dir" (group): will create`,
-                               `3: sync remote: files: "dir" (group): creating`,
-                               `4: sync remote: files: "dir" (group): creating directory`,
-                               `4: sync remote: files: "dir" (group): chmodding drwx---r-x`,
-                               fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
+                               `4: files: "dir" (group): will create`,
+                               `3: files: "dir" (group): creating`,
+                               `4: files: "dir" (group): creating directory`,
+                               `4: files: "dir" (group): chmodding drwx---r-x`,
+                               fmt.Sprintf(`4: files: "dir" (group): chowning %d/%d`, uid, gid),
                        },
                        nil,
                },
@@ -1504,9 +1479,9 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "dir" (group): will create`,
-                               `3: sync remote: files: "dir" (group): creating`,
-                               `4: sync remote: files: "dir" (group): dry-run, skipping changes`,
+                               `4: files: "dir" (group): will create`,
+                               `3: files: "dir" (group): creating`,
+                               `4: files: "dir" (group): dry-run, skipping changes`,
                        },
                        nil,
                },
@@ -1534,7 +1509,7 @@ func TestSyncFile(t *testing.T) {
                        },
                        safcm.MsgSyncResp{},
                        []string{
-                               `4: sync remote: files: "dir" (group): unchanged`,
+                               `4: files: "dir" (group): unchanged`,
                        },
                        nil,
                },
@@ -1582,9 +1557,9 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "dir" (group): permission differs dtr-x------ -> dgrwxr-xr-x`,
-                               `3: sync remote: files: "dir" (group): updating`,
-                               `4: sync remote: files: "dir" (group): chmodding dgrwxr-xr-x`,
+                               `4: files: "dir" (group): permission differs dtr-x------ -> dgrwxr-xr-x`,
+                               `3: files: "dir" (group): updating`,
+                               `4: files: "dir" (group): chmodding dgrwxr-xr-x`,
                        },
                        nil,
                },
@@ -1638,12 +1613,12 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "path" (group): type differs ---------- -> d---------`,
-                               `3: sync remote: files: "path" (group): updating`,
-                               `4: sync remote: files: "path" (group): removing (due to type change)`,
-                               `4: sync remote: files: "path" (group): creating directory`,
-                               `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
-                               fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
+                               `4: files: "path" (group): type differs ---------- -> d---------`,
+                               `3: files: "path" (group): updating`,
+                               `4: files: "path" (group): removing (due to type change)`,
+                               `4: files: "path" (group): creating directory`,
+                               `4: files: "path" (group): chmodding drwxr-x--x`,
+                               fmt.Sprintf(`4: files: "path" (group): chowning %d/%d`, uid, gid),
                        },
                        nil,
                },
@@ -1698,10 +1673,10 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
-                               `3: sync remote: files: "path" (group): updating`,
-                               `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
-                               `4: sync remote: files: "path" (group): renaming ".pathRND"`,
+                               `4: files: "path" (group): type differs ---------- -> L---------`,
+                               `3: files: "path" (group): updating`,
+                               `4: files: "path" (group): creating temporary symlink ".pathRND"`,
+                               `4: files: "path" (group): renaming ".pathRND"`,
                        },
                        nil,
                },
@@ -1756,10 +1731,10 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "path" (group): type differs L--------- -> ----------`,
-                               `3: sync remote: files: "path" (group): updating`,
-                               `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
-                               `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
+                               `4: files: "path" (group): type differs L--------- -> ----------`,
+                               `3: files: "path" (group): updating`,
+                               `4: files: "path" (group): creating temporary file ".path*"`,
+                               `4: files: "path" (group): renaming "./.pathRND"`,
                        },
                        nil,
                },
@@ -1811,12 +1786,12 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "path" (group): type differs L--------- -> d---------`,
-                               `3: sync remote: files: "path" (group): updating`,
-                               `4: sync remote: files: "path" (group): removing (due to type change)`,
-                               `4: sync remote: files: "path" (group): creating directory`,
-                               `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
-                               fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
+                               `4: files: "path" (group): type differs L--------- -> d---------`,
+                               `3: files: "path" (group): updating`,
+                               `4: files: "path" (group): removing (due to type change)`,
+                               `4: files: "path" (group): creating directory`,
+                               `4: files: "path" (group): chmodding drwxr-x--x`,
+                               fmt.Sprintf(`4: files: "path" (group): chowning %d/%d`, uid, gid),
                        },
                        nil,
                },
@@ -1866,11 +1841,11 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "path" (group): type differs d--------- -> ----------`,
-                               `3: sync remote: files: "path" (group): updating`,
-                               `4: sync remote: files: "path" (group): removing (due to type change)`,
-                               `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
-                               `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
+                               `4: files: "path" (group): type differs d--------- -> ----------`,
+                               `3: files: "path" (group): updating`,
+                               `4: files: "path" (group): removing (due to type change)`,
+                               `4: files: "path" (group): creating temporary file ".path*"`,
+                               `4: files: "path" (group): renaming "./.pathRND"`,
                        },
                        nil,
                },
@@ -1920,11 +1895,11 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "path" (group): type differs d--------- -> L---------`,
-                               `3: sync remote: files: "path" (group): updating`,
-                               `4: sync remote: files: "path" (group): removing (due to type change)`,
-                               `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
-                               `4: sync remote: files: "path" (group): renaming ".pathRND"`,
+                               `4: files: "path" (group): type differs d--------- -> L---------`,
+                               `3: files: "path" (group): updating`,
+                               `4: files: "path" (group): removing (due to type change)`,
+                               `4: files: "path" (group): creating temporary symlink ".pathRND"`,
+                               `4: files: "path" (group): renaming ".pathRND"`,
                        },
                        nil,
                },
@@ -1974,10 +1949,10 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "path" (group): type differs p--------- -> ----------`,
-                               `3: sync remote: files: "path" (group): updating`,
-                               `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
-                               `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
+                               `4: files: "path" (group): type differs p--------- -> ----------`,
+                               `3: files: "path" (group): updating`,
+                               `4: files: "path" (group): creating temporary file ".path*"`,
+                               `4: files: "path" (group): renaming "./.pathRND"`,
                        },
                        nil,
                },
@@ -2027,10 +2002,10 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "path" (group): type differs p--------- -> L---------`,
-                               `3: sync remote: files: "path" (group): updating`,
-                               `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
-                               `4: sync remote: files: "path" (group): renaming ".pathRND"`,
+                               `4: files: "path" (group): type differs p--------- -> L---------`,
+                               `3: files: "path" (group): updating`,
+                               `4: files: "path" (group): creating temporary symlink ".pathRND"`,
+                               `4: files: "path" (group): renaming ".pathRND"`,
                        },
                        nil,
                },
@@ -2078,12 +2053,12 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "path" (group): type differs p--------- -> d---------`,
-                               `3: sync remote: files: "path" (group): updating`,
-                               `4: sync remote: files: "path" (group): removing (due to type change)`,
-                               `4: sync remote: files: "path" (group): creating directory`,
-                               `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
-                               fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
+                               `4: files: "path" (group): type differs p--------- -> d---------`,
+                               `3: files: "path" (group): updating`,
+                               `4: files: "path" (group): removing (due to type change)`,
+                               `4: files: "path" (group): creating directory`,
+                               `4: files: "path" (group): chmodding drwxr-x--x`,
+                               fmt.Sprintf(`4: files: "path" (group): chowning %d/%d`, uid, gid),
                        },
                        nil,
                },
@@ -2133,10 +2108,10 @@ func TestSyncFile(t *testing.T) {
                                },
                        },
                        []string{
-                               `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
-                               `3: sync remote: files: "path" (group): updating`,
-                               `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
-                               `4: sync remote: files: "path" (group): renaming ".pathRND"`,
+                               `4: files: "path" (group): type differs ---------- -> L---------`,
+                               `3: files: "path" (group): updating`,
+                               `4: files: "path" (group): creating temporary symlink ".pathRND"`,
+                               `4: files: "path" (group): renaming ".pathRND"`,
                        },
                        nil,
                },
@@ -2214,9 +2189,9 @@ file
                                },
                        },
                        []string{
-                               `4: sync remote: files: "file" (group): content differs`,
-                               `3: sync remote: files: "file" (group): updating`,
-                               `4: sync remote: files: "file" (group): dry-run, skipping changes`,
+                               `4: files: "file" (group): content differs`,
+                               `3: files: "file" (group): updating`,
+                               `4: files: "file" (group): dry-run, skipping changes`,
                        },
                        nil,
                },
@@ -2264,14 +2239,14 @@ file
                                                        Group: group,
                                                        Gid:   gid,
                                                },
-                                               DataDiff: "Binary files differ, cannot show diff",
+                                               DataDiff: "Binary files differ (3 -> 4 bytes), cannot show diff",
                                        },
                                },
                        },
                        []string{
-                               `4: sync remote: files: "file" (group): content differs`,
-                               `3: sync remote: files: "file" (group): updating`,
-                               `4: sync remote: files: "file" (group): dry-run, skipping changes`,
+                               `4: files: "file" (group): content differs`,
+                               `3: files: "file" (group): updating`,
+                               `4: files: "file" (group): dry-run, skipping changes`,
                        },
                        nil,
                },
@@ -2320,7 +2295,7 @@ file
                                                        Gid:   gid,
                                                },
                                                DataDiff: `@@ -1,2 +1,2 @@
--<binary content>
+-<binary content, 3 bytes>
 +content
  
 `,
@@ -2328,9 +2303,9 @@ file
                                },
                        },
                        []string{
-                               `4: sync remote: files: "file" (group): content differs`,
-                               `3: sync remote: files: "file" (group): updating`,
-                               `4: sync remote: files: "file" (group): dry-run, skipping changes`,
+                               `4: files: "file" (group): content differs`,
+                               `3: files: "file" (group): updating`,
+                               `4: files: "file" (group): dry-run, skipping changes`,
                        },
                        nil,
                },
@@ -2380,16 +2355,16 @@ file
                                                },
                                                DataDiff: `@@ -1,2 +1,2 @@
 -content
-+<binary content>
++<binary content, 4 bytes>
  
 `,
                                        },
                                },
                        },
                        []string{
-                               `4: sync remote: files: "file" (group): content differs`,
-                               `3: sync remote: files: "file" (group): updating`,
-                               `4: sync remote: files: "file" (group): dry-run, skipping changes`,
+                               `4: files: "file" (group): content differs`,
+                               `3: files: "file" (group): updating`,
+                               `4: files: "file" (group): dry-run, skipping changes`,
                        },
                        nil,
                },
@@ -2397,63 +2372,50 @@ file
 
        for _, tc := range tests {
                t.Run(tc.name, func(t *testing.T) {
-               // Create separate test directory for each test case
-               path := filepath.Join(cwd, "testdata", "file-"+tc.name)
-               err = os.Mkdir(path, 0700)
-               if err != nil {
-                       t.Fatal(err)
-               }
-               err = os.Chdir(path)
-               if err != nil {
-                       t.Fatal(err)
-               }
-
-               if tc.prepare != nil {
-                       tc.prepare()
-               }
-
-               s, res := prepareSync(tc.req, &testRunner{
-                       t:    t,
-               })
-               s.setDefaults()
-
-               // Deterministic temporary symlink names
-               rand.Seed(0)
-
-               var changed bool
-               err := s.syncFile(tc.file, &changed)
-               // Ugly but the simplest way to compare errors (including nil)
-               if fmt.Sprintf("%s", err) != fmt.Sprintf("%s", tc.expErr) {
-                       t.Errorf("err = %#v, want %#v",
-                               err, tc.expErr)
-               }
-               dbg := res.Wait()
-               // Remove random file names from result
-               for i, x := range dbg {
-                       dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
-               }
-               if !reflect.DeepEqual(tc.expDbg, dbg) {
-                       t.Errorf("dbg: %s",
-                               cmp.Diff(tc.expDbg, dbg))
-               }
-
-               files, err := ft.WalkDir(path)
-               if err != nil {
-                       t.Fatal(err)
-               }
-               if !reflect.DeepEqual(tc.expFiles, files) {
-                       t.Errorf("files: %s",
-                               cmp.Diff(tc.expFiles, files))
-               }
-
-               if tc.expChanged != changed {
-                       t.Errorf("changed = %#v, want %#v",
-                               changed, tc.expChanged)
-               }
-               if !reflect.DeepEqual(tc.expResp, s.resp) {
-                       t.Errorf("resp: %s",
-                               cmp.Diff(tc.expResp, s.resp))
-               }
+                       // Create separate test directory for each test case
+                       path := filepath.Join(cwd, "testdata", "file-"+tc.name)
+                       err := os.Mkdir(path, 0700)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       err = os.Chdir(path)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+
+                       if tc.prepare != nil {
+                               tc.prepare()
+                       }
+
+                       s, res := prepareSync(tc.req, &testRunner{
+                               t: t,
+                       })
+                       err = s.setDefaults()
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+
+                       // Deterministic temporary symlink names
+                       rand.Seed(0)
+
+                       var changed bool
+                       err = s.syncFile(tc.file, &changed)
+                       testutil.AssertErrorEqual(t, "err", err, tc.expErr)
+                       dbg := res.Wait()
+                       // Remove random file names from result
+                       for i, x := range dbg {
+                               dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
+                       }
+                       testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
+
+                       files, err := ft.WalkDir(path)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       testutil.AssertEqual(t, "files", files, tc.expFiles)
+
+                       testutil.AssertEqual(t, "changed", changed, tc.expChanged)
+                       testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
                })
        }