1 // Copyright (C) 2021 Simon Ruderich
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
31 "github.com/google/go-cmp/cmp"
33 "ruderich.org/simon/safcm"
42 func walkDir(basePath string) ([]File, error) {
44 err := filepath.WalkDir(basePath, func(path string, d fs.DirEntry, err error) error {
52 rel, err := filepath.Rel(basePath, path)
61 if f.Mode.Type() == 0 {
62 x, err := os.ReadFile(path)
67 } else if f.Mode.Type() == fs.ModeSymlink {
68 x, err := os.Readlink(path)
83 var randFilesRegexp = regexp.MustCompile(`\d+"$`)
85 func TestSyncFiles(t *testing.T) {
86 cwd, err := os.Getwd()
92 err = os.RemoveAll("testdata")
96 err = os.Mkdir("testdata", 0700)
103 Mode: fs.ModeDir | 0700,
105 user, uid, group, gid := currentUserAndGroup()
107 tmpTestFilePath := "/tmp/safcm-sync-files-test-file"
115 expResp safcm.MsgSyncResp
120 // NOTE: Also update MsgSyncResp in safcm test cases when
121 // changing anything here!
123 // See TestSyncFile() for most file related tests. This
124 // function only tests the overall results and triggers.
129 Files: map[string]*safcm.File{
132 Mode: fs.ModeDir | 0700,
139 Mode: fs.ModeDir | 0755,
149 Data: []byte("content\n"),
160 Mode: fs.ModeDir | 0755,
165 Data: []byte("content\n"),
169 FileChanges: []safcm.FileChange{
173 New: safcm.FileChangeInfo{
174 Mode: fs.ModeDir | 0755,
184 New: safcm.FileChangeInfo{
195 `4: sync remote: files: "." (group): unchanged`,
196 `4: sync remote: files: "dir" (group): will create`,
197 `3: sync remote: files: "dir" (group): creating`,
198 `4: sync remote: files: "dir" (group): creating directory`,
199 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
200 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
201 `4: sync remote: files: "dir/file" (group): will create`,
202 `3: sync remote: files: "dir/file" (group): creating`,
203 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
204 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
212 Files: map[string]*safcm.File{
215 Mode: fs.ModeDir | 0700,
222 Mode: fs.ModeDir | 0755,
232 Data: []byte("content\n"),
238 createDirectory("dir", 0755)
239 createFile("dir/file", "content\n", 0644)
246 Mode: fs.ModeDir | 0755,
251 Data: []byte("content\n"),
256 `4: sync remote: files: "." (group): unchanged`,
257 `4: sync remote: files: "dir" (group): unchanged`,
258 `4: sync remote: files: "dir/file" (group): unchanged`,
264 "invalid File: user",
266 Files: map[string]*safcm.File{
269 Mode: fs.ModeDir | 0700,
284 fmt.Errorf("\".\": cannot set both User (\"user\") and Uid (1)"),
287 "invalid File: group",
289 Files: map[string]*safcm.File{
292 Mode: fs.ModeDir | 0700,
307 fmt.Errorf("\".\": cannot set both Group (\"group\") and Gid (1)"),
311 // We use relative paths for most tests because we
312 // don't want to modify the running system. Use this
313 // test (and the one below for triggers) as a basic
314 // check that absolute paths work.
315 "absolute paths: no change",
317 Files: map[string]*safcm.File{
320 Mode: fs.ModeDir | 0755,
329 Mode: fs.ModeDir | 0755,
338 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
347 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
363 `4: sync remote: files: "/" (group): unchanged`,
364 `4: sync remote: files: "/etc" (group): unchanged`,
365 `4: sync remote: files: "/tmp" (group): unchanged`,
366 `4: sync remote: files: "/var/tmp" (group): unchanged`,
372 "triggers: no change",
374 Files: map[string]*safcm.File{
377 Mode: fs.ModeDir | 0700,
381 TriggerCommands: []string{
387 Mode: fs.ModeDir | 0755,
391 TriggerCommands: []string{
400 Data: []byte("content\n"),
402 TriggerCommands: []string{
403 "echo trigger dir/file",
409 createDirectory("dir", 0755)
410 createFile("dir/file", "content\n", 0644)
417 Mode: fs.ModeDir | 0755,
422 Data: []byte("content\n"),
427 `4: sync remote: files: "." (group): unchanged`,
428 `4: sync remote: files: "dir" (group): unchanged`,
429 `4: sync remote: files: "dir/file" (group): unchanged`,
435 "triggers: change root",
437 Files: map[string]*safcm.File{
440 Mode: fs.ModeDir | 0700,
444 TriggerCommands: []string{
450 Mode: fs.ModeDir | 0755,
454 TriggerCommands: []string{
463 Data: []byte("content\n"),
465 TriggerCommands: []string{
466 "echo trigger dir/file",
472 err = os.Chmod(".", 0750)
476 createDirectory("dir", 0755)
477 createFile("dir/file", "content\n", 0644)
486 Mode: fs.ModeDir | 0755,
491 Data: []byte("content\n"),
495 FileChanges: []safcm.FileChange{
498 Old: safcm.FileChangeInfo{
499 Mode: fs.ModeDir | 0750,
505 New: safcm.FileChangeInfo{
506 Mode: fs.ModeDir | 0700,
516 `4: sync remote: files: "." (group): permission differs drwxr-x--- -> drwx------`,
517 `3: sync remote: files: "." (group): updating`,
518 `4: sync remote: files: "." (group): chmodding drwx------`,
519 `3: sync remote: files: ".": queuing trigger on "."`,
520 `4: sync remote: files: "dir" (group): unchanged`,
521 `4: sync remote: files: "dir/file" (group): unchanged`,
527 "triggers: change middle",
529 Files: map[string]*safcm.File{
532 Mode: fs.ModeDir | 0700,
536 TriggerCommands: []string{
542 Mode: fs.ModeDir | 0755,
546 TriggerCommands: []string{
555 Data: []byte("content\n"),
557 TriggerCommands: []string{
558 "echo trigger dir/file",
564 createDirectory("dir", 0750)
565 createFile("dir/file", "content\n", 0644)
575 Mode: fs.ModeDir | 0755,
580 Data: []byte("content\n"),
584 FileChanges: []safcm.FileChange{
587 Old: safcm.FileChangeInfo{
588 Mode: fs.ModeDir | 0750,
594 New: safcm.FileChangeInfo{
595 Mode: fs.ModeDir | 0755,
605 `4: sync remote: files: "." (group): unchanged`,
606 `4: sync remote: files: "dir" (group): permission differs drwxr-x--- -> drwxr-xr-x`,
607 `3: sync remote: files: "dir" (group): updating`,
608 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
609 `3: sync remote: files: "dir": queuing trigger on "."`,
610 `3: sync remote: files: "dir": queuing trigger on "dir"`,
611 `4: sync remote: files: "dir/file" (group): unchanged`,
617 "triggers: change leaf",
619 Files: map[string]*safcm.File{
622 Mode: fs.ModeDir | 0700,
626 TriggerCommands: []string{
632 Mode: fs.ModeDir | 0755,
636 TriggerCommands: []string{
645 Data: []byte("content\n"),
647 TriggerCommands: []string{
648 "echo trigger dir/file",
654 createDirectory("dir", 0755)
665 Mode: fs.ModeDir | 0755,
670 Data: []byte("content\n"),
674 FileChanges: []safcm.FileChange{
678 New: safcm.FileChangeInfo{
689 `4: sync remote: files: "." (group): unchanged`,
690 `4: sync remote: files: "dir" (group): unchanged`,
691 `4: sync remote: files: "dir/file" (group): will create`,
692 `3: sync remote: files: "dir/file" (group): creating`,
693 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
694 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
695 `3: sync remote: files: "dir/file": queuing trigger on "."`,
696 `3: sync remote: files: "dir/file": queuing trigger on "dir"`,
697 `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
703 "triggers: multiple changes",
705 Files: map[string]*safcm.File{
708 Mode: fs.ModeDir | 0700,
712 TriggerCommands: []string{
718 Mode: fs.ModeDir | 0755,
722 TriggerCommands: []string{
731 Data: []byte("content\n"),
733 TriggerCommands: []string{
734 "echo trigger dir/file",
749 Mode: fs.ModeDir | 0755,
754 Data: []byte("content\n"),
758 FileChanges: []safcm.FileChange{
762 New: safcm.FileChangeInfo{
763 Mode: fs.ModeDir | 0755,
773 New: safcm.FileChangeInfo{
784 `4: sync remote: files: "." (group): unchanged`,
785 `4: sync remote: files: "dir" (group): will create`,
786 `3: sync remote: files: "dir" (group): creating`,
787 `4: sync remote: files: "dir" (group): creating directory`,
788 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
789 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
790 `3: sync remote: files: "dir": queuing trigger on "."`,
791 `3: sync remote: files: "dir": queuing trigger on "dir"`,
792 `4: sync remote: files: "dir/file" (group): will create`,
793 `3: sync remote: files: "dir/file" (group): creating`,
794 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
795 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
796 `4: sync remote: files: "dir/file": skipping trigger on ".", already active`,
797 `4: sync remote: files: "dir/file": skipping trigger on "dir", already active`,
798 `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
804 "triggers: absolute paths",
806 Files: map[string]*safcm.File{
809 Mode: fs.ModeDir | 0755,
815 TriggerCommands: []string{
821 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
827 TriggerCommands: []string{
832 Path: tmpTestFilePath,
837 TriggerCommands: []string{
838 "echo trigger /tmp/file",
844 // This is slightly racy but the file name
845 // should be rare enough that this isn't an
847 _, err := os.Stat(tmpTestFilePath)
849 t.Fatalf("%q exists, aborting",
856 // Don't use variable for more robust test
857 "/tmp/safcm-sync-files-test-file",
863 FileChanges: []safcm.FileChange{
865 Path: "/tmp/safcm-sync-files-test-file",
867 New: safcm.FileChangeInfo{
878 `4: sync remote: files: "/" (group): unchanged`,
879 `4: sync remote: files: "/tmp" (group): unchanged`,
880 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): will create`,
881 `3: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating`,
882 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating temporary file "/tmp/.safcm-sync-files-test-file*"`,
883 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): renaming "/tmp/.safcm-sync-files-test-fileRND"`,
884 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/"`,
885 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp"`,
886 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp/safcm-sync-files-test-file"`,
892 for _, tc := range tests {
893 // Create separate test directory for each test case
894 path := filepath.Join(cwd, "testdata", "files-"+tc.name)
895 err = os.Mkdir(path, 0700)
904 if tc.prepare != nil {
908 s, res := prepareSync(tc.req, &testRunner{
915 // Ugly but the simplest way to compare errors (including nil)
916 if fmt.Sprintf("%s", err) != fmt.Sprintf("%s", tc.expErr) {
917 t.Errorf("%s: err = %#v, want %#v",
918 tc.name, err, tc.expErr)
921 // Remove random file names from result
922 for i, x := range dbg {
923 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
925 if !reflect.DeepEqual(tc.expDbg, dbg) {
926 t.Errorf("%s: dbg: %s", tc.name,
927 cmp.Diff(tc.expDbg, dbg))
930 files, err := walkDir(path)
934 if !reflect.DeepEqual(tc.expFiles, files) {
935 t.Errorf("%s: files: %s", tc.name,
936 cmp.Diff(tc.expFiles, files))
939 if !reflect.DeepEqual(tc.expResp, s.resp) {
940 t.Errorf("%s: resp: %s", tc.name,
941 cmp.Diff(tc.expResp, s.resp))
943 if !reflect.DeepEqual(tc.triggers, s.triggers) {
944 t.Errorf("%s: triggers: %s", tc.name,
945 cmp.Diff(tc.triggers, s.triggers))
949 os.Remove(tmpTestFilePath)
951 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
958 func TestSyncFile(t *testing.T) {
959 cwd, err := os.Getwd()
965 err = os.RemoveAll("testdata")
969 err = os.Mkdir("testdata", 0700)
976 Mode: fs.ModeDir | 0700,
978 user, uid, group, gid := currentUserAndGroup()
987 expResp safcm.MsgSyncResp
992 // NOTE: Also update MsgSyncResp in safcm test cases when
993 // changing anything here!
995 // TODO: Add tests for chown and run them only as root
1007 Data: []byte("content\n"),
1017 Data: []byte("content\n"),
1021 FileChanges: []safcm.FileChange{
1025 New: safcm.FileChangeInfo{
1036 `4: sync remote: files: "file" (group): will create`,
1037 `3: sync remote: files: "file" (group): creating`,
1038 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1039 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1044 "file: create (dry-run)",
1053 Data: []byte("content\n"),
1060 FileChanges: []safcm.FileChange{
1064 New: safcm.FileChangeInfo{
1075 `4: sync remote: files: "file" (group): will create`,
1076 `3: sync remote: files: "file" (group): creating`,
1077 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
1090 Data: []byte("content\n"),
1094 createFile("file", "content\n", 0644)
1102 Data: []byte("content\n"),
1105 safcm.MsgSyncResp{},
1107 `4: sync remote: files: "file" (group): unchanged`,
1113 "file: unchanged (non-default user-group)",
1122 Data: []byte("content\n"),
1126 createFile("file", "content\n", 0644)
1134 Data: []byte("content\n"),
1137 safcm.MsgSyncResp{},
1139 `4: sync remote: files: "file" (group): unchanged`,
1149 Mode: 0755 | fs.ModeSetuid,
1152 Data: []byte("content\n"),
1156 createFile("file", "content\n", 0755)
1163 Mode: 0755 | fs.ModeSetuid,
1164 Data: []byte("content\n"),
1168 FileChanges: []safcm.FileChange{
1171 Old: safcm.FileChangeInfo{
1178 New: safcm.FileChangeInfo{
1179 Mode: 0755 | fs.ModeSetuid,
1189 `4: sync remote: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
1190 `3: sync remote: files: "file" (group): updating`,
1191 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1192 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1205 Data: []byte("content\n"),
1209 createFile("file", "old content\n", 0644)
1217 Data: []byte("content\n"),
1221 FileChanges: []safcm.FileChange{
1224 Old: safcm.FileChangeInfo{
1231 New: safcm.FileChangeInfo{
1238 DataDiff: `@@ -1,2 +1,2 @@
1247 `4: sync remote: files: "file" (group): content differs`,
1248 `3: sync remote: files: "file" (group): updating`,
1249 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1250 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1262 Mode: fs.ModeSymlink | 0777,
1265 Data: []byte("target"),
1274 Mode: fs.ModeSymlink | 0777,
1275 Data: []byte("target"),
1279 FileChanges: []safcm.FileChange{
1283 New: safcm.FileChangeInfo{
1284 Mode: fs.ModeSymlink | 0777,
1294 `4: sync remote: files: "link" (group): will create`,
1295 `3: sync remote: files: "link" (group): creating`,
1296 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1297 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1302 "symlink: create (conflict)",
1306 Mode: fs.ModeSymlink | 0777,
1309 Data: []byte("target"),
1313 createFile(".link8717895732742165505", "", 0600)
1319 Path: ".link8717895732742165505",
1325 Mode: fs.ModeSymlink | 0777,
1326 Data: []byte("target"),
1330 FileChanges: []safcm.FileChange{
1334 New: safcm.FileChangeInfo{
1335 Mode: fs.ModeSymlink | 0777,
1345 `4: sync remote: files: "link" (group): will create`,
1346 `3: sync remote: files: "link" (group): creating`,
1347 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1348 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1349 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1354 "symlink: create (dry-run)",
1360 Mode: fs.ModeSymlink | 0777,
1363 Data: []byte("target"),
1370 FileChanges: []safcm.FileChange{
1374 New: safcm.FileChangeInfo{
1375 Mode: fs.ModeSymlink | 0777,
1385 `4: sync remote: files: "link" (group): will create`,
1386 `3: sync remote: files: "link" (group): creating`,
1387 `4: sync remote: files: "link" (group): dry-run, skipping changes`,
1393 "symlink: unchanged",
1397 Mode: fs.ModeSymlink | 0777,
1400 Data: []byte("target"),
1404 createSymlink("link", "target")
1411 Mode: fs.ModeSymlink | 0777,
1412 Data: []byte("target"),
1415 safcm.MsgSyncResp{},
1417 `4: sync remote: files: "link" (group): unchanged`,
1427 Mode: fs.ModeSymlink | 0777,
1430 Data: []byte("target"),
1434 createSymlink("link", "old-target")
1441 Mode: fs.ModeSymlink | 0777,
1442 Data: []byte("target"),
1446 FileChanges: []safcm.FileChange{
1449 Old: safcm.FileChangeInfo{
1450 Mode: fs.ModeSymlink | 0777,
1456 New: safcm.FileChangeInfo{
1457 Mode: fs.ModeSymlink | 0777,
1463 DataDiff: `@@ -1 +1 @@
1471 `4: sync remote: files: "link" (group): content differs`,
1472 `3: sync remote: files: "link" (group): updating`,
1473 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1474 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1482 "directory: create",
1486 Mode: fs.ModeDir | 0705,
1497 Mode: fs.ModeDir | 0705,
1501 FileChanges: []safcm.FileChange{
1505 New: safcm.FileChangeInfo{
1506 Mode: fs.ModeDir | 0705,
1516 `4: sync remote: files: "dir" (group): will create`,
1517 `3: sync remote: files: "dir" (group): creating`,
1518 `4: sync remote: files: "dir" (group): creating directory`,
1519 `4: sync remote: files: "dir" (group): chmodding drwx---r-x`,
1520 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
1525 "directory: create (dry-run)",
1531 Mode: fs.ModeDir | 0644,
1540 FileChanges: []safcm.FileChange{
1544 New: safcm.FileChangeInfo{
1545 Mode: fs.ModeDir | 0644,
1555 `4: sync remote: files: "dir" (group): will create`,
1556 `3: sync remote: files: "dir" (group): creating`,
1557 `4: sync remote: files: "dir" (group): dry-run, skipping changes`,
1563 "directory: unchanged",
1567 Mode: fs.ModeDir | 0755,
1573 createDirectory("dir", 0755)
1580 Mode: fs.ModeDir | 0755,
1583 safcm.MsgSyncResp{},
1585 `4: sync remote: files: "dir" (group): unchanged`,
1591 "directory: permission",
1595 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1601 createDirectory("dir", 0500|fs.ModeSticky)
1608 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1612 FileChanges: []safcm.FileChange{
1615 Old: safcm.FileChangeInfo{
1616 Mode: fs.ModeDir | 0500 | fs.ModeSticky,
1622 New: safcm.FileChangeInfo{
1623 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1633 `4: sync remote: files: "dir" (group): permission differs dtr-x------ -> dgrwxr-xr-x`,
1634 `3: sync remote: files: "dir" (group): updating`,
1635 `4: sync remote: files: "dir" (group): chmodding dgrwxr-xr-x`,
1643 "change: file to directory",
1647 Mode: fs.ModeDir | 0751,
1653 createFile("path", "content\n", 0644)
1660 Mode: fs.ModeDir | 0751,
1664 FileChanges: []safcm.FileChange{
1667 Old: safcm.FileChangeInfo{
1674 New: safcm.FileChangeInfo{
1675 Mode: fs.ModeDir | 0751,
1681 DataDiff: `@@ -1,2 +1 @@
1689 `4: sync remote: files: "path" (group): type differs ---------- -> d---------`,
1690 `3: sync remote: files: "path" (group): updating`,
1691 `4: sync remote: files: "path" (group): removing (due to type change)`,
1692 `4: sync remote: files: "path" (group): creating directory`,
1693 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1694 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1700 "change: file to symlink",
1704 Mode: fs.ModeSymlink | 0777,
1708 Data: []byte("target"),
1711 createFile("path", "content\n", 0644)
1718 Mode: fs.ModeSymlink | 0777,
1719 Data: []byte("target"),
1723 FileChanges: []safcm.FileChange{
1726 Old: safcm.FileChangeInfo{
1733 New: safcm.FileChangeInfo{
1734 Mode: fs.ModeSymlink | 0777,
1740 DataDiff: `@@ -1,2 +1 @@
1749 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
1750 `3: sync remote: files: "path" (group): updating`,
1751 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1752 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1758 "change: symlink to file",
1766 Data: []byte("content\n"),
1769 createSymlink("path", "target")
1777 Data: []byte("content\n"),
1781 FileChanges: []safcm.FileChange{
1784 Old: safcm.FileChangeInfo{
1785 Mode: fs.ModeSymlink | 0777,
1791 New: safcm.FileChangeInfo{
1798 DataDiff: `@@ -1 +1,2 @@
1807 `4: sync remote: files: "path" (group): type differs L--------- -> ----------`,
1808 `3: sync remote: files: "path" (group): updating`,
1809 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1810 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1816 "change: symlink to directory",
1820 Mode: fs.ModeDir | 0751,
1826 createSymlink("path", "target")
1833 Mode: fs.ModeDir | 0751,
1837 FileChanges: []safcm.FileChange{
1840 Old: safcm.FileChangeInfo{
1841 Mode: fs.ModeSymlink | 0777,
1847 New: safcm.FileChangeInfo{
1848 Mode: fs.ModeDir | 0751,
1854 DataDiff: `@@ -1 +1 @@
1862 `4: sync remote: files: "path" (group): type differs L--------- -> d---------`,
1863 `3: sync remote: files: "path" (group): updating`,
1864 `4: sync remote: files: "path" (group): removing (due to type change)`,
1865 `4: sync remote: files: "path" (group): creating directory`,
1866 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1867 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1873 "change: directory to file",
1881 Data: []byte("content\n"),
1884 createDirectory("path", 0777)
1892 Data: []byte("content\n"),
1896 FileChanges: []safcm.FileChange{
1899 Old: safcm.FileChangeInfo{
1900 Mode: fs.ModeDir | 0777,
1906 New: safcm.FileChangeInfo{
1917 `4: sync remote: files: "path" (group): type differs d--------- -> ----------`,
1918 `3: sync remote: files: "path" (group): updating`,
1919 `4: sync remote: files: "path" (group): removing (due to type change)`,
1920 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1921 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1927 "change: directory to symlink",
1931 Mode: fs.ModeSymlink | 0777,
1935 Data: []byte("target"),
1938 createDirectory("path", 0777)
1945 Mode: fs.ModeSymlink | 0777,
1946 Data: []byte("target"),
1950 FileChanges: []safcm.FileChange{
1953 Old: safcm.FileChangeInfo{
1954 Mode: fs.ModeDir | 0777,
1960 New: safcm.FileChangeInfo{
1961 Mode: fs.ModeSymlink | 0777,
1971 `4: sync remote: files: "path" (group): type differs d--------- -> L---------`,
1972 `3: sync remote: files: "path" (group): updating`,
1973 `4: sync remote: files: "path" (group): removing (due to type change)`,
1974 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1975 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1981 "change: other to file",
1989 Data: []byte("content\n"),
1992 createFifo("path", 0666)
2000 Data: []byte("content\n"),
2004 FileChanges: []safcm.FileChange{
2007 Old: safcm.FileChangeInfo{
2008 Mode: fs.ModeNamedPipe | 0666,
2014 New: safcm.FileChangeInfo{
2025 `4: sync remote: files: "path" (group): type differs p--------- -> ----------`,
2026 `3: sync remote: files: "path" (group): updating`,
2027 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
2028 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
2034 "change: other to symlink",
2038 Mode: fs.ModeSymlink | 0777,
2042 Data: []byte("target"),
2045 createFifo("path", 0666)
2052 Mode: fs.ModeSymlink | 0777,
2053 Data: []byte("target"),
2057 FileChanges: []safcm.FileChange{
2060 Old: safcm.FileChangeInfo{
2061 Mode: fs.ModeNamedPipe | 0666,
2067 New: safcm.FileChangeInfo{
2068 Mode: fs.ModeSymlink | 0777,
2078 `4: sync remote: files: "path" (group): type differs p--------- -> L---------`,
2079 `3: sync remote: files: "path" (group): updating`,
2080 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2081 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2087 "change: other to directory",
2091 Mode: fs.ModeDir | 0751,
2097 createFifo("path", 0666)
2104 Mode: fs.ModeDir | 0751,
2108 FileChanges: []safcm.FileChange{
2111 Old: safcm.FileChangeInfo{
2112 Mode: fs.ModeNamedPipe | 0666,
2118 New: safcm.FileChangeInfo{
2119 Mode: fs.ModeDir | 0751,
2129 `4: sync remote: files: "path" (group): type differs p--------- -> d---------`,
2130 `3: sync remote: files: "path" (group): updating`,
2131 `4: sync remote: files: "path" (group): removing (due to type change)`,
2132 `4: sync remote: files: "path" (group): creating directory`,
2133 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
2134 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
2140 "change: file to symlink (same content)",
2144 Mode: fs.ModeSymlink | 0777,
2148 Data: []byte("target"),
2151 createFile("path", "target", 0644)
2158 Mode: fs.ModeSymlink | 0777,
2159 Data: []byte("target"),
2163 FileChanges: []safcm.FileChange{
2166 Old: safcm.FileChangeInfo{
2173 New: safcm.FileChangeInfo{
2174 Mode: fs.ModeSymlink | 0777,
2184 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
2185 `3: sync remote: files: "path" (group): updating`,
2186 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2187 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2214 createFile("file", `this
2234 FileChanges: []safcm.FileChange{
2237 Old: safcm.FileChangeInfo{
2244 New: safcm.FileChangeInfo{
2251 DataDiff: `@@ -1,5 +1,7 @@
2265 `4: sync remote: files: "file" (group): content differs`,
2266 `3: sync remote: files: "file" (group): updating`,
2267 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2273 "diff: binary both",
2282 Data: []byte("\x00\x01\x02\x03"),
2286 createFile("file", "\x00\x01\x02", 0644)
2294 Data: []byte("\x00\x01\x02"),
2298 FileChanges: []safcm.FileChange{
2301 Old: safcm.FileChangeInfo{
2308 New: safcm.FileChangeInfo{
2315 DataDiff: "Binary files differ, cannot show diff",
2320 `4: sync remote: files: "file" (group): content differs`,
2321 `3: sync remote: files: "file" (group): updating`,
2322 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2337 Data: []byte("content\n"),
2341 createFile("file", "\x00\x01\x02", 0644)
2349 Data: []byte("\x00\x01\x02"),
2353 FileChanges: []safcm.FileChange{
2356 Old: safcm.FileChangeInfo{
2363 New: safcm.FileChangeInfo{
2370 DataDiff: `@@ -1,2 +1,2 @@
2379 `4: sync remote: files: "file" (group): content differs`,
2380 `3: sync remote: files: "file" (group): updating`,
2381 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2396 Data: []byte("\x00\x01\x02\x03"),
2400 createFile("file", "content\n", 0644)
2408 Data: []byte("content\n"),
2412 FileChanges: []safcm.FileChange{
2415 Old: safcm.FileChangeInfo{
2422 New: safcm.FileChangeInfo{
2429 DataDiff: `@@ -1,2 +1,2 @@
2438 `4: sync remote: files: "file" (group): content differs`,
2439 `3: sync remote: files: "file" (group): updating`,
2440 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2446 for _, tc := range tests {
2447 // Create separate test directory for each test case
2448 path := filepath.Join(cwd, "testdata", "file-"+tc.name)
2449 err = os.Mkdir(path, 0700)
2453 err = os.Chdir(path)
2458 if tc.prepare != nil {
2462 s, res := prepareSync(tc.req, &testRunner{
2468 // Deterministic temporary symlink names
2472 err := s.syncFile(tc.file, &changed)
2473 // Ugly but the simplest way to compare errors (including nil)
2474 if fmt.Sprintf("%s", err) != fmt.Sprintf("%s", tc.expErr) {
2475 t.Errorf("%s: err = %#v, want %#v",
2476 tc.name, err, tc.expErr)
2479 // Remove random file names from result
2480 for i, x := range dbg {
2481 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
2483 if !reflect.DeepEqual(tc.expDbg, dbg) {
2484 t.Errorf("%s: dbg: %s", tc.name,
2485 cmp.Diff(tc.expDbg, dbg))
2488 files, err := walkDir(path)
2492 if !reflect.DeepEqual(tc.expFiles, files) {
2493 t.Errorf("%s: files: %s", tc.name,
2494 cmp.Diff(tc.expFiles, files))
2497 if tc.expChanged != changed {
2498 t.Errorf("%s: changed = %#v, want %#v",
2499 tc.name, changed, tc.expChanged)
2501 if !reflect.DeepEqual(tc.expResp, s.resp) {
2502 t.Errorf("%s: resp: %s", tc.name,
2503 cmp.Diff(tc.expResp, s.resp))
2508 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
2517 func createFile(path string, data string, mode fs.FileMode) {
2518 err := os.WriteFile(path, []byte(data), 0644)
2522 err = os.Chmod(path, mode)
2527 func createSymlink(path string, data string) {
2528 err := os.Symlink(data, path)
2533 func createDirectory(path string, mode fs.FileMode) {
2534 err := os.Mkdir(path, 0700)
2538 err = os.Chmod(path, mode)
2543 func createFifo(path string, mode fs.FileMode) {
2544 err := syscall.Mkfifo(path, 0600)
2548 err = os.Chmod(path, mode)
2554 func currentUserAndGroup() (string, int, string, int) {
2555 u, err := user.Current()
2559 g, err := user.LookupGroupId(u.Gid)
2563 uid, err := strconv.Atoi(u.Uid)
2567 gid, err := strconv.Atoi(g.Gid)
2571 return u.Username, uid, g.Name, gid