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.
264 "absolute paths: no change",
266 Files: map[string]*safcm.File{
269 Mode: fs.ModeDir | 0755,
278 Mode: fs.ModeDir | 0755,
287 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
296 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
312 `4: sync remote: files: "/" (group): unchanged`,
313 `4: sync remote: files: "/etc" (group): unchanged`,
314 `4: sync remote: files: "/tmp" (group): unchanged`,
315 `4: sync remote: files: "/var/tmp" (group): unchanged`,
321 "triggers: no change",
323 Files: map[string]*safcm.File{
326 Mode: fs.ModeDir | 0700,
330 TriggerCommands: []string{
336 Mode: fs.ModeDir | 0755,
340 TriggerCommands: []string{
349 Data: []byte("content\n"),
351 TriggerCommands: []string{
352 "echo trigger dir/file",
358 ft.CreateDirectory("dir", 0755)
359 ft.CreateFile("dir/file", "content\n", 0644)
366 Mode: fs.ModeDir | 0755,
371 Data: []byte("content\n"),
376 `4: sync remote: files: "." (group): unchanged`,
377 `4: sync remote: files: "dir" (group): unchanged`,
378 `4: sync remote: files: "dir/file" (group): unchanged`,
384 "triggers: change root",
386 Files: map[string]*safcm.File{
389 Mode: fs.ModeDir | 0700,
393 TriggerCommands: []string{
399 Mode: fs.ModeDir | 0755,
403 TriggerCommands: []string{
412 Data: []byte("content\n"),
414 TriggerCommands: []string{
415 "echo trigger dir/file",
421 err = os.Chmod(".", 0750)
425 ft.CreateDirectory("dir", 0755)
426 ft.CreateFile("dir/file", "content\n", 0644)
435 Mode: fs.ModeDir | 0755,
440 Data: []byte("content\n"),
444 FileChanges: []safcm.FileChange{
447 Old: safcm.FileChangeInfo{
448 Mode: fs.ModeDir | 0750,
454 New: safcm.FileChangeInfo{
455 Mode: fs.ModeDir | 0700,
465 `4: sync remote: files: "." (group): permission differs drwxr-x--- -> drwx------`,
466 `3: sync remote: files: "." (group): updating`,
467 `4: sync remote: files: "." (group): chmodding drwx------`,
468 `3: sync remote: files: ".": queuing trigger on "."`,
469 `4: sync remote: files: "dir" (group): unchanged`,
470 `4: sync remote: files: "dir/file" (group): unchanged`,
476 "triggers: change middle",
478 Files: map[string]*safcm.File{
481 Mode: fs.ModeDir | 0700,
485 TriggerCommands: []string{
491 Mode: fs.ModeDir | 0755,
495 TriggerCommands: []string{
504 Data: []byte("content\n"),
506 TriggerCommands: []string{
507 "echo trigger dir/file",
513 ft.CreateDirectory("dir", 0750)
514 ft.CreateFile("dir/file", "content\n", 0644)
524 Mode: fs.ModeDir | 0755,
529 Data: []byte("content\n"),
533 FileChanges: []safcm.FileChange{
536 Old: safcm.FileChangeInfo{
537 Mode: fs.ModeDir | 0750,
543 New: safcm.FileChangeInfo{
544 Mode: fs.ModeDir | 0755,
554 `4: sync remote: files: "." (group): unchanged`,
555 `4: sync remote: files: "dir" (group): permission differs drwxr-x--- -> drwxr-xr-x`,
556 `3: sync remote: files: "dir" (group): updating`,
557 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
558 `3: sync remote: files: "dir": queuing trigger on "."`,
559 `3: sync remote: files: "dir": queuing trigger on "dir"`,
560 `4: sync remote: files: "dir/file" (group): unchanged`,
566 "triggers: change leaf",
568 Files: map[string]*safcm.File{
571 Mode: fs.ModeDir | 0700,
575 TriggerCommands: []string{
581 Mode: fs.ModeDir | 0755,
585 TriggerCommands: []string{
594 Data: []byte("content\n"),
596 TriggerCommands: []string{
597 "echo trigger dir/file",
603 ft.CreateDirectory("dir", 0755)
614 Mode: fs.ModeDir | 0755,
619 Data: []byte("content\n"),
623 FileChanges: []safcm.FileChange{
627 New: safcm.FileChangeInfo{
638 `4: sync remote: files: "." (group): unchanged`,
639 `4: sync remote: files: "dir" (group): unchanged`,
640 `4: sync remote: files: "dir/file" (group): will create`,
641 `3: sync remote: files: "dir/file" (group): creating`,
642 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
643 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
644 `3: sync remote: files: "dir/file": queuing trigger on "."`,
645 `3: sync remote: files: "dir/file": queuing trigger on "dir"`,
646 `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
652 "triggers: multiple changes",
654 Files: map[string]*safcm.File{
657 Mode: fs.ModeDir | 0700,
661 TriggerCommands: []string{
667 Mode: fs.ModeDir | 0755,
671 TriggerCommands: []string{
680 Data: []byte("content\n"),
682 TriggerCommands: []string{
683 "echo trigger dir/file",
698 Mode: fs.ModeDir | 0755,
703 Data: []byte("content\n"),
707 FileChanges: []safcm.FileChange{
711 New: safcm.FileChangeInfo{
712 Mode: fs.ModeDir | 0755,
722 New: safcm.FileChangeInfo{
733 `4: sync remote: files: "." (group): unchanged`,
734 `4: sync remote: files: "dir" (group): will create`,
735 `3: sync remote: files: "dir" (group): creating`,
736 `4: sync remote: files: "dir" (group): creating directory`,
737 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
738 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
739 `3: sync remote: files: "dir": queuing trigger on "."`,
740 `3: sync remote: files: "dir": queuing trigger on "dir"`,
741 `4: sync remote: files: "dir/file" (group): will create`,
742 `3: sync remote: files: "dir/file" (group): creating`,
743 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
744 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
745 `4: sync remote: files: "dir/file": skipping trigger on ".", already active`,
746 `4: sync remote: files: "dir/file": skipping trigger on "dir", already active`,
747 `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
753 "triggers: absolute paths",
755 Files: map[string]*safcm.File{
758 Mode: fs.ModeDir | 0755,
764 TriggerCommands: []string{
770 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
776 TriggerCommands: []string{
781 Path: tmpTestFilePath,
786 TriggerCommands: []string{
787 "echo trigger /tmp/file",
793 // This is slightly racy but the file name
794 // should be rare enough that this isn't an
796 _, err := os.Stat(tmpTestFilePath)
798 t.Fatalf("%q exists, aborting",
805 // Don't use variable for more robust test
806 "/tmp/safcm-sync-files-test-file",
812 FileChanges: []safcm.FileChange{
814 Path: "/tmp/safcm-sync-files-test-file",
816 New: safcm.FileChangeInfo{
827 `4: sync remote: files: "/" (group): unchanged`,
828 `4: sync remote: files: "/tmp" (group): unchanged`,
829 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): will create`,
830 `3: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating`,
831 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating temporary file "/tmp/.safcm-sync-files-test-file*"`,
832 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): renaming "/tmp/.safcm-sync-files-test-fileRND"`,
833 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/"`,
834 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp"`,
835 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp/safcm-sync-files-test-file"`,
841 for _, tc := range tests {
842 t.Run(tc.name, func(t *testing.T) {
843 // Create separate test directory for each test case
844 path := filepath.Join(cwd, "testdata", "files-"+tc.name)
845 err = os.Mkdir(path, 0700)
854 if tc.prepare != nil {
858 s, res := prepareSync(tc.req, &testRunner{
864 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
866 // Remove random file names from result
867 for i, x := range dbg {
868 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
870 testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
872 files, err := ft.WalkDir(path)
876 testutil.AssertEqual(t, "files", files, tc.expFiles)
878 testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
879 testutil.AssertEqual(t, "triggers",
880 s.triggers, tc.triggers)
884 os.Remove(tmpTestFilePath)
886 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
893 func TestSyncFile(t *testing.T) {
894 cwd, err := os.Getwd()
900 err = os.RemoveAll("testdata")
904 err = os.Mkdir("testdata", 0700)
911 Mode: fs.ModeDir | 0700,
913 user, uid, group, gid := ft.CurrentUserAndGroup()
922 expResp safcm.MsgSyncResp
927 // NOTE: Also update MsgSyncResp in safcm test cases when
928 // changing anything here!
930 // TODO: Add tests for chown and run them only as root
942 Data: []byte("content\n"),
952 Data: []byte("content\n"),
956 FileChanges: []safcm.FileChange{
960 New: safcm.FileChangeInfo{
971 `4: sync remote: files: "file" (group): will create`,
972 `3: sync remote: files: "file" (group): creating`,
973 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
974 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
979 "file: create (dry-run)",
988 Data: []byte("content\n"),
995 FileChanges: []safcm.FileChange{
999 New: safcm.FileChangeInfo{
1010 `4: sync remote: files: "file" (group): will create`,
1011 `3: sync remote: files: "file" (group): creating`,
1012 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
1025 Data: []byte("content\n"),
1029 ft.CreateFile("file", "content\n", 0644)
1037 Data: []byte("content\n"),
1040 safcm.MsgSyncResp{},
1042 `4: sync remote: files: "file" (group): unchanged`,
1048 "file: unchanged (non-default user-group)",
1057 Data: []byte("content\n"),
1061 ft.CreateFile("file", "content\n", 0644)
1069 Data: []byte("content\n"),
1072 safcm.MsgSyncResp{},
1074 `4: sync remote: files: "file" (group): unchanged`,
1084 Mode: 0755 | fs.ModeSetuid,
1087 Data: []byte("content\n"),
1091 ft.CreateFile("file", "content\n", 0755)
1098 Mode: 0755 | fs.ModeSetuid,
1099 Data: []byte("content\n"),
1103 FileChanges: []safcm.FileChange{
1106 Old: safcm.FileChangeInfo{
1113 New: safcm.FileChangeInfo{
1114 Mode: 0755 | fs.ModeSetuid,
1124 `4: sync remote: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
1125 `3: sync remote: files: "file" (group): updating`,
1126 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1127 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1140 Data: []byte("content\n"),
1144 ft.CreateFile("file", "old content\n", 0644)
1152 Data: []byte("content\n"),
1156 FileChanges: []safcm.FileChange{
1159 Old: safcm.FileChangeInfo{
1166 New: safcm.FileChangeInfo{
1173 DataDiff: `@@ -1,2 +1,2 @@
1182 `4: sync remote: files: "file" (group): content differs`,
1183 `3: sync remote: files: "file" (group): updating`,
1184 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1185 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1197 Mode: fs.ModeSymlink | 0777,
1200 Data: []byte("target"),
1209 Mode: fs.ModeSymlink | 0777,
1210 Data: []byte("target"),
1214 FileChanges: []safcm.FileChange{
1218 New: safcm.FileChangeInfo{
1219 Mode: fs.ModeSymlink | 0777,
1229 `4: sync remote: files: "link" (group): will create`,
1230 `3: sync remote: files: "link" (group): creating`,
1231 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1232 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1237 "symlink: create (conflict)",
1241 Mode: fs.ModeSymlink | 0777,
1244 Data: []byte("target"),
1248 ft.CreateFile(".link8717895732742165505", "", 0600)
1254 Path: ".link8717895732742165505",
1260 Mode: fs.ModeSymlink | 0777,
1261 Data: []byte("target"),
1265 FileChanges: []safcm.FileChange{
1269 New: safcm.FileChangeInfo{
1270 Mode: fs.ModeSymlink | 0777,
1280 `4: sync remote: files: "link" (group): will create`,
1281 `3: sync remote: files: "link" (group): creating`,
1282 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1283 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1284 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1289 "symlink: create (dry-run)",
1295 Mode: fs.ModeSymlink | 0777,
1298 Data: []byte("target"),
1305 FileChanges: []safcm.FileChange{
1309 New: safcm.FileChangeInfo{
1310 Mode: fs.ModeSymlink | 0777,
1320 `4: sync remote: files: "link" (group): will create`,
1321 `3: sync remote: files: "link" (group): creating`,
1322 `4: sync remote: files: "link" (group): dry-run, skipping changes`,
1328 "symlink: unchanged",
1332 Mode: fs.ModeSymlink | 0777,
1335 Data: []byte("target"),
1339 ft.CreateSymlink("link", "target")
1346 Mode: fs.ModeSymlink | 0777,
1347 Data: []byte("target"),
1350 safcm.MsgSyncResp{},
1352 `4: sync remote: files: "link" (group): unchanged`,
1362 Mode: fs.ModeSymlink | 0777,
1365 Data: []byte("target"),
1369 ft.CreateSymlink("link", "old-target")
1376 Mode: fs.ModeSymlink | 0777,
1377 Data: []byte("target"),
1381 FileChanges: []safcm.FileChange{
1384 Old: safcm.FileChangeInfo{
1385 Mode: fs.ModeSymlink | 0777,
1391 New: safcm.FileChangeInfo{
1392 Mode: fs.ModeSymlink | 0777,
1398 DataDiff: `@@ -1 +1 @@
1406 `4: sync remote: files: "link" (group): content differs`,
1407 `3: sync remote: files: "link" (group): updating`,
1408 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1409 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1417 "directory: create",
1421 Mode: fs.ModeDir | 0705,
1432 Mode: fs.ModeDir | 0705,
1436 FileChanges: []safcm.FileChange{
1440 New: safcm.FileChangeInfo{
1441 Mode: fs.ModeDir | 0705,
1451 `4: sync remote: files: "dir" (group): will create`,
1452 `3: sync remote: files: "dir" (group): creating`,
1453 `4: sync remote: files: "dir" (group): creating directory`,
1454 `4: sync remote: files: "dir" (group): chmodding drwx---r-x`,
1455 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
1460 "directory: create (dry-run)",
1466 Mode: fs.ModeDir | 0644,
1475 FileChanges: []safcm.FileChange{
1479 New: safcm.FileChangeInfo{
1480 Mode: fs.ModeDir | 0644,
1490 `4: sync remote: files: "dir" (group): will create`,
1491 `3: sync remote: files: "dir" (group): creating`,
1492 `4: sync remote: files: "dir" (group): dry-run, skipping changes`,
1498 "directory: unchanged",
1502 Mode: fs.ModeDir | 0755,
1508 ft.CreateDirectory("dir", 0755)
1515 Mode: fs.ModeDir | 0755,
1518 safcm.MsgSyncResp{},
1520 `4: sync remote: files: "dir" (group): unchanged`,
1526 "directory: permission",
1530 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1536 ft.CreateDirectory("dir", 0500|fs.ModeSticky)
1543 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1547 FileChanges: []safcm.FileChange{
1550 Old: safcm.FileChangeInfo{
1551 Mode: fs.ModeDir | 0500 | fs.ModeSticky,
1557 New: safcm.FileChangeInfo{
1558 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1568 `4: sync remote: files: "dir" (group): permission differs dtr-x------ -> dgrwxr-xr-x`,
1569 `3: sync remote: files: "dir" (group): updating`,
1570 `4: sync remote: files: "dir" (group): chmodding dgrwxr-xr-x`,
1578 "change: file to directory",
1582 Mode: fs.ModeDir | 0751,
1588 ft.CreateFile("path", "content\n", 0644)
1595 Mode: fs.ModeDir | 0751,
1599 FileChanges: []safcm.FileChange{
1602 Old: safcm.FileChangeInfo{
1609 New: safcm.FileChangeInfo{
1610 Mode: fs.ModeDir | 0751,
1616 DataDiff: `@@ -1,2 +1 @@
1624 `4: sync remote: files: "path" (group): type differs ---------- -> d---------`,
1625 `3: sync remote: files: "path" (group): updating`,
1626 `4: sync remote: files: "path" (group): removing (due to type change)`,
1627 `4: sync remote: files: "path" (group): creating directory`,
1628 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1629 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1635 "change: file to symlink",
1639 Mode: fs.ModeSymlink | 0777,
1643 Data: []byte("target"),
1646 ft.CreateFile("path", "content\n", 0644)
1653 Mode: fs.ModeSymlink | 0777,
1654 Data: []byte("target"),
1658 FileChanges: []safcm.FileChange{
1661 Old: safcm.FileChangeInfo{
1668 New: safcm.FileChangeInfo{
1669 Mode: fs.ModeSymlink | 0777,
1675 DataDiff: `@@ -1,2 +1 @@
1684 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
1685 `3: sync remote: files: "path" (group): updating`,
1686 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1687 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1693 "change: symlink to file",
1701 Data: []byte("content\n"),
1704 ft.CreateSymlink("path", "target")
1712 Data: []byte("content\n"),
1716 FileChanges: []safcm.FileChange{
1719 Old: safcm.FileChangeInfo{
1720 Mode: fs.ModeSymlink | 0777,
1726 New: safcm.FileChangeInfo{
1733 DataDiff: `@@ -1 +1,2 @@
1742 `4: sync remote: files: "path" (group): type differs L--------- -> ----------`,
1743 `3: sync remote: files: "path" (group): updating`,
1744 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1745 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1751 "change: symlink to directory",
1755 Mode: fs.ModeDir | 0751,
1761 ft.CreateSymlink("path", "target")
1768 Mode: fs.ModeDir | 0751,
1772 FileChanges: []safcm.FileChange{
1775 Old: safcm.FileChangeInfo{
1776 Mode: fs.ModeSymlink | 0777,
1782 New: safcm.FileChangeInfo{
1783 Mode: fs.ModeDir | 0751,
1789 DataDiff: `@@ -1 +1 @@
1797 `4: sync remote: files: "path" (group): type differs L--------- -> d---------`,
1798 `3: sync remote: files: "path" (group): updating`,
1799 `4: sync remote: files: "path" (group): removing (due to type change)`,
1800 `4: sync remote: files: "path" (group): creating directory`,
1801 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1802 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1808 "change: directory to file",
1816 Data: []byte("content\n"),
1819 ft.CreateDirectory("path", 0777)
1827 Data: []byte("content\n"),
1831 FileChanges: []safcm.FileChange{
1834 Old: safcm.FileChangeInfo{
1835 Mode: fs.ModeDir | 0777,
1841 New: safcm.FileChangeInfo{
1852 `4: sync remote: files: "path" (group): type differs d--------- -> ----------`,
1853 `3: sync remote: files: "path" (group): updating`,
1854 `4: sync remote: files: "path" (group): removing (due to type change)`,
1855 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1856 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1862 "change: directory to symlink",
1866 Mode: fs.ModeSymlink | 0777,
1870 Data: []byte("target"),
1873 ft.CreateDirectory("path", 0777)
1880 Mode: fs.ModeSymlink | 0777,
1881 Data: []byte("target"),
1885 FileChanges: []safcm.FileChange{
1888 Old: safcm.FileChangeInfo{
1889 Mode: fs.ModeDir | 0777,
1895 New: safcm.FileChangeInfo{
1896 Mode: fs.ModeSymlink | 0777,
1906 `4: sync remote: files: "path" (group): type differs d--------- -> L---------`,
1907 `3: sync remote: files: "path" (group): updating`,
1908 `4: sync remote: files: "path" (group): removing (due to type change)`,
1909 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1910 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1916 "change: other to file",
1924 Data: []byte("content\n"),
1927 ft.CreateFifo("path", 0666)
1935 Data: []byte("content\n"),
1939 FileChanges: []safcm.FileChange{
1942 Old: safcm.FileChangeInfo{
1943 Mode: fs.ModeNamedPipe | 0666,
1949 New: safcm.FileChangeInfo{
1960 `4: sync remote: files: "path" (group): type differs p--------- -> ----------`,
1961 `3: sync remote: files: "path" (group): updating`,
1962 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1963 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1969 "change: other to symlink",
1973 Mode: fs.ModeSymlink | 0777,
1977 Data: []byte("target"),
1980 ft.CreateFifo("path", 0666)
1987 Mode: fs.ModeSymlink | 0777,
1988 Data: []byte("target"),
1992 FileChanges: []safcm.FileChange{
1995 Old: safcm.FileChangeInfo{
1996 Mode: fs.ModeNamedPipe | 0666,
2002 New: safcm.FileChangeInfo{
2003 Mode: fs.ModeSymlink | 0777,
2013 `4: sync remote: files: "path" (group): type differs p--------- -> L---------`,
2014 `3: sync remote: files: "path" (group): updating`,
2015 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2016 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2022 "change: other to directory",
2026 Mode: fs.ModeDir | 0751,
2032 ft.CreateFifo("path", 0666)
2039 Mode: fs.ModeDir | 0751,
2043 FileChanges: []safcm.FileChange{
2046 Old: safcm.FileChangeInfo{
2047 Mode: fs.ModeNamedPipe | 0666,
2053 New: safcm.FileChangeInfo{
2054 Mode: fs.ModeDir | 0751,
2064 `4: sync remote: files: "path" (group): type differs p--------- -> d---------`,
2065 `3: sync remote: files: "path" (group): updating`,
2066 `4: sync remote: files: "path" (group): removing (due to type change)`,
2067 `4: sync remote: files: "path" (group): creating directory`,
2068 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
2069 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
2075 "change: file to symlink (same content)",
2079 Mode: fs.ModeSymlink | 0777,
2083 Data: []byte("target"),
2086 ft.CreateFile("path", "target", 0644)
2093 Mode: fs.ModeSymlink | 0777,
2094 Data: []byte("target"),
2098 FileChanges: []safcm.FileChange{
2101 Old: safcm.FileChangeInfo{
2108 New: safcm.FileChangeInfo{
2109 Mode: fs.ModeSymlink | 0777,
2119 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
2120 `3: sync remote: files: "path" (group): updating`,
2121 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2122 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2149 ft.CreateFile("file", `this
2169 FileChanges: []safcm.FileChange{
2172 Old: safcm.FileChangeInfo{
2179 New: safcm.FileChangeInfo{
2186 DataDiff: `@@ -1,5 +1,7 @@
2200 `4: sync remote: files: "file" (group): content differs`,
2201 `3: sync remote: files: "file" (group): updating`,
2202 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2208 "diff: binary both",
2217 Data: []byte("\x00\x01\x02\x03"),
2221 ft.CreateFile("file", "\x00\x01\x02", 0644)
2229 Data: []byte("\x00\x01\x02"),
2233 FileChanges: []safcm.FileChange{
2236 Old: safcm.FileChangeInfo{
2243 New: safcm.FileChangeInfo{
2250 DataDiff: "Binary files differ, cannot show diff",
2255 `4: sync remote: files: "file" (group): content differs`,
2256 `3: sync remote: files: "file" (group): updating`,
2257 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2272 Data: []byte("content\n"),
2276 ft.CreateFile("file", "\x00\x01\x02", 0644)
2284 Data: []byte("\x00\x01\x02"),
2288 FileChanges: []safcm.FileChange{
2291 Old: safcm.FileChangeInfo{
2298 New: safcm.FileChangeInfo{
2305 DataDiff: `@@ -1,2 +1,2 @@
2314 `4: sync remote: files: "file" (group): content differs`,
2315 `3: sync remote: files: "file" (group): updating`,
2316 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2331 Data: []byte("\x00\x01\x02\x03"),
2335 ft.CreateFile("file", "content\n", 0644)
2343 Data: []byte("content\n"),
2347 FileChanges: []safcm.FileChange{
2350 Old: safcm.FileChangeInfo{
2357 New: safcm.FileChangeInfo{
2364 DataDiff: `@@ -1,2 +1,2 @@
2373 `4: sync remote: files: "file" (group): content differs`,
2374 `3: sync remote: files: "file" (group): updating`,
2375 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2381 for _, tc := range tests {
2382 t.Run(tc.name, func(t *testing.T) {
2383 // Create separate test directory for each test case
2384 path := filepath.Join(cwd, "testdata", "file-"+tc.name)
2385 err = os.Mkdir(path, 0700)
2389 err = os.Chdir(path)
2394 if tc.prepare != nil {
2398 s, res := prepareSync(tc.req, &testRunner{
2403 // Deterministic temporary symlink names
2407 err := s.syncFile(tc.file, &changed)
2408 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
2410 // Remove random file names from result
2411 for i, x := range dbg {
2412 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
2414 testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
2416 files, err := ft.WalkDir(path)
2420 testutil.AssertEqual(t, "files", files, tc.expFiles)
2422 testutil.AssertEqual(t, "changed", changed, tc.expChanged)
2423 testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
2428 err = os.RemoveAll(filepath.Join(cwd, "testdata"))