]> ruderich.org/simon Gitweb - safcm/safcm.git/blobdiff - cmd/safcm-remote/sync/files_test.go
sync: tests: check return value of setDefaults()
[safcm/safcm.git] / cmd / safcm-remote / sync / files_test.go
index 22daa63fae92537239b21cb3fab375f7329ab313..5787404519936d519272a5050cebfec467ec64b1 100644 (file)
@@ -20,66 +20,15 @@ import (
        "io/fs"
        "math/rand"
        "os"
-       "os/user"
        "path/filepath"
-       "reflect"
        "regexp"
-       "strconv"
-       "syscall"
        "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"
 )
 
-type File struct {
-       Path string
-       Mode fs.FileMode
-       Data []byte
-}
-
-func walkDir(basePath string) ([]File, error) {
-       var res []File
-       err := filepath.WalkDir(basePath, func(path string, d fs.DirEntry, err error) error {
-               if err != nil {
-                       return err
-               }
-               info, err := d.Info()
-               if err != nil {
-                       return err
-               }
-               rel, err := filepath.Rel(basePath, path)
-               if err != nil {
-                       return err
-               }
-
-               f := File{
-                       Path: rel,
-                       Mode: info.Mode(),
-               }
-               if f.Mode.Type() == 0 {
-                       x, err := os.ReadFile(path)
-                       if err != nil {
-                               return err
-                       }
-                       f.Data = x
-               } else if f.Mode.Type() == fs.ModeSymlink {
-                       x, err := os.Readlink(path)
-                       if err != nil {
-                               return err
-                       }
-                       f.Data = []byte(x)
-               }
-               res = append(res, f)
-               return nil
-       })
-       if err != nil {
-               return nil, err
-       }
-       return res, nil
-}
-
 var randFilesRegexp = regexp.MustCompile(`\d+"$`)
 
 func TestSyncFiles(t *testing.T) {
@@ -98,23 +47,26 @@ func TestSyncFiles(t *testing.T) {
                t.Fatal(err)
        }
 
-       root := File{
+       root := ft.File{
                Path: ".",
                Mode: fs.ModeDir | 0700,
        }
-       user, uid, group, gid := currentUserAndGroup()
+       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 []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
@@ -125,6 +77,7 @@ func TestSyncFiles(t *testing.T) {
 
                {
                        "basic: create",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -153,7 +106,7 @@ func TestSyncFiles(t *testing.T) {
                        },
                        nil,
                        nil,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "dir",
@@ -208,6 +161,7 @@ func TestSyncFiles(t *testing.T) {
 
                {
                        "basic: no change",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -235,11 +189,11 @@ func TestSyncFiles(t *testing.T) {
                                },
                        },
                        func() {
-                               createDirectory("dir", 0755)
-                               createFile("dir/file", "content\n", 0644)
+                               ft.CreateDirectory("dir", 0755)
+                               ft.CreateFile("dir/file", "content\n", 0644)
                        },
                        nil,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "dir",
@@ -262,6 +216,7 @@ func TestSyncFiles(t *testing.T) {
 
                {
                        "invalid File: user",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -276,7 +231,7 @@ func TestSyncFiles(t *testing.T) {
                        },
                        nil,
                        nil,
-                       []File{
+                       []ft.File{
                                root,
                        },
                        safcm.MsgSyncResp{},
@@ -285,6 +240,7 @@ func TestSyncFiles(t *testing.T) {
                },
                {
                        "invalid File: group",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -299,7 +255,7 @@ func TestSyncFiles(t *testing.T) {
                        },
                        nil,
                        nil,
-                       []File{
+                       []ft.File{
                                root,
                        },
                        safcm.MsgSyncResp{},
@@ -312,50 +268,39 @@ 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",
                                        },
                                },
                        },
                        nil,
                        nil,
-                       []File{
+                       []ft.File{
                                root,
                        },
                        safcm.MsgSyncResp{},
@@ -363,13 +308,13 @@ func TestSyncFiles(t *testing.T) {
                                `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`,
                        },
                        nil,
                },
 
                {
                        "triggers: no change",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -406,11 +351,11 @@ func TestSyncFiles(t *testing.T) {
                                },
                        },
                        func() {
-                               createDirectory("dir", 0755)
-                               createFile("dir/file", "content\n", 0644)
+                               ft.CreateDirectory("dir", 0755)
+                               ft.CreateFile("dir/file", "content\n", 0644)
                        },
                        nil,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "dir",
@@ -433,6 +378,7 @@ func TestSyncFiles(t *testing.T) {
 
                {
                        "triggers: change root",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -473,13 +419,13 @@ func TestSyncFiles(t *testing.T) {
                                if err != nil {
                                        panic(err)
                                }
-                               createDirectory("dir", 0755)
-                               createFile("dir/file", "content\n", 0644)
+                               ft.CreateDirectory("dir", 0755)
+                               ft.CreateFile("dir/file", "content\n", 0644)
                        },
                        []string{
                                ".",
                        },
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "dir",
@@ -525,6 +471,7 @@ func TestSyncFiles(t *testing.T) {
 
                {
                        "triggers: change middle",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -561,14 +508,14 @@ func TestSyncFiles(t *testing.T) {
                                },
                        },
                        func() {
-                               createDirectory("dir", 0750)
-                               createFile("dir/file", "content\n", 0644)
+                               ft.CreateDirectory("dir", 0750)
+                               ft.CreateFile("dir/file", "content\n", 0644)
                        },
                        []string{
                                ".",
                                "dir",
                        },
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "dir",
@@ -615,6 +562,7 @@ func TestSyncFiles(t *testing.T) {
 
                {
                        "triggers: change leaf",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -651,14 +599,14 @@ func TestSyncFiles(t *testing.T) {
                                },
                        },
                        func() {
-                               createDirectory("dir", 0755)
+                               ft.CreateDirectory("dir", 0755)
                        },
                        []string{
                                ".",
                                "dir",
                                "dir/file",
                        },
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "dir",
@@ -701,6 +649,7 @@ func TestSyncFiles(t *testing.T) {
 
                {
                        "triggers: multiple changes",
+                       false,
                        safcm.MsgSyncReq{
                                Files: map[string]*safcm.File{
                                        ".": {
@@ -742,7 +691,7 @@ func TestSyncFiles(t *testing.T) {
                                "dir",
                                "dir/file",
                        },
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "dir",
@@ -802,15 +751,14 @@ func TestSyncFiles(t *testing.T) {
 
                {
                        "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 /",
@@ -819,10 +767,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",
@@ -840,23 +786,14 @@ 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",
                                // Don't use variable for more robust test
                                "/tmp/safcm-sync-files-test-file",
                        },
-                       []File{
+                       []ft.File{
                                root,
                        },
                        safcm.MsgSyncResp{
@@ -890,60 +827,53 @@ func TestSyncFiles(t *testing.T) {
        }
 
        for _, tc := range tests {
-               // 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)
-               }
+               t.Run(tc.name, func(t *testing.T) {
+                       if tc.skip {
+                               t.SkipNow()
+                       }
 
-               if tc.prepare != nil {
-                       tc.prepare()
-               }
+                       // 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)
+                       }
 
-               s, res := prepareSync(tc.req, &testRunner{
-                       t:    t,
-                       name: tc.name,
-               })
-               s.setDefaults()
+                       if tc.prepare != nil {
+                               tc.prepare()
+                       }
 
-               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("%s: err = %#v, want %#v",
-                               tc.name, 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("%s: dbg: %s", tc.name,
-                               cmp.Diff(tc.expDbg, dbg))
-               }
+                       s, res := prepareSync(tc.req, &testRunner{
+                               t: t,
+                       })
+                       err = s.setDefaults()
+                       if err != nil {
+                               t.Fatal(err)
+                       }
 
-               files, err := walkDir(path)
-               if err != nil {
-                       t.Fatal(err)
-               }
-               if !reflect.DeepEqual(tc.expFiles, files) {
-                       t.Errorf("%s: files: %s", tc.name,
-                               cmp.Diff(tc.expFiles, files))
-               }
+                       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)
 
-               if !reflect.DeepEqual(tc.expResp, s.resp) {
-                       t.Errorf("%s: resp: %s", tc.name,
-                               cmp.Diff(tc.expResp, s.resp))
-               }
-               if !reflect.DeepEqual(tc.triggers, s.triggers) {
-                       t.Errorf("%s: triggers: %s", tc.name,
-                               cmp.Diff(tc.triggers, s.triggers))
-               }
+                       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)
+               })
        }
 
        os.Remove(tmpTestFilePath)
@@ -971,11 +901,11 @@ func TestSyncFile(t *testing.T) {
                t.Fatal(err)
        }
 
-       root := File{
+       root := ft.File{
                Path: ".",
                Mode: fs.ModeDir | 0700,
        }
-       user, uid, group, gid := currentUserAndGroup()
+       user, uid, group, gid := ft.CurrentUserAndGroup()
 
        tests := []struct {
                name       string
@@ -983,7 +913,7 @@ func TestSyncFile(t *testing.T) {
                file       *safcm.File
                prepare    func()
                expChanged bool
-               expFiles   []File
+               expFiles   []ft.File
                expResp    safcm.MsgSyncResp
                expDbg     []string
                expErr     error
@@ -1009,7 +939,7 @@ func TestSyncFile(t *testing.T) {
                        },
                        nil,
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "file",
@@ -1055,7 +985,7 @@ func TestSyncFile(t *testing.T) {
                        },
                        nil,
                        true,
-                       []File{root},
+                       []ft.File{root},
                        safcm.MsgSyncResp{
                                FileChanges: []safcm.FileChange{
                                        {
@@ -1091,10 +1021,10 @@ func TestSyncFile(t *testing.T) {
                                OrigGroup: "group",
                        },
                        func() {
-                               createFile("file", "content\n", 0644)
+                               ft.CreateFile("file", "content\n", 0644)
                        },
                        false,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "file",
@@ -1123,10 +1053,10 @@ func TestSyncFile(t *testing.T) {
                                OrigGroup: "group",
                        },
                        func() {
-                               createFile("file", "content\n", 0644)
+                               ft.CreateFile("file", "content\n", 0644)
                        },
                        false,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "file",
@@ -1153,10 +1083,10 @@ func TestSyncFile(t *testing.T) {
                                OrigGroup: "group",
                        },
                        func() {
-                               createFile("file", "content\n", 0755)
+                               ft.CreateFile("file", "content\n", 0755)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "file",
@@ -1206,10 +1136,10 @@ func TestSyncFile(t *testing.T) {
                                OrigGroup: "group",
                        },
                        func() {
-                               createFile("file", "old content\n", 0644)
+                               ft.CreateFile("file", "old content\n", 0644)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "file",
@@ -1267,7 +1197,7 @@ func TestSyncFile(t *testing.T) {
                        },
                        nil,
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "link",
@@ -1310,10 +1240,10 @@ func TestSyncFile(t *testing.T) {
                                OrigGroup: "group",
                        },
                        func() {
-                               createFile(".link8717895732742165505", "", 0600)
+                               ft.CreateFile(".link8717895732742165505", "", 0600)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: ".link8717895732742165505",
@@ -1365,7 +1295,7 @@ func TestSyncFile(t *testing.T) {
                        },
                        nil,
                        true,
-                       []File{root},
+                       []ft.File{root},
                        safcm.MsgSyncResp{
                                FileChanges: []safcm.FileChange{
                                        {
@@ -1401,10 +1331,10 @@ func TestSyncFile(t *testing.T) {
                                OrigGroup: "group",
                        },
                        func() {
-                               createSymlink("link", "target")
+                               ft.CreateSymlink("link", "target")
                        },
                        false,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "link",
@@ -1431,10 +1361,10 @@ func TestSyncFile(t *testing.T) {
                                OrigGroup: "group",
                        },
                        func() {
-                               createSymlink("link", "old-target")
+                               ft.CreateSymlink("link", "old-target")
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "link",
@@ -1490,7 +1420,7 @@ func TestSyncFile(t *testing.T) {
                        },
                        nil,
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "dir",
@@ -1535,7 +1465,7 @@ func TestSyncFile(t *testing.T) {
                        },
                        nil,
                        true,
-                       []File{root},
+                       []ft.File{root},
                        safcm.MsgSyncResp{
                                FileChanges: []safcm.FileChange{
                                        {
@@ -1570,10 +1500,10 @@ func TestSyncFile(t *testing.T) {
                                OrigGroup: "group",
                        },
                        func() {
-                               createDirectory("dir", 0755)
+                               ft.CreateDirectory("dir", 0755)
                        },
                        false,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "dir",
@@ -1598,10 +1528,10 @@ func TestSyncFile(t *testing.T) {
                                OrigGroup: "group",
                        },
                        func() {
-                               createDirectory("dir", 0500|fs.ModeSticky)
+                               ft.CreateDirectory("dir", 0500|fs.ModeSticky)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "dir",
@@ -1650,10 +1580,10 @@ func TestSyncFile(t *testing.T) {
                                OrigGroup: "group",
                        },
                        func() {
-                               createFile("path", "content\n", 0644)
+                               ft.CreateFile("path", "content\n", 0644)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "path",
@@ -1708,10 +1638,10 @@ func TestSyncFile(t *testing.T) {
                                Data:      []byte("target"),
                        },
                        func() {
-                               createFile("path", "content\n", 0644)
+                               ft.CreateFile("path", "content\n", 0644)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "path",
@@ -1766,10 +1696,10 @@ func TestSyncFile(t *testing.T) {
                                Data:      []byte("content\n"),
                        },
                        func() {
-                               createSymlink("path", "target")
+                               ft.CreateSymlink("path", "target")
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "path",
@@ -1823,10 +1753,10 @@ func TestSyncFile(t *testing.T) {
                                OrigGroup: "group",
                        },
                        func() {
-                               createSymlink("path", "target")
+                               ft.CreateSymlink("path", "target")
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "path",
@@ -1881,10 +1811,10 @@ func TestSyncFile(t *testing.T) {
                                Data:      []byte("content\n"),
                        },
                        func() {
-                               createDirectory("path", 0777)
+                               ft.CreateDirectory("path", 0777)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "path",
@@ -1935,10 +1865,10 @@ func TestSyncFile(t *testing.T) {
                                Data:      []byte("target"),
                        },
                        func() {
-                               createDirectory("path", 0777)
+                               ft.CreateDirectory("path", 0777)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "path",
@@ -1989,10 +1919,10 @@ func TestSyncFile(t *testing.T) {
                                Data:      []byte("content\n"),
                        },
                        func() {
-                               createFifo("path", 0666)
+                               ft.CreateFifo("path", 0666)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "path",
@@ -2042,10 +1972,10 @@ func TestSyncFile(t *testing.T) {
                                Data:      []byte("target"),
                        },
                        func() {
-                               createFifo("path", 0666)
+                               ft.CreateFifo("path", 0666)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "path",
@@ -2094,10 +2024,10 @@ func TestSyncFile(t *testing.T) {
                                OrigGroup: "group",
                        },
                        func() {
-                               createFifo("path", 0666)
+                               ft.CreateFifo("path", 0666)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "path",
@@ -2148,10 +2078,10 @@ func TestSyncFile(t *testing.T) {
                                Data:      []byte("target"),
                        },
                        func() {
-                               createFile("path", "target", 0644)
+                               ft.CreateFile("path", "target", 0644)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "path",
@@ -2211,14 +2141,14 @@ file
                                OrigGroup: "group",
                        },
                        func() {
-                               createFile("file", `this
+                               ft.CreateFile("file", `this
 is
 file
 !
 `, 0644)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "file",
@@ -2283,10 +2213,10 @@ file
                                OrigGroup: "group",
                        },
                        func() {
-                               createFile("file", "\x00\x01\x02", 0644)
+                               ft.CreateFile("file", "\x00\x01\x02", 0644)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "file",
@@ -2338,10 +2268,10 @@ file
                                OrigGroup: "group",
                        },
                        func() {
-                               createFile("file", "\x00\x01\x02", 0644)
+                               ft.CreateFile("file", "\x00\x01\x02", 0644)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "file",
@@ -2397,10 +2327,10 @@ file
                                OrigGroup: "group",
                        },
                        func() {
-                               createFile("file", "content\n", 0644)
+                               ft.CreateFile("file", "content\n", 0644)
                        },
                        true,
-                       []File{
+                       []ft.File{
                                root,
                                {
                                        Path: "file",
@@ -2444,64 +2374,52 @@ file
        }
 
        for _, tc := range tests {
-               // 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)
-               }
+               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()
-               }
+                       if tc.prepare != nil {
+                               tc.prepare()
+                       }
 
-               s, res := prepareSync(tc.req, &testRunner{
-                       t:    t,
-                       name: tc.name,
-               })
-               s.setDefaults()
+                       s, res := prepareSync(tc.req, &testRunner{
+                               t: t,
+                       })
+                       err = s.setDefaults()
+                       if err != nil {
+                               t.Fatal(err)
+                       }
 
-               // Deterministic temporary symlink names
-               rand.Seed(0)
+                       // 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("%s: err = %#v, want %#v",
-                               tc.name, 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("%s: dbg: %s", tc.name,
-                               cmp.Diff(tc.expDbg, dbg))
-               }
+                       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 := walkDir(path)
-               if err != nil {
-                       t.Fatal(err)
-               }
-               if !reflect.DeepEqual(tc.expFiles, files) {
-                       t.Errorf("%s: files: %s", tc.name,
-                               cmp.Diff(tc.expFiles, files))
-               }
+                       files, err := ft.WalkDir(path)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       testutil.AssertEqual(t, "files", files, tc.expFiles)
 
-               if tc.expChanged != changed {
-                       t.Errorf("%s: changed = %#v, want %#v",
-                               tc.name, changed, tc.expChanged)
-               }
-               if !reflect.DeepEqual(tc.expResp, s.resp) {
-                       t.Errorf("%s: resp: %s", tc.name,
-                               cmp.Diff(tc.expResp, s.resp))
-               }
+                       testutil.AssertEqual(t, "changed", changed, tc.expChanged)
+                       testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
+               })
        }
 
        if !t.Failed() {
@@ -2511,62 +2429,3 @@ file
                }
        }
 }
-
-// Helper functions
-
-func createFile(path string, data string, mode fs.FileMode) {
-       err := os.WriteFile(path, []byte(data), 0644)
-       if err != nil {
-               panic(err)
-       }
-       err = os.Chmod(path, mode)
-       if err != nil {
-               panic(err)
-       }
-}
-func createSymlink(path string, data string) {
-       err := os.Symlink(data, path)
-       if err != nil {
-               panic(err)
-       }
-}
-func createDirectory(path string, mode fs.FileMode) {
-       err := os.Mkdir(path, 0700)
-       if err != nil {
-               panic(err)
-       }
-       err = os.Chmod(path, mode)
-       if err != nil {
-               panic(err)
-       }
-}
-func createFifo(path string, mode fs.FileMode) {
-       err := syscall.Mkfifo(path, 0600)
-       if err != nil {
-               panic(err)
-       }
-       err = os.Chmod(path, mode)
-       if err != nil {
-               panic(err)
-       }
-}
-
-func currentUserAndGroup() (string, int, string, int) {
-       u, err := user.Current()
-       if err != nil {
-               panic(err)
-       }
-       g, err := user.LookupGroupId(u.Gid)
-       if err != nil {
-               panic(err)
-       }
-       uid, err := strconv.Atoi(u.Uid)
-       if err != nil {
-               panic(err)
-       }
-       gid, err := strconv.Atoi(g.Gid)
-       if err != nil {
-               panic(err)
-       }
-       return u.Username, uid, g.Name, gid
-}