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/>.
28 "github.com/google/go-cmp/cmp"
30 "ruderich.org/simon/safcm"
31 ft "ruderich.org/simon/safcm/cmd/safcm-remote/sync/filetest"
34 var randFilesRegexp = regexp.MustCompile(`\d+"$`)
36 func TestSyncFiles(t *testing.T) {
37 cwd, err := os.Getwd()
43 err = os.RemoveAll("testdata")
47 err = os.Mkdir("testdata", 0700)
54 Mode: fs.ModeDir | 0700,
56 user, uid, group, gid := ft.CurrentUserAndGroup()
58 tmpTestFilePath := "/tmp/safcm-sync-files-test-file"
66 expResp safcm.MsgSyncResp
71 // NOTE: Also update MsgSyncResp in safcm test cases when
72 // changing anything here!
74 // See TestSyncFile() for most file related tests. This
75 // function only tests the overall results and triggers.
80 Files: map[string]*safcm.File{
83 Mode: fs.ModeDir | 0700,
90 Mode: fs.ModeDir | 0755,
100 Data: []byte("content\n"),
111 Mode: fs.ModeDir | 0755,
116 Data: []byte("content\n"),
120 FileChanges: []safcm.FileChange{
124 New: safcm.FileChangeInfo{
125 Mode: fs.ModeDir | 0755,
135 New: safcm.FileChangeInfo{
146 `4: sync remote: files: "." (group): unchanged`,
147 `4: sync remote: files: "dir" (group): will create`,
148 `3: sync remote: files: "dir" (group): creating`,
149 `4: sync remote: files: "dir" (group): creating directory`,
150 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
151 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
152 `4: sync remote: files: "dir/file" (group): will create`,
153 `3: sync remote: files: "dir/file" (group): creating`,
154 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
155 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
163 Files: map[string]*safcm.File{
166 Mode: fs.ModeDir | 0700,
173 Mode: fs.ModeDir | 0755,
183 Data: []byte("content\n"),
189 ft.CreateDirectory("dir", 0755)
190 ft.CreateFile("dir/file", "content\n", 0644)
197 Mode: fs.ModeDir | 0755,
202 Data: []byte("content\n"),
207 `4: sync remote: files: "." (group): unchanged`,
208 `4: sync remote: files: "dir" (group): unchanged`,
209 `4: sync remote: files: "dir/file" (group): unchanged`,
215 "invalid File: user",
217 Files: map[string]*safcm.File{
220 Mode: fs.ModeDir | 0700,
235 fmt.Errorf("\".\": cannot set both User (\"user\") and Uid (1)"),
238 "invalid File: group",
240 Files: map[string]*safcm.File{
243 Mode: fs.ModeDir | 0700,
258 fmt.Errorf("\".\": cannot set both Group (\"group\") and Gid (1)"),
262 // We use relative paths for most tests because we
263 // don't want to modify the running system. Use this
264 // test (and the one below for triggers) as a basic
265 // check that absolute paths work.
266 "absolute paths: no change",
268 Files: map[string]*safcm.File{
271 Mode: fs.ModeDir | 0755,
280 Mode: fs.ModeDir | 0755,
289 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
298 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
314 `4: sync remote: files: "/" (group): unchanged`,
315 `4: sync remote: files: "/etc" (group): unchanged`,
316 `4: sync remote: files: "/tmp" (group): unchanged`,
317 `4: sync remote: files: "/var/tmp" (group): unchanged`,
323 "triggers: no change",
325 Files: map[string]*safcm.File{
328 Mode: fs.ModeDir | 0700,
332 TriggerCommands: []string{
338 Mode: fs.ModeDir | 0755,
342 TriggerCommands: []string{
351 Data: []byte("content\n"),
353 TriggerCommands: []string{
354 "echo trigger dir/file",
360 ft.CreateDirectory("dir", 0755)
361 ft.CreateFile("dir/file", "content\n", 0644)
368 Mode: fs.ModeDir | 0755,
373 Data: []byte("content\n"),
378 `4: sync remote: files: "." (group): unchanged`,
379 `4: sync remote: files: "dir" (group): unchanged`,
380 `4: sync remote: files: "dir/file" (group): unchanged`,
386 "triggers: change root",
388 Files: map[string]*safcm.File{
391 Mode: fs.ModeDir | 0700,
395 TriggerCommands: []string{
401 Mode: fs.ModeDir | 0755,
405 TriggerCommands: []string{
414 Data: []byte("content\n"),
416 TriggerCommands: []string{
417 "echo trigger dir/file",
423 err = os.Chmod(".", 0750)
427 ft.CreateDirectory("dir", 0755)
428 ft.CreateFile("dir/file", "content\n", 0644)
437 Mode: fs.ModeDir | 0755,
442 Data: []byte("content\n"),
446 FileChanges: []safcm.FileChange{
449 Old: safcm.FileChangeInfo{
450 Mode: fs.ModeDir | 0750,
456 New: safcm.FileChangeInfo{
457 Mode: fs.ModeDir | 0700,
467 `4: sync remote: files: "." (group): permission differs drwxr-x--- -> drwx------`,
468 `3: sync remote: files: "." (group): updating`,
469 `4: sync remote: files: "." (group): chmodding drwx------`,
470 `3: sync remote: files: ".": queuing trigger on "."`,
471 `4: sync remote: files: "dir" (group): unchanged`,
472 `4: sync remote: files: "dir/file" (group): unchanged`,
478 "triggers: change middle",
480 Files: map[string]*safcm.File{
483 Mode: fs.ModeDir | 0700,
487 TriggerCommands: []string{
493 Mode: fs.ModeDir | 0755,
497 TriggerCommands: []string{
506 Data: []byte("content\n"),
508 TriggerCommands: []string{
509 "echo trigger dir/file",
515 ft.CreateDirectory("dir", 0750)
516 ft.CreateFile("dir/file", "content\n", 0644)
526 Mode: fs.ModeDir | 0755,
531 Data: []byte("content\n"),
535 FileChanges: []safcm.FileChange{
538 Old: safcm.FileChangeInfo{
539 Mode: fs.ModeDir | 0750,
545 New: safcm.FileChangeInfo{
546 Mode: fs.ModeDir | 0755,
556 `4: sync remote: files: "." (group): unchanged`,
557 `4: sync remote: files: "dir" (group): permission differs drwxr-x--- -> drwxr-xr-x`,
558 `3: sync remote: files: "dir" (group): updating`,
559 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
560 `3: sync remote: files: "dir": queuing trigger on "."`,
561 `3: sync remote: files: "dir": queuing trigger on "dir"`,
562 `4: sync remote: files: "dir/file" (group): unchanged`,
568 "triggers: change leaf",
570 Files: map[string]*safcm.File{
573 Mode: fs.ModeDir | 0700,
577 TriggerCommands: []string{
583 Mode: fs.ModeDir | 0755,
587 TriggerCommands: []string{
596 Data: []byte("content\n"),
598 TriggerCommands: []string{
599 "echo trigger dir/file",
605 ft.CreateDirectory("dir", 0755)
616 Mode: fs.ModeDir | 0755,
621 Data: []byte("content\n"),
625 FileChanges: []safcm.FileChange{
629 New: safcm.FileChangeInfo{
640 `4: sync remote: files: "." (group): unchanged`,
641 `4: sync remote: files: "dir" (group): unchanged`,
642 `4: sync remote: files: "dir/file" (group): will create`,
643 `3: sync remote: files: "dir/file" (group): creating`,
644 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
645 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
646 `3: sync remote: files: "dir/file": queuing trigger on "."`,
647 `3: sync remote: files: "dir/file": queuing trigger on "dir"`,
648 `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
654 "triggers: multiple changes",
656 Files: map[string]*safcm.File{
659 Mode: fs.ModeDir | 0700,
663 TriggerCommands: []string{
669 Mode: fs.ModeDir | 0755,
673 TriggerCommands: []string{
682 Data: []byte("content\n"),
684 TriggerCommands: []string{
685 "echo trigger dir/file",
700 Mode: fs.ModeDir | 0755,
705 Data: []byte("content\n"),
709 FileChanges: []safcm.FileChange{
713 New: safcm.FileChangeInfo{
714 Mode: fs.ModeDir | 0755,
724 New: safcm.FileChangeInfo{
735 `4: sync remote: files: "." (group): unchanged`,
736 `4: sync remote: files: "dir" (group): will create`,
737 `3: sync remote: files: "dir" (group): creating`,
738 `4: sync remote: files: "dir" (group): creating directory`,
739 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
740 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
741 `3: sync remote: files: "dir": queuing trigger on "."`,
742 `3: sync remote: files: "dir": queuing trigger on "dir"`,
743 `4: sync remote: files: "dir/file" (group): will create`,
744 `3: sync remote: files: "dir/file" (group): creating`,
745 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
746 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
747 `4: sync remote: files: "dir/file": skipping trigger on ".", already active`,
748 `4: sync remote: files: "dir/file": skipping trigger on "dir", already active`,
749 `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
755 "triggers: absolute paths",
757 Files: map[string]*safcm.File{
760 Mode: fs.ModeDir | 0755,
766 TriggerCommands: []string{
772 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
778 TriggerCommands: []string{
783 Path: tmpTestFilePath,
788 TriggerCommands: []string{
789 "echo trigger /tmp/file",
795 // This is slightly racy but the file name
796 // should be rare enough that this isn't an
798 _, err := os.Stat(tmpTestFilePath)
800 t.Fatalf("%q exists, aborting",
807 // Don't use variable for more robust test
808 "/tmp/safcm-sync-files-test-file",
814 FileChanges: []safcm.FileChange{
816 Path: "/tmp/safcm-sync-files-test-file",
818 New: safcm.FileChangeInfo{
829 `4: sync remote: files: "/" (group): unchanged`,
830 `4: sync remote: files: "/tmp" (group): unchanged`,
831 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): will create`,
832 `3: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating`,
833 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating temporary file "/tmp/.safcm-sync-files-test-file*"`,
834 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): renaming "/tmp/.safcm-sync-files-test-fileRND"`,
835 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/"`,
836 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp"`,
837 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp/safcm-sync-files-test-file"`,
843 for _, tc := range tests {
844 t.Run(tc.name, func(t *testing.T) {
845 // Create separate test directory for each test case
846 path := filepath.Join(cwd, "testdata", "files-"+tc.name)
847 err = os.Mkdir(path, 0700)
856 if tc.prepare != nil {
860 s, res := prepareSync(tc.req, &testRunner{
866 // Ugly but the simplest way to compare errors (including nil)
867 if fmt.Sprintf("%s", err) != fmt.Sprintf("%s", tc.expErr) {
868 t.Errorf("err = %#v, want %#v",
872 // Remove random file names from result
873 for i, x := range dbg {
874 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
876 if !reflect.DeepEqual(tc.expDbg, dbg) {
878 cmp.Diff(tc.expDbg, dbg))
881 files, err := ft.WalkDir(path)
885 if !reflect.DeepEqual(tc.expFiles, files) {
886 t.Errorf("files: %s",
887 cmp.Diff(tc.expFiles, files))
890 if !reflect.DeepEqual(tc.expResp, s.resp) {
892 cmp.Diff(tc.expResp, s.resp))
894 if !reflect.DeepEqual(tc.triggers, s.triggers) {
895 t.Errorf("triggers: %s",
896 cmp.Diff(tc.triggers, s.triggers))
901 os.Remove(tmpTestFilePath)
903 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
910 func TestSyncFile(t *testing.T) {
911 cwd, err := os.Getwd()
917 err = os.RemoveAll("testdata")
921 err = os.Mkdir("testdata", 0700)
928 Mode: fs.ModeDir | 0700,
930 user, uid, group, gid := ft.CurrentUserAndGroup()
939 expResp safcm.MsgSyncResp
944 // NOTE: Also update MsgSyncResp in safcm test cases when
945 // changing anything here!
947 // TODO: Add tests for chown and run them only as root
959 Data: []byte("content\n"),
969 Data: []byte("content\n"),
973 FileChanges: []safcm.FileChange{
977 New: safcm.FileChangeInfo{
988 `4: sync remote: files: "file" (group): will create`,
989 `3: sync remote: files: "file" (group): creating`,
990 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
991 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
996 "file: create (dry-run)",
1005 Data: []byte("content\n"),
1012 FileChanges: []safcm.FileChange{
1016 New: safcm.FileChangeInfo{
1027 `4: sync remote: files: "file" (group): will create`,
1028 `3: sync remote: files: "file" (group): creating`,
1029 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
1042 Data: []byte("content\n"),
1046 ft.CreateFile("file", "content\n", 0644)
1054 Data: []byte("content\n"),
1057 safcm.MsgSyncResp{},
1059 `4: sync remote: files: "file" (group): unchanged`,
1065 "file: unchanged (non-default user-group)",
1074 Data: []byte("content\n"),
1078 ft.CreateFile("file", "content\n", 0644)
1086 Data: []byte("content\n"),
1089 safcm.MsgSyncResp{},
1091 `4: sync remote: files: "file" (group): unchanged`,
1101 Mode: 0755 | fs.ModeSetuid,
1104 Data: []byte("content\n"),
1108 ft.CreateFile("file", "content\n", 0755)
1115 Mode: 0755 | fs.ModeSetuid,
1116 Data: []byte("content\n"),
1120 FileChanges: []safcm.FileChange{
1123 Old: safcm.FileChangeInfo{
1130 New: safcm.FileChangeInfo{
1131 Mode: 0755 | fs.ModeSetuid,
1141 `4: sync remote: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
1142 `3: sync remote: files: "file" (group): updating`,
1143 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1144 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1157 Data: []byte("content\n"),
1161 ft.CreateFile("file", "old content\n", 0644)
1169 Data: []byte("content\n"),
1173 FileChanges: []safcm.FileChange{
1176 Old: safcm.FileChangeInfo{
1183 New: safcm.FileChangeInfo{
1190 DataDiff: `@@ -1,2 +1,2 @@
1199 `4: sync remote: files: "file" (group): content differs`,
1200 `3: sync remote: files: "file" (group): updating`,
1201 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1202 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1214 Mode: fs.ModeSymlink | 0777,
1217 Data: []byte("target"),
1226 Mode: fs.ModeSymlink | 0777,
1227 Data: []byte("target"),
1231 FileChanges: []safcm.FileChange{
1235 New: safcm.FileChangeInfo{
1236 Mode: fs.ModeSymlink | 0777,
1246 `4: sync remote: files: "link" (group): will create`,
1247 `3: sync remote: files: "link" (group): creating`,
1248 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1249 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1254 "symlink: create (conflict)",
1258 Mode: fs.ModeSymlink | 0777,
1261 Data: []byte("target"),
1265 ft.CreateFile(".link8717895732742165505", "", 0600)
1271 Path: ".link8717895732742165505",
1277 Mode: fs.ModeSymlink | 0777,
1278 Data: []byte("target"),
1282 FileChanges: []safcm.FileChange{
1286 New: safcm.FileChangeInfo{
1287 Mode: fs.ModeSymlink | 0777,
1297 `4: sync remote: files: "link" (group): will create`,
1298 `3: sync remote: files: "link" (group): creating`,
1299 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1300 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1301 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1306 "symlink: create (dry-run)",
1312 Mode: fs.ModeSymlink | 0777,
1315 Data: []byte("target"),
1322 FileChanges: []safcm.FileChange{
1326 New: safcm.FileChangeInfo{
1327 Mode: fs.ModeSymlink | 0777,
1337 `4: sync remote: files: "link" (group): will create`,
1338 `3: sync remote: files: "link" (group): creating`,
1339 `4: sync remote: files: "link" (group): dry-run, skipping changes`,
1345 "symlink: unchanged",
1349 Mode: fs.ModeSymlink | 0777,
1352 Data: []byte("target"),
1356 ft.CreateSymlink("link", "target")
1363 Mode: fs.ModeSymlink | 0777,
1364 Data: []byte("target"),
1367 safcm.MsgSyncResp{},
1369 `4: sync remote: files: "link" (group): unchanged`,
1379 Mode: fs.ModeSymlink | 0777,
1382 Data: []byte("target"),
1386 ft.CreateSymlink("link", "old-target")
1393 Mode: fs.ModeSymlink | 0777,
1394 Data: []byte("target"),
1398 FileChanges: []safcm.FileChange{
1401 Old: safcm.FileChangeInfo{
1402 Mode: fs.ModeSymlink | 0777,
1408 New: safcm.FileChangeInfo{
1409 Mode: fs.ModeSymlink | 0777,
1415 DataDiff: `@@ -1 +1 @@
1423 `4: sync remote: files: "link" (group): content differs`,
1424 `3: sync remote: files: "link" (group): updating`,
1425 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1426 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1434 "directory: create",
1438 Mode: fs.ModeDir | 0705,
1449 Mode: fs.ModeDir | 0705,
1453 FileChanges: []safcm.FileChange{
1457 New: safcm.FileChangeInfo{
1458 Mode: fs.ModeDir | 0705,
1468 `4: sync remote: files: "dir" (group): will create`,
1469 `3: sync remote: files: "dir" (group): creating`,
1470 `4: sync remote: files: "dir" (group): creating directory`,
1471 `4: sync remote: files: "dir" (group): chmodding drwx---r-x`,
1472 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
1477 "directory: create (dry-run)",
1483 Mode: fs.ModeDir | 0644,
1492 FileChanges: []safcm.FileChange{
1496 New: safcm.FileChangeInfo{
1497 Mode: fs.ModeDir | 0644,
1507 `4: sync remote: files: "dir" (group): will create`,
1508 `3: sync remote: files: "dir" (group): creating`,
1509 `4: sync remote: files: "dir" (group): dry-run, skipping changes`,
1515 "directory: unchanged",
1519 Mode: fs.ModeDir | 0755,
1525 ft.CreateDirectory("dir", 0755)
1532 Mode: fs.ModeDir | 0755,
1535 safcm.MsgSyncResp{},
1537 `4: sync remote: files: "dir" (group): unchanged`,
1543 "directory: permission",
1547 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1553 ft.CreateDirectory("dir", 0500|fs.ModeSticky)
1560 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1564 FileChanges: []safcm.FileChange{
1567 Old: safcm.FileChangeInfo{
1568 Mode: fs.ModeDir | 0500 | fs.ModeSticky,
1574 New: safcm.FileChangeInfo{
1575 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1585 `4: sync remote: files: "dir" (group): permission differs dtr-x------ -> dgrwxr-xr-x`,
1586 `3: sync remote: files: "dir" (group): updating`,
1587 `4: sync remote: files: "dir" (group): chmodding dgrwxr-xr-x`,
1595 "change: file to directory",
1599 Mode: fs.ModeDir | 0751,
1605 ft.CreateFile("path", "content\n", 0644)
1612 Mode: fs.ModeDir | 0751,
1616 FileChanges: []safcm.FileChange{
1619 Old: safcm.FileChangeInfo{
1626 New: safcm.FileChangeInfo{
1627 Mode: fs.ModeDir | 0751,
1633 DataDiff: `@@ -1,2 +1 @@
1641 `4: sync remote: files: "path" (group): type differs ---------- -> d---------`,
1642 `3: sync remote: files: "path" (group): updating`,
1643 `4: sync remote: files: "path" (group): removing (due to type change)`,
1644 `4: sync remote: files: "path" (group): creating directory`,
1645 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1646 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1652 "change: file to symlink",
1656 Mode: fs.ModeSymlink | 0777,
1660 Data: []byte("target"),
1663 ft.CreateFile("path", "content\n", 0644)
1670 Mode: fs.ModeSymlink | 0777,
1671 Data: []byte("target"),
1675 FileChanges: []safcm.FileChange{
1678 Old: safcm.FileChangeInfo{
1685 New: safcm.FileChangeInfo{
1686 Mode: fs.ModeSymlink | 0777,
1692 DataDiff: `@@ -1,2 +1 @@
1701 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
1702 `3: sync remote: files: "path" (group): updating`,
1703 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1704 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1710 "change: symlink to file",
1718 Data: []byte("content\n"),
1721 ft.CreateSymlink("path", "target")
1729 Data: []byte("content\n"),
1733 FileChanges: []safcm.FileChange{
1736 Old: safcm.FileChangeInfo{
1737 Mode: fs.ModeSymlink | 0777,
1743 New: safcm.FileChangeInfo{
1750 DataDiff: `@@ -1 +1,2 @@
1759 `4: sync remote: files: "path" (group): type differs L--------- -> ----------`,
1760 `3: sync remote: files: "path" (group): updating`,
1761 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1762 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1768 "change: symlink to directory",
1772 Mode: fs.ModeDir | 0751,
1778 ft.CreateSymlink("path", "target")
1785 Mode: fs.ModeDir | 0751,
1789 FileChanges: []safcm.FileChange{
1792 Old: safcm.FileChangeInfo{
1793 Mode: fs.ModeSymlink | 0777,
1799 New: safcm.FileChangeInfo{
1800 Mode: fs.ModeDir | 0751,
1806 DataDiff: `@@ -1 +1 @@
1814 `4: sync remote: files: "path" (group): type differs L--------- -> d---------`,
1815 `3: sync remote: files: "path" (group): updating`,
1816 `4: sync remote: files: "path" (group): removing (due to type change)`,
1817 `4: sync remote: files: "path" (group): creating directory`,
1818 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1819 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1825 "change: directory to file",
1833 Data: []byte("content\n"),
1836 ft.CreateDirectory("path", 0777)
1844 Data: []byte("content\n"),
1848 FileChanges: []safcm.FileChange{
1851 Old: safcm.FileChangeInfo{
1852 Mode: fs.ModeDir | 0777,
1858 New: safcm.FileChangeInfo{
1869 `4: sync remote: files: "path" (group): type differs d--------- -> ----------`,
1870 `3: sync remote: files: "path" (group): updating`,
1871 `4: sync remote: files: "path" (group): removing (due to type change)`,
1872 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1873 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1879 "change: directory to symlink",
1883 Mode: fs.ModeSymlink | 0777,
1887 Data: []byte("target"),
1890 ft.CreateDirectory("path", 0777)
1897 Mode: fs.ModeSymlink | 0777,
1898 Data: []byte("target"),
1902 FileChanges: []safcm.FileChange{
1905 Old: safcm.FileChangeInfo{
1906 Mode: fs.ModeDir | 0777,
1912 New: safcm.FileChangeInfo{
1913 Mode: fs.ModeSymlink | 0777,
1923 `4: sync remote: files: "path" (group): type differs d--------- -> L---------`,
1924 `3: sync remote: files: "path" (group): updating`,
1925 `4: sync remote: files: "path" (group): removing (due to type change)`,
1926 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1927 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1933 "change: other to file",
1941 Data: []byte("content\n"),
1944 ft.CreateFifo("path", 0666)
1952 Data: []byte("content\n"),
1956 FileChanges: []safcm.FileChange{
1959 Old: safcm.FileChangeInfo{
1960 Mode: fs.ModeNamedPipe | 0666,
1966 New: safcm.FileChangeInfo{
1977 `4: sync remote: files: "path" (group): type differs p--------- -> ----------`,
1978 `3: sync remote: files: "path" (group): updating`,
1979 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1980 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1986 "change: other to symlink",
1990 Mode: fs.ModeSymlink | 0777,
1994 Data: []byte("target"),
1997 ft.CreateFifo("path", 0666)
2004 Mode: fs.ModeSymlink | 0777,
2005 Data: []byte("target"),
2009 FileChanges: []safcm.FileChange{
2012 Old: safcm.FileChangeInfo{
2013 Mode: fs.ModeNamedPipe | 0666,
2019 New: safcm.FileChangeInfo{
2020 Mode: fs.ModeSymlink | 0777,
2030 `4: sync remote: files: "path" (group): type differs p--------- -> L---------`,
2031 `3: sync remote: files: "path" (group): updating`,
2032 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2033 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2039 "change: other to directory",
2043 Mode: fs.ModeDir | 0751,
2049 ft.CreateFifo("path", 0666)
2056 Mode: fs.ModeDir | 0751,
2060 FileChanges: []safcm.FileChange{
2063 Old: safcm.FileChangeInfo{
2064 Mode: fs.ModeNamedPipe | 0666,
2070 New: safcm.FileChangeInfo{
2071 Mode: fs.ModeDir | 0751,
2081 `4: sync remote: files: "path" (group): type differs p--------- -> d---------`,
2082 `3: sync remote: files: "path" (group): updating`,
2083 `4: sync remote: files: "path" (group): removing (due to type change)`,
2084 `4: sync remote: files: "path" (group): creating directory`,
2085 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
2086 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
2092 "change: file to symlink (same content)",
2096 Mode: fs.ModeSymlink | 0777,
2100 Data: []byte("target"),
2103 ft.CreateFile("path", "target", 0644)
2110 Mode: fs.ModeSymlink | 0777,
2111 Data: []byte("target"),
2115 FileChanges: []safcm.FileChange{
2118 Old: safcm.FileChangeInfo{
2125 New: safcm.FileChangeInfo{
2126 Mode: fs.ModeSymlink | 0777,
2136 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
2137 `3: sync remote: files: "path" (group): updating`,
2138 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2139 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2166 ft.CreateFile("file", `this
2186 FileChanges: []safcm.FileChange{
2189 Old: safcm.FileChangeInfo{
2196 New: safcm.FileChangeInfo{
2203 DataDiff: `@@ -1,5 +1,7 @@
2217 `4: sync remote: files: "file" (group): content differs`,
2218 `3: sync remote: files: "file" (group): updating`,
2219 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2225 "diff: binary both",
2234 Data: []byte("\x00\x01\x02\x03"),
2238 ft.CreateFile("file", "\x00\x01\x02", 0644)
2246 Data: []byte("\x00\x01\x02"),
2250 FileChanges: []safcm.FileChange{
2253 Old: safcm.FileChangeInfo{
2260 New: safcm.FileChangeInfo{
2267 DataDiff: "Binary files differ, cannot show diff",
2272 `4: sync remote: files: "file" (group): content differs`,
2273 `3: sync remote: files: "file" (group): updating`,
2274 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2289 Data: []byte("content\n"),
2293 ft.CreateFile("file", "\x00\x01\x02", 0644)
2301 Data: []byte("\x00\x01\x02"),
2305 FileChanges: []safcm.FileChange{
2308 Old: safcm.FileChangeInfo{
2315 New: safcm.FileChangeInfo{
2322 DataDiff: `@@ -1,2 +1,2 @@
2331 `4: sync remote: files: "file" (group): content differs`,
2332 `3: sync remote: files: "file" (group): updating`,
2333 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2348 Data: []byte("\x00\x01\x02\x03"),
2352 ft.CreateFile("file", "content\n", 0644)
2360 Data: []byte("content\n"),
2364 FileChanges: []safcm.FileChange{
2367 Old: safcm.FileChangeInfo{
2374 New: safcm.FileChangeInfo{
2381 DataDiff: `@@ -1,2 +1,2 @@
2390 `4: sync remote: files: "file" (group): content differs`,
2391 `3: sync remote: files: "file" (group): updating`,
2392 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2398 for _, tc := range tests {
2399 t.Run(tc.name, func(t *testing.T) {
2400 // Create separate test directory for each test case
2401 path := filepath.Join(cwd, "testdata", "file-"+tc.name)
2402 err = os.Mkdir(path, 0700)
2406 err = os.Chdir(path)
2411 if tc.prepare != nil {
2415 s, res := prepareSync(tc.req, &testRunner{
2420 // Deterministic temporary symlink names
2424 err := s.syncFile(tc.file, &changed)
2425 // Ugly but the simplest way to compare errors (including nil)
2426 if fmt.Sprintf("%s", err) != fmt.Sprintf("%s", tc.expErr) {
2427 t.Errorf("err = %#v, want %#v",
2431 // Remove random file names from result
2432 for i, x := range dbg {
2433 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
2435 if !reflect.DeepEqual(tc.expDbg, dbg) {
2437 cmp.Diff(tc.expDbg, dbg))
2440 files, err := ft.WalkDir(path)
2444 if !reflect.DeepEqual(tc.expFiles, files) {
2445 t.Errorf("files: %s",
2446 cmp.Diff(tc.expFiles, files))
2449 if tc.expChanged != changed {
2450 t.Errorf("changed = %#v, want %#v",
2451 changed, tc.expChanged)
2453 if !reflect.DeepEqual(tc.expResp, s.resp) {
2454 t.Errorf("resp: %s",
2455 cmp.Diff(tc.expResp, s.resp))
2461 err = os.RemoveAll(filepath.Join(cwd, "testdata"))