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,
300 `4: sync remote: files: "/" (group): unchanged`,
301 `4: sync remote: files: "/etc" (group): unchanged`,
302 `4: sync remote: files: "/tmp" (group): unchanged`,
308 "triggers: no change",
310 Files: map[string]*safcm.File{
313 Mode: fs.ModeDir | 0700,
317 TriggerCommands: []string{
323 Mode: fs.ModeDir | 0755,
327 TriggerCommands: []string{
336 Data: []byte("content\n"),
338 TriggerCommands: []string{
339 "echo trigger dir/file",
345 ft.CreateDirectory("dir", 0755)
346 ft.CreateFile("dir/file", "content\n", 0644)
353 Mode: fs.ModeDir | 0755,
358 Data: []byte("content\n"),
363 `4: sync remote: files: "." (group): unchanged`,
364 `4: sync remote: files: "dir" (group): unchanged`,
365 `4: sync remote: files: "dir/file" (group): unchanged`,
371 "triggers: change root",
373 Files: map[string]*safcm.File{
376 Mode: fs.ModeDir | 0700,
380 TriggerCommands: []string{
386 Mode: fs.ModeDir | 0755,
390 TriggerCommands: []string{
399 Data: []byte("content\n"),
401 TriggerCommands: []string{
402 "echo trigger dir/file",
408 err = os.Chmod(".", 0750)
412 ft.CreateDirectory("dir", 0755)
413 ft.CreateFile("dir/file", "content\n", 0644)
422 Mode: fs.ModeDir | 0755,
427 Data: []byte("content\n"),
431 FileChanges: []safcm.FileChange{
434 Old: safcm.FileChangeInfo{
435 Mode: fs.ModeDir | 0750,
441 New: safcm.FileChangeInfo{
442 Mode: fs.ModeDir | 0700,
452 `4: sync remote: files: "." (group): permission differs drwxr-x--- -> drwx------`,
453 `3: sync remote: files: "." (group): updating`,
454 `4: sync remote: files: "." (group): chmodding drwx------`,
455 `3: sync remote: files: ".": queuing trigger on "."`,
456 `4: sync remote: files: "dir" (group): unchanged`,
457 `4: sync remote: files: "dir/file" (group): unchanged`,
463 "triggers: change middle",
465 Files: map[string]*safcm.File{
468 Mode: fs.ModeDir | 0700,
472 TriggerCommands: []string{
478 Mode: fs.ModeDir | 0755,
482 TriggerCommands: []string{
491 Data: []byte("content\n"),
493 TriggerCommands: []string{
494 "echo trigger dir/file",
500 ft.CreateDirectory("dir", 0750)
501 ft.CreateFile("dir/file", "content\n", 0644)
511 Mode: fs.ModeDir | 0755,
516 Data: []byte("content\n"),
520 FileChanges: []safcm.FileChange{
523 Old: safcm.FileChangeInfo{
524 Mode: fs.ModeDir | 0750,
530 New: safcm.FileChangeInfo{
531 Mode: fs.ModeDir | 0755,
541 `4: sync remote: files: "." (group): unchanged`,
542 `4: sync remote: files: "dir" (group): permission differs drwxr-x--- -> drwxr-xr-x`,
543 `3: sync remote: files: "dir" (group): updating`,
544 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
545 `3: sync remote: files: "dir": queuing trigger on "."`,
546 `3: sync remote: files: "dir": queuing trigger on "dir"`,
547 `4: sync remote: files: "dir/file" (group): unchanged`,
553 "triggers: change leaf",
555 Files: map[string]*safcm.File{
558 Mode: fs.ModeDir | 0700,
562 TriggerCommands: []string{
568 Mode: fs.ModeDir | 0755,
572 TriggerCommands: []string{
581 Data: []byte("content\n"),
583 TriggerCommands: []string{
584 "echo trigger dir/file",
590 ft.CreateDirectory("dir", 0755)
601 Mode: fs.ModeDir | 0755,
606 Data: []byte("content\n"),
610 FileChanges: []safcm.FileChange{
614 New: safcm.FileChangeInfo{
625 `4: sync remote: files: "." (group): unchanged`,
626 `4: sync remote: files: "dir" (group): unchanged`,
627 `4: sync remote: files: "dir/file" (group): will create`,
628 `3: sync remote: files: "dir/file" (group): creating`,
629 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
630 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
631 `3: sync remote: files: "dir/file": queuing trigger on "."`,
632 `3: sync remote: files: "dir/file": queuing trigger on "dir"`,
633 `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
639 "triggers: multiple changes",
641 Files: map[string]*safcm.File{
644 Mode: fs.ModeDir | 0700,
648 TriggerCommands: []string{
654 Mode: fs.ModeDir | 0755,
658 TriggerCommands: []string{
667 Data: []byte("content\n"),
669 TriggerCommands: []string{
670 "echo trigger dir/file",
685 Mode: fs.ModeDir | 0755,
690 Data: []byte("content\n"),
694 FileChanges: []safcm.FileChange{
698 New: safcm.FileChangeInfo{
699 Mode: fs.ModeDir | 0755,
709 New: safcm.FileChangeInfo{
720 `4: sync remote: files: "." (group): unchanged`,
721 `4: sync remote: files: "dir" (group): will create`,
722 `3: sync remote: files: "dir" (group): creating`,
723 `4: sync remote: files: "dir" (group): creating directory`,
724 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
725 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
726 `3: sync remote: files: "dir": queuing trigger on "."`,
727 `3: sync remote: files: "dir": queuing trigger on "dir"`,
728 `4: sync remote: files: "dir/file" (group): will create`,
729 `3: sync remote: files: "dir/file" (group): creating`,
730 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
731 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
732 `4: sync remote: files: "dir/file": skipping trigger on ".", already active`,
733 `4: sync remote: files: "dir/file": skipping trigger on "dir", already active`,
734 `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
740 "triggers: absolute paths",
742 Files: map[string]*safcm.File{
745 Mode: fs.ModeDir | 0755,
749 TriggerCommands: []string{
755 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
759 TriggerCommands: []string{
764 Path: tmpTestFilePath,
769 TriggerCommands: []string{
770 "echo trigger /tmp/file",
776 // This is slightly racy but the file name
777 // should be rare enough that this isn't an
779 _, err := os.Stat(tmpTestFilePath)
781 t.Fatalf("%q exists, aborting",
788 // Don't use variable for more robust test
789 "/tmp/safcm-sync-files-test-file",
795 FileChanges: []safcm.FileChange{
797 Path: "/tmp/safcm-sync-files-test-file",
799 New: safcm.FileChangeInfo{
810 `4: sync remote: files: "/" (group): unchanged`,
811 `4: sync remote: files: "/tmp" (group): unchanged`,
812 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): will create`,
813 `3: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating`,
814 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating temporary file "/tmp/.safcm-sync-files-test-file*"`,
815 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): renaming "/tmp/.safcm-sync-files-test-fileRND"`,
816 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/"`,
817 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp"`,
818 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp/safcm-sync-files-test-file"`,
824 for _, tc := range tests {
825 t.Run(tc.name, func(t *testing.T) {
826 // Create separate test directory for each test case
827 path := filepath.Join(cwd, "testdata", "files-"+tc.name)
828 err = os.Mkdir(path, 0700)
837 if tc.prepare != nil {
841 s, res := prepareSync(tc.req, &testRunner{
847 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
849 // Remove random file names from result
850 for i, x := range dbg {
851 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
853 testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
855 files, err := ft.WalkDir(path)
859 testutil.AssertEqual(t, "files", files, tc.expFiles)
861 testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
862 testutil.AssertEqual(t, "triggers",
863 s.triggers, tc.triggers)
867 os.Remove(tmpTestFilePath)
869 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
876 func TestSyncFile(t *testing.T) {
877 cwd, err := os.Getwd()
883 err = os.RemoveAll("testdata")
887 err = os.Mkdir("testdata", 0700)
894 Mode: fs.ModeDir | 0700,
896 user, uid, group, gid := ft.CurrentUserAndGroup()
905 expResp safcm.MsgSyncResp
910 // NOTE: Also update MsgSyncResp in safcm test cases when
911 // changing anything here!
913 // TODO: Add tests for chown and run them only as root
925 Data: []byte("content\n"),
935 Data: []byte("content\n"),
939 FileChanges: []safcm.FileChange{
943 New: safcm.FileChangeInfo{
954 `4: sync remote: files: "file" (group): will create`,
955 `3: sync remote: files: "file" (group): creating`,
956 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
957 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
962 "file: create (dry-run)",
971 Data: []byte("content\n"),
978 FileChanges: []safcm.FileChange{
982 New: safcm.FileChangeInfo{
993 `4: sync remote: files: "file" (group): will create`,
994 `3: sync remote: files: "file" (group): creating`,
995 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
1008 Data: []byte("content\n"),
1012 ft.CreateFile("file", "content\n", 0644)
1020 Data: []byte("content\n"),
1023 safcm.MsgSyncResp{},
1025 `4: sync remote: files: "file" (group): unchanged`,
1031 "file: unchanged (non-default user-group)",
1040 Data: []byte("content\n"),
1044 ft.CreateFile("file", "content\n", 0644)
1052 Data: []byte("content\n"),
1055 safcm.MsgSyncResp{},
1057 `4: sync remote: files: "file" (group): unchanged`,
1067 Mode: 0755 | fs.ModeSetuid,
1070 Data: []byte("content\n"),
1074 ft.CreateFile("file", "content\n", 0755)
1081 Mode: 0755 | fs.ModeSetuid,
1082 Data: []byte("content\n"),
1086 FileChanges: []safcm.FileChange{
1089 Old: safcm.FileChangeInfo{
1096 New: safcm.FileChangeInfo{
1097 Mode: 0755 | fs.ModeSetuid,
1107 `4: sync remote: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
1108 `3: sync remote: files: "file" (group): updating`,
1109 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1110 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1123 Data: []byte("content\n"),
1127 ft.CreateFile("file", "old content\n", 0644)
1135 Data: []byte("content\n"),
1139 FileChanges: []safcm.FileChange{
1142 Old: safcm.FileChangeInfo{
1149 New: safcm.FileChangeInfo{
1156 DataDiff: `@@ -1,2 +1,2 @@
1165 `4: sync remote: files: "file" (group): content differs`,
1166 `3: sync remote: files: "file" (group): updating`,
1167 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1168 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1180 Mode: fs.ModeSymlink | 0777,
1183 Data: []byte("target"),
1192 Mode: fs.ModeSymlink | 0777,
1193 Data: []byte("target"),
1197 FileChanges: []safcm.FileChange{
1201 New: safcm.FileChangeInfo{
1202 Mode: fs.ModeSymlink | 0777,
1212 `4: sync remote: files: "link" (group): will create`,
1213 `3: sync remote: files: "link" (group): creating`,
1214 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1215 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1220 "symlink: create (conflict)",
1224 Mode: fs.ModeSymlink | 0777,
1227 Data: []byte("target"),
1231 ft.CreateFile(".link8717895732742165505", "", 0600)
1237 Path: ".link8717895732742165505",
1243 Mode: fs.ModeSymlink | 0777,
1244 Data: []byte("target"),
1248 FileChanges: []safcm.FileChange{
1252 New: safcm.FileChangeInfo{
1253 Mode: fs.ModeSymlink | 0777,
1263 `4: sync remote: files: "link" (group): will create`,
1264 `3: sync remote: files: "link" (group): creating`,
1265 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1266 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1267 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1272 "symlink: create (dry-run)",
1278 Mode: fs.ModeSymlink | 0777,
1281 Data: []byte("target"),
1288 FileChanges: []safcm.FileChange{
1292 New: safcm.FileChangeInfo{
1293 Mode: fs.ModeSymlink | 0777,
1303 `4: sync remote: files: "link" (group): will create`,
1304 `3: sync remote: files: "link" (group): creating`,
1305 `4: sync remote: files: "link" (group): dry-run, skipping changes`,
1311 "symlink: unchanged",
1315 Mode: fs.ModeSymlink | 0777,
1318 Data: []byte("target"),
1322 ft.CreateSymlink("link", "target")
1329 Mode: fs.ModeSymlink | 0777,
1330 Data: []byte("target"),
1333 safcm.MsgSyncResp{},
1335 `4: sync remote: files: "link" (group): unchanged`,
1345 Mode: fs.ModeSymlink | 0777,
1348 Data: []byte("target"),
1352 ft.CreateSymlink("link", "old-target")
1359 Mode: fs.ModeSymlink | 0777,
1360 Data: []byte("target"),
1364 FileChanges: []safcm.FileChange{
1367 Old: safcm.FileChangeInfo{
1368 Mode: fs.ModeSymlink | 0777,
1374 New: safcm.FileChangeInfo{
1375 Mode: fs.ModeSymlink | 0777,
1381 DataDiff: `@@ -1 +1 @@
1389 `4: sync remote: files: "link" (group): content differs`,
1390 `3: sync remote: files: "link" (group): updating`,
1391 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1392 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1400 "directory: create",
1404 Mode: fs.ModeDir | 0705,
1415 Mode: fs.ModeDir | 0705,
1419 FileChanges: []safcm.FileChange{
1423 New: safcm.FileChangeInfo{
1424 Mode: fs.ModeDir | 0705,
1434 `4: sync remote: files: "dir" (group): will create`,
1435 `3: sync remote: files: "dir" (group): creating`,
1436 `4: sync remote: files: "dir" (group): creating directory`,
1437 `4: sync remote: files: "dir" (group): chmodding drwx---r-x`,
1438 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
1443 "directory: create (dry-run)",
1449 Mode: fs.ModeDir | 0644,
1458 FileChanges: []safcm.FileChange{
1462 New: safcm.FileChangeInfo{
1463 Mode: fs.ModeDir | 0644,
1473 `4: sync remote: files: "dir" (group): will create`,
1474 `3: sync remote: files: "dir" (group): creating`,
1475 `4: sync remote: files: "dir" (group): dry-run, skipping changes`,
1481 "directory: unchanged",
1485 Mode: fs.ModeDir | 0755,
1491 ft.CreateDirectory("dir", 0755)
1498 Mode: fs.ModeDir | 0755,
1501 safcm.MsgSyncResp{},
1503 `4: sync remote: files: "dir" (group): unchanged`,
1509 "directory: permission",
1513 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1519 ft.CreateDirectory("dir", 0500|fs.ModeSticky)
1526 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1530 FileChanges: []safcm.FileChange{
1533 Old: safcm.FileChangeInfo{
1534 Mode: fs.ModeDir | 0500 | fs.ModeSticky,
1540 New: safcm.FileChangeInfo{
1541 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1551 `4: sync remote: files: "dir" (group): permission differs dtr-x------ -> dgrwxr-xr-x`,
1552 `3: sync remote: files: "dir" (group): updating`,
1553 `4: sync remote: files: "dir" (group): chmodding dgrwxr-xr-x`,
1561 "change: file to directory",
1565 Mode: fs.ModeDir | 0751,
1571 ft.CreateFile("path", "content\n", 0644)
1578 Mode: fs.ModeDir | 0751,
1582 FileChanges: []safcm.FileChange{
1585 Old: safcm.FileChangeInfo{
1592 New: safcm.FileChangeInfo{
1593 Mode: fs.ModeDir | 0751,
1599 DataDiff: `@@ -1,2 +1 @@
1607 `4: sync remote: files: "path" (group): type differs ---------- -> d---------`,
1608 `3: sync remote: files: "path" (group): updating`,
1609 `4: sync remote: files: "path" (group): removing (due to type change)`,
1610 `4: sync remote: files: "path" (group): creating directory`,
1611 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1612 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1618 "change: file to symlink",
1622 Mode: fs.ModeSymlink | 0777,
1626 Data: []byte("target"),
1629 ft.CreateFile("path", "content\n", 0644)
1636 Mode: fs.ModeSymlink | 0777,
1637 Data: []byte("target"),
1641 FileChanges: []safcm.FileChange{
1644 Old: safcm.FileChangeInfo{
1651 New: safcm.FileChangeInfo{
1652 Mode: fs.ModeSymlink | 0777,
1658 DataDiff: `@@ -1,2 +1 @@
1667 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
1668 `3: sync remote: files: "path" (group): updating`,
1669 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1670 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1676 "change: symlink to file",
1684 Data: []byte("content\n"),
1687 ft.CreateSymlink("path", "target")
1695 Data: []byte("content\n"),
1699 FileChanges: []safcm.FileChange{
1702 Old: safcm.FileChangeInfo{
1703 Mode: fs.ModeSymlink | 0777,
1709 New: safcm.FileChangeInfo{
1716 DataDiff: `@@ -1 +1,2 @@
1725 `4: sync remote: files: "path" (group): type differs L--------- -> ----------`,
1726 `3: sync remote: files: "path" (group): updating`,
1727 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1728 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1734 "change: symlink to directory",
1738 Mode: fs.ModeDir | 0751,
1744 ft.CreateSymlink("path", "target")
1751 Mode: fs.ModeDir | 0751,
1755 FileChanges: []safcm.FileChange{
1758 Old: safcm.FileChangeInfo{
1759 Mode: fs.ModeSymlink | 0777,
1765 New: safcm.FileChangeInfo{
1766 Mode: fs.ModeDir | 0751,
1772 DataDiff: `@@ -1 +1 @@
1780 `4: sync remote: files: "path" (group): type differs L--------- -> d---------`,
1781 `3: sync remote: files: "path" (group): updating`,
1782 `4: sync remote: files: "path" (group): removing (due to type change)`,
1783 `4: sync remote: files: "path" (group): creating directory`,
1784 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1785 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1791 "change: directory to file",
1799 Data: []byte("content\n"),
1802 ft.CreateDirectory("path", 0777)
1810 Data: []byte("content\n"),
1814 FileChanges: []safcm.FileChange{
1817 Old: safcm.FileChangeInfo{
1818 Mode: fs.ModeDir | 0777,
1824 New: safcm.FileChangeInfo{
1835 `4: sync remote: files: "path" (group): type differs d--------- -> ----------`,
1836 `3: sync remote: files: "path" (group): updating`,
1837 `4: sync remote: files: "path" (group): removing (due to type change)`,
1838 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1839 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1845 "change: directory to symlink",
1849 Mode: fs.ModeSymlink | 0777,
1853 Data: []byte("target"),
1856 ft.CreateDirectory("path", 0777)
1863 Mode: fs.ModeSymlink | 0777,
1864 Data: []byte("target"),
1868 FileChanges: []safcm.FileChange{
1871 Old: safcm.FileChangeInfo{
1872 Mode: fs.ModeDir | 0777,
1878 New: safcm.FileChangeInfo{
1879 Mode: fs.ModeSymlink | 0777,
1889 `4: sync remote: files: "path" (group): type differs d--------- -> L---------`,
1890 `3: sync remote: files: "path" (group): updating`,
1891 `4: sync remote: files: "path" (group): removing (due to type change)`,
1892 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1893 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1899 "change: other to file",
1907 Data: []byte("content\n"),
1910 ft.CreateFifo("path", 0666)
1918 Data: []byte("content\n"),
1922 FileChanges: []safcm.FileChange{
1925 Old: safcm.FileChangeInfo{
1926 Mode: fs.ModeNamedPipe | 0666,
1932 New: safcm.FileChangeInfo{
1943 `4: sync remote: files: "path" (group): type differs p--------- -> ----------`,
1944 `3: sync remote: files: "path" (group): updating`,
1945 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1946 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1952 "change: other to symlink",
1956 Mode: fs.ModeSymlink | 0777,
1960 Data: []byte("target"),
1963 ft.CreateFifo("path", 0666)
1970 Mode: fs.ModeSymlink | 0777,
1971 Data: []byte("target"),
1975 FileChanges: []safcm.FileChange{
1978 Old: safcm.FileChangeInfo{
1979 Mode: fs.ModeNamedPipe | 0666,
1985 New: safcm.FileChangeInfo{
1986 Mode: fs.ModeSymlink | 0777,
1996 `4: sync remote: files: "path" (group): type differs p--------- -> L---------`,
1997 `3: sync remote: files: "path" (group): updating`,
1998 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1999 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2005 "change: other to directory",
2009 Mode: fs.ModeDir | 0751,
2015 ft.CreateFifo("path", 0666)
2022 Mode: fs.ModeDir | 0751,
2026 FileChanges: []safcm.FileChange{
2029 Old: safcm.FileChangeInfo{
2030 Mode: fs.ModeNamedPipe | 0666,
2036 New: safcm.FileChangeInfo{
2037 Mode: fs.ModeDir | 0751,
2047 `4: sync remote: files: "path" (group): type differs p--------- -> d---------`,
2048 `3: sync remote: files: "path" (group): updating`,
2049 `4: sync remote: files: "path" (group): removing (due to type change)`,
2050 `4: sync remote: files: "path" (group): creating directory`,
2051 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
2052 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
2058 "change: file to symlink (same content)",
2062 Mode: fs.ModeSymlink | 0777,
2066 Data: []byte("target"),
2069 ft.CreateFile("path", "target", 0644)
2076 Mode: fs.ModeSymlink | 0777,
2077 Data: []byte("target"),
2081 FileChanges: []safcm.FileChange{
2084 Old: safcm.FileChangeInfo{
2091 New: safcm.FileChangeInfo{
2092 Mode: fs.ModeSymlink | 0777,
2102 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
2103 `3: sync remote: files: "path" (group): updating`,
2104 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2105 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2132 ft.CreateFile("file", `this
2152 FileChanges: []safcm.FileChange{
2155 Old: safcm.FileChangeInfo{
2162 New: safcm.FileChangeInfo{
2169 DataDiff: `@@ -1,5 +1,7 @@
2183 `4: sync remote: files: "file" (group): content differs`,
2184 `3: sync remote: files: "file" (group): updating`,
2185 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2191 "diff: binary both",
2200 Data: []byte("\x00\x01\x02\x03"),
2204 ft.CreateFile("file", "\x00\x01\x02", 0644)
2212 Data: []byte("\x00\x01\x02"),
2216 FileChanges: []safcm.FileChange{
2219 Old: safcm.FileChangeInfo{
2226 New: safcm.FileChangeInfo{
2233 DataDiff: "Binary files differ, cannot show diff",
2238 `4: sync remote: files: "file" (group): content differs`,
2239 `3: sync remote: files: "file" (group): updating`,
2240 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2255 Data: []byte("content\n"),
2259 ft.CreateFile("file", "\x00\x01\x02", 0644)
2267 Data: []byte("\x00\x01\x02"),
2271 FileChanges: []safcm.FileChange{
2274 Old: safcm.FileChangeInfo{
2281 New: safcm.FileChangeInfo{
2288 DataDiff: `@@ -1,2 +1,2 @@
2297 `4: sync remote: files: "file" (group): content differs`,
2298 `3: sync remote: files: "file" (group): updating`,
2299 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2314 Data: []byte("\x00\x01\x02\x03"),
2318 ft.CreateFile("file", "content\n", 0644)
2326 Data: []byte("content\n"),
2330 FileChanges: []safcm.FileChange{
2333 Old: safcm.FileChangeInfo{
2340 New: safcm.FileChangeInfo{
2347 DataDiff: `@@ -1,2 +1,2 @@
2356 `4: sync remote: files: "file" (group): content differs`,
2357 `3: sync remote: files: "file" (group): updating`,
2358 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2364 for _, tc := range tests {
2365 t.Run(tc.name, func(t *testing.T) {
2366 // Create separate test directory for each test case
2367 path := filepath.Join(cwd, "testdata", "file-"+tc.name)
2368 err = os.Mkdir(path, 0700)
2372 err = os.Chdir(path)
2377 if tc.prepare != nil {
2381 s, res := prepareSync(tc.req, &testRunner{
2386 // Deterministic temporary symlink names
2390 err := s.syncFile(tc.file, &changed)
2391 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
2393 // Remove random file names from result
2394 for i, x := range dbg {
2395 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
2397 testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
2399 files, err := ft.WalkDir(path)
2403 testutil.AssertEqual(t, "files", files, tc.expFiles)
2405 testutil.AssertEqual(t, "changed", changed, tc.expChanged)
2406 testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
2411 err = os.RemoveAll(filepath.Join(cwd, "testdata"))