"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) {
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
- // 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{
".": {
},
nil,
nil,
- []File{
+ []ft.File{
root,
{
Path: "dir",
},
},
[]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{
".": {
},
},
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",
},
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{
".": {
},
nil,
nil,
- []File{
+ []ft.File{
root,
},
safcm.MsgSyncResp{},
},
{
"invalid File: group",
+ false,
safcm.MsgSyncReq{
Files: map[string]*safcm.File{
".": {
},
nil,
nil,
- []File{
+ []ft.File{
root,
},
safcm.MsgSyncResp{},
// 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{},
[]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{
".": {
},
},
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",
},
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{
".": {
},
},
func() {
- err = os.Chmod(".", 0750)
- if err != nil {
- panic(err)
- }
- createDirectory("dir", 0755)
- createFile("dir/file", "content\n", 0644)
+ ft.CreateDirectoryExists(".", 0750)
+ ft.CreateDirectory("dir", 0755)
+ ft.CreateFile("dir/file", "content\n", 0644)
},
[]string{
".",
},
- []File{
+ []ft.File{
root,
{
Path: "dir",
},
},
[]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{
".": {
},
},
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",
},
},
[]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{
".": {
},
},
func() {
- createDirectory("dir", 0755)
+ ft.CreateDirectory("dir", 0755)
},
[]string{
".",
"dir",
"dir/file",
},
- []File{
+ []ft.File{
root,
{
Path: "dir",
},
},
[]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{
".": {
"dir",
"dir/file",
},
- []File{
+ []ft.File{
root,
{
Path: "dir",
},
},
[]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 /",
"/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",
},
},
},
- 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{
},
},
[]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,
},
}
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)
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
file *safcm.File
prepare func()
expChanged bool
- expFiles []File
+ 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!
// TODO: Add tests for chown and run them only as root
},
nil,
true,
- []File{
+ []ft.File{
root,
{
Path: "file",
},
},
[]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,
},
},
nil,
true,
- []File{root},
+ []ft.File{root},
safcm.MsgSyncResp{
FileChanges: []safcm.FileChange{
{
},
},
[]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,
},
OrigGroup: "group",
},
func() {
- createFile("file", "content\n", 0644)
+ ft.CreateFile("file", "content\n", 0644)
},
false,
- []File{
+ []ft.File{
root,
{
Path: "file",
},
safcm.MsgSyncResp{},
[]string{
- `4: sync remote: files: "file" (group): unchanged`,
+ `4: files: "file" (group): unchanged`,
},
nil,
},
OrigGroup: "group",
},
func() {
- createFile("file", "content\n", 0644)
+ ft.CreateFile("file", "content\n", 0644)
},
false,
- []File{
+ []ft.File{
root,
{
Path: "file",
},
safcm.MsgSyncResp{},
[]string{
- `4: sync remote: files: "file" (group): unchanged`,
+ `4: files: "file" (group): unchanged`,
},
nil,
},
OrigGroup: "group",
},
func() {
- createFile("file", "content\n", 0755)
+ ft.CreateFile("file", "content\n", 0755)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "file",
},
},
[]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,
},
OrigGroup: "group",
},
func() {
- createFile("file", "old content\n", 0644)
+ ft.CreateFile("file", "old content\n", 0644)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "file",
},
},
[]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,
},
},
nil,
true,
- []File{
+ []ft.File{
root,
{
Path: "link",
},
},
[]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,
},
OrigGroup: "group",
},
func() {
- createFile(".link8717895732742165505", "", 0600)
+ ft.CreateFile(".link8717895732742165505", "", 0600)
},
true,
- []File{
+ []ft.File{
root,
{
Path: ".link8717895732742165505",
},
},
[]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,
},
},
nil,
true,
- []File{root},
+ []ft.File{root},
safcm.MsgSyncResp{
FileChanges: []safcm.FileChange{
{
},
},
[]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,
},
OrigGroup: "group",
},
func() {
- createSymlink("link", "target")
+ ft.CreateSymlink("link", "target")
},
false,
- []File{
+ []ft.File{
root,
{
Path: "link",
},
safcm.MsgSyncResp{},
[]string{
- `4: sync remote: files: "link" (group): unchanged`,
+ `4: files: "link" (group): unchanged`,
},
nil,
},
OrigGroup: "group",
},
func() {
- createSymlink("link", "old-target")
+ ft.CreateSymlink("link", "old-target")
},
true,
- []File{
+ []ft.File{
root,
{
Path: "link",
},
},
[]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,
},
},
nil,
true,
- []File{
+ []ft.File{
root,
{
Path: "dir",
},
},
[]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,
},
},
nil,
true,
- []File{root},
+ []ft.File{root},
safcm.MsgSyncResp{
FileChanges: []safcm.FileChange{
{
},
},
[]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,
},
OrigGroup: "group",
},
func() {
- createDirectory("dir", 0755)
+ ft.CreateDirectory("dir", 0755)
},
false,
- []File{
+ []ft.File{
root,
{
Path: "dir",
},
safcm.MsgSyncResp{},
[]string{
- `4: sync remote: files: "dir" (group): unchanged`,
+ `4: files: "dir" (group): unchanged`,
},
nil,
},
OrigGroup: "group",
},
func() {
- createDirectory("dir", 0500|fs.ModeSticky)
+ ft.CreateDirectory("dir", 0500|fs.ModeSticky)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "dir",
},
},
[]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,
},
OrigGroup: "group",
},
func() {
- createFile("path", "content\n", 0644)
+ ft.CreateFile("path", "content\n", 0644)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "path",
},
},
[]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,
},
Data: []byte("target"),
},
func() {
- createFile("path", "content\n", 0644)
+ ft.CreateFile("path", "content\n", 0644)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "path",
},
},
[]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,
},
Data: []byte("content\n"),
},
func() {
- createSymlink("path", "target")
+ ft.CreateSymlink("path", "target")
},
true,
- []File{
+ []ft.File{
root,
{
Path: "path",
},
},
[]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,
},
OrigGroup: "group",
},
func() {
- createSymlink("path", "target")
+ ft.CreateSymlink("path", "target")
},
true,
- []File{
+ []ft.File{
root,
{
Path: "path",
},
},
[]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,
},
Data: []byte("content\n"),
},
func() {
- createDirectory("path", 0777)
+ ft.CreateDirectory("path", 0777)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "path",
},
},
[]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,
},
Data: []byte("target"),
},
func() {
- createDirectory("path", 0777)
+ ft.CreateDirectory("path", 0777)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "path",
},
},
[]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,
},
Data: []byte("content\n"),
},
func() {
- createFifo("path", 0666)
+ ft.CreateFifo("path", 0666)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "path",
},
},
[]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,
},
Data: []byte("target"),
},
func() {
- createFifo("path", 0666)
+ ft.CreateFifo("path", 0666)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "path",
},
},
[]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,
},
OrigGroup: "group",
},
func() {
- createFifo("path", 0666)
+ ft.CreateFifo("path", 0666)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "path",
},
},
[]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,
},
Data: []byte("target"),
},
func() {
- createFile("path", "target", 0644)
+ ft.CreateFile("path", "target", 0644)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "path",
},
},
[]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,
},
OrigGroup: "group",
},
func() {
- createFile("file", `this
+ ft.CreateFile("file", `this
is
file
!
`, 0644)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "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,
},
OrigGroup: "group",
},
func() {
- createFile("file", "\x00\x01\x02", 0644)
+ ft.CreateFile("file", "\x00\x01\x02", 0644)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "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,
},
OrigGroup: "group",
},
func() {
- createFile("file", "\x00\x01\x02", 0644)
+ ft.CreateFile("file", "\x00\x01\x02", 0644)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "file",
Gid: gid,
},
DataDiff: `@@ -1,2 +1,2 @@
--<binary content>
+-<binary content, 3 bytes>
+content
`,
},
},
[]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,
},
OrigGroup: "group",
},
func() {
- createFile("file", "content\n", 0644)
+ ft.CreateFile("file", "content\n", 0644)
},
true,
- []File{
+ []ft.File{
root,
{
Path: "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,
},
}
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() {
}
}
}
-
-// 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
-}