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/>.
27 "ruderich.org/simon/safcm"
28 ft "ruderich.org/simon/safcm/cmd/safcm-remote/sync/filetest"
29 "ruderich.org/simon/safcm/testutil"
32 var randFilesRegexp = regexp.MustCompile(`\d+"$`)
34 func TestSyncFiles(t *testing.T) {
35 cwd, err := os.Getwd()
41 err = os.RemoveAll("testdata")
45 err = os.Mkdir("testdata", 0700)
52 Mode: fs.ModeDir | 0700,
54 user, uid, group, gid := ft.CurrentUserAndGroup()
56 tmpTestFilePath := "/tmp/safcm-sync-files-test-file"
64 expResp safcm.MsgSyncResp
69 // NOTE: Also update MsgSyncResp in safcm test cases when
70 // changing anything here!
72 // See TestSyncFile() for most file related tests. This
73 // function only tests the overall results and triggers.
78 Files: map[string]*safcm.File{
81 Mode: fs.ModeDir | 0700,
88 Mode: fs.ModeDir | 0755,
98 Data: []byte("content\n"),
109 Mode: fs.ModeDir | 0755,
114 Data: []byte("content\n"),
118 FileChanges: []safcm.FileChange{
122 New: safcm.FileChangeInfo{
123 Mode: fs.ModeDir | 0755,
133 New: safcm.FileChangeInfo{
144 `4: sync remote: files: "." (group): unchanged`,
145 `4: sync remote: files: "dir" (group): will create`,
146 `3: sync remote: files: "dir" (group): creating`,
147 `4: sync remote: files: "dir" (group): creating directory`,
148 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
149 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
150 `4: sync remote: files: "dir/file" (group): will create`,
151 `3: sync remote: files: "dir/file" (group): creating`,
152 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
153 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
161 Files: map[string]*safcm.File{
164 Mode: fs.ModeDir | 0700,
171 Mode: fs.ModeDir | 0755,
181 Data: []byte("content\n"),
187 ft.CreateDirectory("dir", 0755)
188 ft.CreateFile("dir/file", "content\n", 0644)
195 Mode: fs.ModeDir | 0755,
200 Data: []byte("content\n"),
205 `4: sync remote: files: "." (group): unchanged`,
206 `4: sync remote: files: "dir" (group): unchanged`,
207 `4: sync remote: files: "dir/file" (group): unchanged`,
213 "invalid File: user",
215 Files: map[string]*safcm.File{
218 Mode: fs.ModeDir | 0700,
233 fmt.Errorf("\".\": cannot set both User (\"user\") and Uid (1)"),
236 "invalid File: group",
238 Files: map[string]*safcm.File{
241 Mode: fs.ModeDir | 0700,
256 fmt.Errorf("\".\": cannot set both Group (\"group\") and Gid (1)"),
260 // We use relative paths for most tests because we
261 // don't want to modify the running system. Use this
262 // test (and the one below for triggers) as a basic
263 // check that absolute paths work.
265 // Use numeric IDs as not all systems use root/root;
266 // for example BSDs use root/wheel.
267 "absolute paths: no change",
269 Files: map[string]*safcm.File{
272 Mode: fs.ModeDir | 0755,
279 Mode: fs.ModeDir | 0755,
286 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
293 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
307 `4: sync remote: files: "/" (group): unchanged`,
308 `4: sync remote: files: "/etc" (group): unchanged`,
309 `4: sync remote: files: "/tmp" (group): unchanged`,
310 `4: sync remote: files: "/var/tmp" (group): unchanged`,
316 "triggers: no change",
318 Files: map[string]*safcm.File{
321 Mode: fs.ModeDir | 0700,
325 TriggerCommands: []string{
331 Mode: fs.ModeDir | 0755,
335 TriggerCommands: []string{
344 Data: []byte("content\n"),
346 TriggerCommands: []string{
347 "echo trigger dir/file",
353 ft.CreateDirectory("dir", 0755)
354 ft.CreateFile("dir/file", "content\n", 0644)
361 Mode: fs.ModeDir | 0755,
366 Data: []byte("content\n"),
371 `4: sync remote: files: "." (group): unchanged`,
372 `4: sync remote: files: "dir" (group): unchanged`,
373 `4: sync remote: files: "dir/file" (group): unchanged`,
379 "triggers: change root",
381 Files: map[string]*safcm.File{
384 Mode: fs.ModeDir | 0700,
388 TriggerCommands: []string{
394 Mode: fs.ModeDir | 0755,
398 TriggerCommands: []string{
407 Data: []byte("content\n"),
409 TriggerCommands: []string{
410 "echo trigger dir/file",
416 err = os.Chmod(".", 0750)
420 ft.CreateDirectory("dir", 0755)
421 ft.CreateFile("dir/file", "content\n", 0644)
430 Mode: fs.ModeDir | 0755,
435 Data: []byte("content\n"),
439 FileChanges: []safcm.FileChange{
442 Old: safcm.FileChangeInfo{
443 Mode: fs.ModeDir | 0750,
449 New: safcm.FileChangeInfo{
450 Mode: fs.ModeDir | 0700,
460 `4: sync remote: files: "." (group): permission differs drwxr-x--- -> drwx------`,
461 `3: sync remote: files: "." (group): updating`,
462 `4: sync remote: files: "." (group): chmodding drwx------`,
463 `3: sync remote: files: ".": queuing trigger on "."`,
464 `4: sync remote: files: "dir" (group): unchanged`,
465 `4: sync remote: files: "dir/file" (group): unchanged`,
471 "triggers: change middle",
473 Files: map[string]*safcm.File{
476 Mode: fs.ModeDir | 0700,
480 TriggerCommands: []string{
486 Mode: fs.ModeDir | 0755,
490 TriggerCommands: []string{
499 Data: []byte("content\n"),
501 TriggerCommands: []string{
502 "echo trigger dir/file",
508 ft.CreateDirectory("dir", 0750)
509 ft.CreateFile("dir/file", "content\n", 0644)
519 Mode: fs.ModeDir | 0755,
524 Data: []byte("content\n"),
528 FileChanges: []safcm.FileChange{
531 Old: safcm.FileChangeInfo{
532 Mode: fs.ModeDir | 0750,
538 New: safcm.FileChangeInfo{
539 Mode: fs.ModeDir | 0755,
549 `4: sync remote: files: "." (group): unchanged`,
550 `4: sync remote: files: "dir" (group): permission differs drwxr-x--- -> drwxr-xr-x`,
551 `3: sync remote: files: "dir" (group): updating`,
552 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
553 `3: sync remote: files: "dir": queuing trigger on "."`,
554 `3: sync remote: files: "dir": queuing trigger on "dir"`,
555 `4: sync remote: files: "dir/file" (group): unchanged`,
561 "triggers: change leaf",
563 Files: map[string]*safcm.File{
566 Mode: fs.ModeDir | 0700,
570 TriggerCommands: []string{
576 Mode: fs.ModeDir | 0755,
580 TriggerCommands: []string{
589 Data: []byte("content\n"),
591 TriggerCommands: []string{
592 "echo trigger dir/file",
598 ft.CreateDirectory("dir", 0755)
609 Mode: fs.ModeDir | 0755,
614 Data: []byte("content\n"),
618 FileChanges: []safcm.FileChange{
622 New: safcm.FileChangeInfo{
633 `4: sync remote: files: "." (group): unchanged`,
634 `4: sync remote: files: "dir" (group): unchanged`,
635 `4: sync remote: files: "dir/file" (group): will create`,
636 `3: sync remote: files: "dir/file" (group): creating`,
637 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
638 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
639 `3: sync remote: files: "dir/file": queuing trigger on "."`,
640 `3: sync remote: files: "dir/file": queuing trigger on "dir"`,
641 `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
647 "triggers: multiple changes",
649 Files: map[string]*safcm.File{
652 Mode: fs.ModeDir | 0700,
656 TriggerCommands: []string{
662 Mode: fs.ModeDir | 0755,
666 TriggerCommands: []string{
675 Data: []byte("content\n"),
677 TriggerCommands: []string{
678 "echo trigger dir/file",
693 Mode: fs.ModeDir | 0755,
698 Data: []byte("content\n"),
702 FileChanges: []safcm.FileChange{
706 New: safcm.FileChangeInfo{
707 Mode: fs.ModeDir | 0755,
717 New: safcm.FileChangeInfo{
728 `4: sync remote: files: "." (group): unchanged`,
729 `4: sync remote: files: "dir" (group): will create`,
730 `3: sync remote: files: "dir" (group): creating`,
731 `4: sync remote: files: "dir" (group): creating directory`,
732 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
733 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
734 `3: sync remote: files: "dir": queuing trigger on "."`,
735 `3: sync remote: files: "dir": queuing trigger on "dir"`,
736 `4: sync remote: files: "dir/file" (group): will create`,
737 `3: sync remote: files: "dir/file" (group): creating`,
738 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
739 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
740 `4: sync remote: files: "dir/file": skipping trigger on ".", already active`,
741 `4: sync remote: files: "dir/file": skipping trigger on "dir", already active`,
742 `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
748 "triggers: absolute paths",
750 Files: map[string]*safcm.File{
753 Mode: fs.ModeDir | 0755,
757 TriggerCommands: []string{
763 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
767 TriggerCommands: []string{
772 Path: tmpTestFilePath,
777 TriggerCommands: []string{
778 "echo trigger /tmp/file",
784 // This is slightly racy but the file name
785 // should be rare enough that this isn't an
787 _, err := os.Stat(tmpTestFilePath)
789 t.Fatalf("%q exists, aborting",
796 // Don't use variable for more robust test
797 "/tmp/safcm-sync-files-test-file",
803 FileChanges: []safcm.FileChange{
805 Path: "/tmp/safcm-sync-files-test-file",
807 New: safcm.FileChangeInfo{
818 `4: sync remote: files: "/" (group): unchanged`,
819 `4: sync remote: files: "/tmp" (group): unchanged`,
820 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): will create`,
821 `3: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating`,
822 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating temporary file "/tmp/.safcm-sync-files-test-file*"`,
823 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): renaming "/tmp/.safcm-sync-files-test-fileRND"`,
824 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/"`,
825 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp"`,
826 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp/safcm-sync-files-test-file"`,
832 for _, tc := range tests {
833 t.Run(tc.name, func(t *testing.T) {
834 // Create separate test directory for each test case
835 path := filepath.Join(cwd, "testdata", "files-"+tc.name)
836 err = os.Mkdir(path, 0700)
845 if tc.prepare != nil {
849 s, res := prepareSync(tc.req, &testRunner{
855 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
857 // Remove random file names from result
858 for i, x := range dbg {
859 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
861 testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
863 files, err := ft.WalkDir(path)
867 testutil.AssertEqual(t, "files", files, tc.expFiles)
869 testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
870 testutil.AssertEqual(t, "triggers",
871 s.triggers, tc.triggers)
875 os.Remove(tmpTestFilePath)
877 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
884 func TestSyncFile(t *testing.T) {
885 cwd, err := os.Getwd()
891 err = os.RemoveAll("testdata")
895 err = os.Mkdir("testdata", 0700)
902 Mode: fs.ModeDir | 0700,
904 user, uid, group, gid := ft.CurrentUserAndGroup()
913 expResp safcm.MsgSyncResp
918 // NOTE: Also update MsgSyncResp in safcm test cases when
919 // changing anything here!
921 // TODO: Add tests for chown and run them only as root
933 Data: []byte("content\n"),
943 Data: []byte("content\n"),
947 FileChanges: []safcm.FileChange{
951 New: safcm.FileChangeInfo{
962 `4: sync remote: files: "file" (group): will create`,
963 `3: sync remote: files: "file" (group): creating`,
964 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
965 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
970 "file: create (dry-run)",
979 Data: []byte("content\n"),
986 FileChanges: []safcm.FileChange{
990 New: safcm.FileChangeInfo{
1001 `4: sync remote: files: "file" (group): will create`,
1002 `3: sync remote: files: "file" (group): creating`,
1003 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
1016 Data: []byte("content\n"),
1020 ft.CreateFile("file", "content\n", 0644)
1028 Data: []byte("content\n"),
1031 safcm.MsgSyncResp{},
1033 `4: sync remote: files: "file" (group): unchanged`,
1039 "file: unchanged (non-default user-group)",
1048 Data: []byte("content\n"),
1052 ft.CreateFile("file", "content\n", 0644)
1060 Data: []byte("content\n"),
1063 safcm.MsgSyncResp{},
1065 `4: sync remote: files: "file" (group): unchanged`,
1075 Mode: 0755 | fs.ModeSetuid,
1078 Data: []byte("content\n"),
1082 ft.CreateFile("file", "content\n", 0755)
1089 Mode: 0755 | fs.ModeSetuid,
1090 Data: []byte("content\n"),
1094 FileChanges: []safcm.FileChange{
1097 Old: safcm.FileChangeInfo{
1104 New: safcm.FileChangeInfo{
1105 Mode: 0755 | fs.ModeSetuid,
1115 `4: sync remote: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
1116 `3: sync remote: files: "file" (group): updating`,
1117 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1118 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1131 Data: []byte("content\n"),
1135 ft.CreateFile("file", "old content\n", 0644)
1143 Data: []byte("content\n"),
1147 FileChanges: []safcm.FileChange{
1150 Old: safcm.FileChangeInfo{
1157 New: safcm.FileChangeInfo{
1164 DataDiff: `@@ -1,2 +1,2 @@
1173 `4: sync remote: files: "file" (group): content differs`,
1174 `3: sync remote: files: "file" (group): updating`,
1175 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1176 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1188 Mode: fs.ModeSymlink | 0777,
1191 Data: []byte("target"),
1200 Mode: fs.ModeSymlink | 0777,
1201 Data: []byte("target"),
1205 FileChanges: []safcm.FileChange{
1209 New: safcm.FileChangeInfo{
1210 Mode: fs.ModeSymlink | 0777,
1220 `4: sync remote: files: "link" (group): will create`,
1221 `3: sync remote: files: "link" (group): creating`,
1222 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1223 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1228 "symlink: create (conflict)",
1232 Mode: fs.ModeSymlink | 0777,
1235 Data: []byte("target"),
1239 ft.CreateFile(".link8717895732742165505", "", 0600)
1245 Path: ".link8717895732742165505",
1251 Mode: fs.ModeSymlink | 0777,
1252 Data: []byte("target"),
1256 FileChanges: []safcm.FileChange{
1260 New: safcm.FileChangeInfo{
1261 Mode: fs.ModeSymlink | 0777,
1271 `4: sync remote: files: "link" (group): will create`,
1272 `3: sync remote: files: "link" (group): creating`,
1273 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1274 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1275 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1280 "symlink: create (dry-run)",
1286 Mode: fs.ModeSymlink | 0777,
1289 Data: []byte("target"),
1296 FileChanges: []safcm.FileChange{
1300 New: safcm.FileChangeInfo{
1301 Mode: fs.ModeSymlink | 0777,
1311 `4: sync remote: files: "link" (group): will create`,
1312 `3: sync remote: files: "link" (group): creating`,
1313 `4: sync remote: files: "link" (group): dry-run, skipping changes`,
1319 "symlink: unchanged",
1323 Mode: fs.ModeSymlink | 0777,
1326 Data: []byte("target"),
1330 ft.CreateSymlink("link", "target")
1337 Mode: fs.ModeSymlink | 0777,
1338 Data: []byte("target"),
1341 safcm.MsgSyncResp{},
1343 `4: sync remote: files: "link" (group): unchanged`,
1353 Mode: fs.ModeSymlink | 0777,
1356 Data: []byte("target"),
1360 ft.CreateSymlink("link", "old-target")
1367 Mode: fs.ModeSymlink | 0777,
1368 Data: []byte("target"),
1372 FileChanges: []safcm.FileChange{
1375 Old: safcm.FileChangeInfo{
1376 Mode: fs.ModeSymlink | 0777,
1382 New: safcm.FileChangeInfo{
1383 Mode: fs.ModeSymlink | 0777,
1389 DataDiff: `@@ -1 +1 @@
1397 `4: sync remote: files: "link" (group): content differs`,
1398 `3: sync remote: files: "link" (group): updating`,
1399 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1400 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1408 "directory: create",
1412 Mode: fs.ModeDir | 0705,
1423 Mode: fs.ModeDir | 0705,
1427 FileChanges: []safcm.FileChange{
1431 New: safcm.FileChangeInfo{
1432 Mode: fs.ModeDir | 0705,
1442 `4: sync remote: files: "dir" (group): will create`,
1443 `3: sync remote: files: "dir" (group): creating`,
1444 `4: sync remote: files: "dir" (group): creating directory`,
1445 `4: sync remote: files: "dir" (group): chmodding drwx---r-x`,
1446 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
1451 "directory: create (dry-run)",
1457 Mode: fs.ModeDir | 0644,
1466 FileChanges: []safcm.FileChange{
1470 New: safcm.FileChangeInfo{
1471 Mode: fs.ModeDir | 0644,
1481 `4: sync remote: files: "dir" (group): will create`,
1482 `3: sync remote: files: "dir" (group): creating`,
1483 `4: sync remote: files: "dir" (group): dry-run, skipping changes`,
1489 "directory: unchanged",
1493 Mode: fs.ModeDir | 0755,
1499 ft.CreateDirectory("dir", 0755)
1506 Mode: fs.ModeDir | 0755,
1509 safcm.MsgSyncResp{},
1511 `4: sync remote: files: "dir" (group): unchanged`,
1517 "directory: permission",
1521 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1527 ft.CreateDirectory("dir", 0500|fs.ModeSticky)
1534 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1538 FileChanges: []safcm.FileChange{
1541 Old: safcm.FileChangeInfo{
1542 Mode: fs.ModeDir | 0500 | fs.ModeSticky,
1548 New: safcm.FileChangeInfo{
1549 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1559 `4: sync remote: files: "dir" (group): permission differs dtr-x------ -> dgrwxr-xr-x`,
1560 `3: sync remote: files: "dir" (group): updating`,
1561 `4: sync remote: files: "dir" (group): chmodding dgrwxr-xr-x`,
1569 "change: file to directory",
1573 Mode: fs.ModeDir | 0751,
1579 ft.CreateFile("path", "content\n", 0644)
1586 Mode: fs.ModeDir | 0751,
1590 FileChanges: []safcm.FileChange{
1593 Old: safcm.FileChangeInfo{
1600 New: safcm.FileChangeInfo{
1601 Mode: fs.ModeDir | 0751,
1607 DataDiff: `@@ -1,2 +1 @@
1615 `4: sync remote: files: "path" (group): type differs ---------- -> d---------`,
1616 `3: sync remote: files: "path" (group): updating`,
1617 `4: sync remote: files: "path" (group): removing (due to type change)`,
1618 `4: sync remote: files: "path" (group): creating directory`,
1619 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1620 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1626 "change: file to symlink",
1630 Mode: fs.ModeSymlink | 0777,
1634 Data: []byte("target"),
1637 ft.CreateFile("path", "content\n", 0644)
1644 Mode: fs.ModeSymlink | 0777,
1645 Data: []byte("target"),
1649 FileChanges: []safcm.FileChange{
1652 Old: safcm.FileChangeInfo{
1659 New: safcm.FileChangeInfo{
1660 Mode: fs.ModeSymlink | 0777,
1666 DataDiff: `@@ -1,2 +1 @@
1675 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
1676 `3: sync remote: files: "path" (group): updating`,
1677 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1678 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1684 "change: symlink to file",
1692 Data: []byte("content\n"),
1695 ft.CreateSymlink("path", "target")
1703 Data: []byte("content\n"),
1707 FileChanges: []safcm.FileChange{
1710 Old: safcm.FileChangeInfo{
1711 Mode: fs.ModeSymlink | 0777,
1717 New: safcm.FileChangeInfo{
1724 DataDiff: `@@ -1 +1,2 @@
1733 `4: sync remote: files: "path" (group): type differs L--------- -> ----------`,
1734 `3: sync remote: files: "path" (group): updating`,
1735 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1736 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1742 "change: symlink to directory",
1746 Mode: fs.ModeDir | 0751,
1752 ft.CreateSymlink("path", "target")
1759 Mode: fs.ModeDir | 0751,
1763 FileChanges: []safcm.FileChange{
1766 Old: safcm.FileChangeInfo{
1767 Mode: fs.ModeSymlink | 0777,
1773 New: safcm.FileChangeInfo{
1774 Mode: fs.ModeDir | 0751,
1780 DataDiff: `@@ -1 +1 @@
1788 `4: sync remote: files: "path" (group): type differs L--------- -> d---------`,
1789 `3: sync remote: files: "path" (group): updating`,
1790 `4: sync remote: files: "path" (group): removing (due to type change)`,
1791 `4: sync remote: files: "path" (group): creating directory`,
1792 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1793 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1799 "change: directory to file",
1807 Data: []byte("content\n"),
1810 ft.CreateDirectory("path", 0777)
1818 Data: []byte("content\n"),
1822 FileChanges: []safcm.FileChange{
1825 Old: safcm.FileChangeInfo{
1826 Mode: fs.ModeDir | 0777,
1832 New: safcm.FileChangeInfo{
1843 `4: sync remote: files: "path" (group): type differs d--------- -> ----------`,
1844 `3: sync remote: files: "path" (group): updating`,
1845 `4: sync remote: files: "path" (group): removing (due to type change)`,
1846 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1847 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1853 "change: directory to symlink",
1857 Mode: fs.ModeSymlink | 0777,
1861 Data: []byte("target"),
1864 ft.CreateDirectory("path", 0777)
1871 Mode: fs.ModeSymlink | 0777,
1872 Data: []byte("target"),
1876 FileChanges: []safcm.FileChange{
1879 Old: safcm.FileChangeInfo{
1880 Mode: fs.ModeDir | 0777,
1886 New: safcm.FileChangeInfo{
1887 Mode: fs.ModeSymlink | 0777,
1897 `4: sync remote: files: "path" (group): type differs d--------- -> L---------`,
1898 `3: sync remote: files: "path" (group): updating`,
1899 `4: sync remote: files: "path" (group): removing (due to type change)`,
1900 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1901 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1907 "change: other to file",
1915 Data: []byte("content\n"),
1918 ft.CreateFifo("path", 0666)
1926 Data: []byte("content\n"),
1930 FileChanges: []safcm.FileChange{
1933 Old: safcm.FileChangeInfo{
1934 Mode: fs.ModeNamedPipe | 0666,
1940 New: safcm.FileChangeInfo{
1951 `4: sync remote: files: "path" (group): type differs p--------- -> ----------`,
1952 `3: sync remote: files: "path" (group): updating`,
1953 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1954 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1960 "change: other to symlink",
1964 Mode: fs.ModeSymlink | 0777,
1968 Data: []byte("target"),
1971 ft.CreateFifo("path", 0666)
1978 Mode: fs.ModeSymlink | 0777,
1979 Data: []byte("target"),
1983 FileChanges: []safcm.FileChange{
1986 Old: safcm.FileChangeInfo{
1987 Mode: fs.ModeNamedPipe | 0666,
1993 New: safcm.FileChangeInfo{
1994 Mode: fs.ModeSymlink | 0777,
2004 `4: sync remote: files: "path" (group): type differs p--------- -> L---------`,
2005 `3: sync remote: files: "path" (group): updating`,
2006 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2007 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2013 "change: other to directory",
2017 Mode: fs.ModeDir | 0751,
2023 ft.CreateFifo("path", 0666)
2030 Mode: fs.ModeDir | 0751,
2034 FileChanges: []safcm.FileChange{
2037 Old: safcm.FileChangeInfo{
2038 Mode: fs.ModeNamedPipe | 0666,
2044 New: safcm.FileChangeInfo{
2045 Mode: fs.ModeDir | 0751,
2055 `4: sync remote: files: "path" (group): type differs p--------- -> d---------`,
2056 `3: sync remote: files: "path" (group): updating`,
2057 `4: sync remote: files: "path" (group): removing (due to type change)`,
2058 `4: sync remote: files: "path" (group): creating directory`,
2059 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
2060 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
2066 "change: file to symlink (same content)",
2070 Mode: fs.ModeSymlink | 0777,
2074 Data: []byte("target"),
2077 ft.CreateFile("path", "target", 0644)
2084 Mode: fs.ModeSymlink | 0777,
2085 Data: []byte("target"),
2089 FileChanges: []safcm.FileChange{
2092 Old: safcm.FileChangeInfo{
2099 New: safcm.FileChangeInfo{
2100 Mode: fs.ModeSymlink | 0777,
2110 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
2111 `3: sync remote: files: "path" (group): updating`,
2112 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2113 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2140 ft.CreateFile("file", `this
2160 FileChanges: []safcm.FileChange{
2163 Old: safcm.FileChangeInfo{
2170 New: safcm.FileChangeInfo{
2177 DataDiff: `@@ -1,5 +1,7 @@
2191 `4: sync remote: files: "file" (group): content differs`,
2192 `3: sync remote: files: "file" (group): updating`,
2193 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2199 "diff: binary both",
2208 Data: []byte("\x00\x01\x02\x03"),
2212 ft.CreateFile("file", "\x00\x01\x02", 0644)
2220 Data: []byte("\x00\x01\x02"),
2224 FileChanges: []safcm.FileChange{
2227 Old: safcm.FileChangeInfo{
2234 New: safcm.FileChangeInfo{
2241 DataDiff: "Binary files differ, cannot show diff",
2246 `4: sync remote: files: "file" (group): content differs`,
2247 `3: sync remote: files: "file" (group): updating`,
2248 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2263 Data: []byte("content\n"),
2267 ft.CreateFile("file", "\x00\x01\x02", 0644)
2275 Data: []byte("\x00\x01\x02"),
2279 FileChanges: []safcm.FileChange{
2282 Old: safcm.FileChangeInfo{
2289 New: safcm.FileChangeInfo{
2296 DataDiff: `@@ -1,2 +1,2 @@
2305 `4: sync remote: files: "file" (group): content differs`,
2306 `3: sync remote: files: "file" (group): updating`,
2307 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2322 Data: []byte("\x00\x01\x02\x03"),
2326 ft.CreateFile("file", "content\n", 0644)
2334 Data: []byte("content\n"),
2338 FileChanges: []safcm.FileChange{
2341 Old: safcm.FileChangeInfo{
2348 New: safcm.FileChangeInfo{
2355 DataDiff: `@@ -1,2 +1,2 @@
2364 `4: sync remote: files: "file" (group): content differs`,
2365 `3: sync remote: files: "file" (group): updating`,
2366 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2372 for _, tc := range tests {
2373 t.Run(tc.name, func(t *testing.T) {
2374 // Create separate test directory for each test case
2375 path := filepath.Join(cwd, "testdata", "file-"+tc.name)
2376 err = os.Mkdir(path, 0700)
2380 err = os.Chdir(path)
2385 if tc.prepare != nil {
2389 s, res := prepareSync(tc.req, &testRunner{
2394 // Deterministic temporary symlink names
2398 err := s.syncFile(tc.file, &changed)
2399 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
2401 // Remove random file names from result
2402 for i, x := range dbg {
2403 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
2405 testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
2407 files, err := ft.WalkDir(path)
2411 testutil.AssertEqual(t, "files", files, tc.expFiles)
2413 testutil.AssertEqual(t, "changed", changed, tc.expChanged)
2414 testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
2419 err = os.RemoveAll(filepath.Join(cwd, "testdata"))