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 skipUnlessCiRun := len(os.Getenv("SAFCM_CI_RUN")) == 0
58 tmpTestFilePath := "/tmp/safcm-sync-files-test-file"
67 expResp safcm.MsgSyncResp
72 // NOTE: Also update MsgSyncResp in safcm test cases when
73 // changing anything here!
75 // See TestSyncFile() for most file related tests. This
76 // function only tests the overall results and triggers.
82 Files: map[string]*safcm.File{
85 Mode: fs.ModeDir | 0700,
92 Mode: fs.ModeDir | 0755,
102 Data: []byte("content\n"),
113 Mode: fs.ModeDir | 0755,
118 Data: []byte("content\n"),
122 FileChanges: []safcm.FileChange{
126 New: safcm.FileChangeInfo{
127 Mode: fs.ModeDir | 0755,
137 New: safcm.FileChangeInfo{
148 `4: sync remote: files: "." (group): unchanged`,
149 `4: sync remote: files: "dir" (group): will create`,
150 `3: sync remote: files: "dir" (group): creating`,
151 `4: sync remote: files: "dir" (group): creating directory`,
152 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
153 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
154 `4: sync remote: files: "dir/file" (group): will create`,
155 `3: sync remote: files: "dir/file" (group): creating`,
156 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
157 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
166 Files: map[string]*safcm.File{
169 Mode: fs.ModeDir | 0700,
176 Mode: fs.ModeDir | 0755,
186 Data: []byte("content\n"),
192 ft.CreateDirectory("dir", 0755)
193 ft.CreateFile("dir/file", "content\n", 0644)
200 Mode: fs.ModeDir | 0755,
205 Data: []byte("content\n"),
210 `4: sync remote: files: "." (group): unchanged`,
211 `4: sync remote: files: "dir" (group): unchanged`,
212 `4: sync remote: files: "dir/file" (group): unchanged`,
218 "invalid File: user",
221 Files: map[string]*safcm.File{
224 Mode: fs.ModeDir | 0700,
239 fmt.Errorf("\".\": cannot set both User (\"user\") and Uid (1)"),
242 "invalid File: group",
245 Files: map[string]*safcm.File{
248 Mode: fs.ModeDir | 0700,
263 fmt.Errorf("\".\": cannot set both Group (\"group\") and Gid (1)"),
267 // We use relative paths for most tests because we
268 // don't want to modify the running system. Use this
269 // test (and the one below for triggers) as a basic
270 // check that absolute paths work.
272 // Use numeric IDs as not all systems use root/root;
273 // for example BSDs use root/wheel.
274 "absolute paths: no change",
277 Files: map[string]*safcm.File{
280 Mode: fs.ModeDir | 0755,
287 Mode: fs.ModeDir | 0755,
294 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
308 `4: sync remote: files: "/" (group): unchanged`,
309 `4: sync remote: files: "/etc" (group): unchanged`,
310 `4: sync remote: files: "/tmp" (group): unchanged`,
316 "triggers: no change",
319 Files: map[string]*safcm.File{
322 Mode: fs.ModeDir | 0700,
326 TriggerCommands: []string{
332 Mode: fs.ModeDir | 0755,
336 TriggerCommands: []string{
345 Data: []byte("content\n"),
347 TriggerCommands: []string{
348 "echo trigger dir/file",
354 ft.CreateDirectory("dir", 0755)
355 ft.CreateFile("dir/file", "content\n", 0644)
362 Mode: fs.ModeDir | 0755,
367 Data: []byte("content\n"),
372 `4: sync remote: files: "." (group): unchanged`,
373 `4: sync remote: files: "dir" (group): unchanged`,
374 `4: sync remote: files: "dir/file" (group): unchanged`,
380 "triggers: change root",
383 Files: map[string]*safcm.File{
386 Mode: fs.ModeDir | 0700,
390 TriggerCommands: []string{
396 Mode: fs.ModeDir | 0755,
400 TriggerCommands: []string{
409 Data: []byte("content\n"),
411 TriggerCommands: []string{
412 "echo trigger dir/file",
418 ft.CreateDirectoryExists(".", 0750)
419 ft.CreateDirectory("dir", 0755)
420 ft.CreateFile("dir/file", "content\n", 0644)
429 Mode: fs.ModeDir | 0755,
434 Data: []byte("content\n"),
438 FileChanges: []safcm.FileChange{
441 Old: safcm.FileChangeInfo{
442 Mode: fs.ModeDir | 0750,
448 New: safcm.FileChangeInfo{
449 Mode: fs.ModeDir | 0700,
459 `4: sync remote: files: "." (group): permission differs drwxr-x--- -> drwx------`,
460 `3: sync remote: files: "." (group): updating`,
461 `4: sync remote: files: "." (group): chmodding drwx------`,
462 `3: sync remote: files: ".": queuing trigger on "."`,
463 `4: sync remote: files: "dir" (group): unchanged`,
464 `4: sync remote: files: "dir/file" (group): unchanged`,
470 "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",
564 Files: map[string]*safcm.File{
567 Mode: fs.ModeDir | 0700,
571 TriggerCommands: []string{
577 Mode: fs.ModeDir | 0755,
581 TriggerCommands: []string{
590 Data: []byte("content\n"),
592 TriggerCommands: []string{
593 "echo trigger dir/file",
599 ft.CreateDirectory("dir", 0755)
610 Mode: fs.ModeDir | 0755,
615 Data: []byte("content\n"),
619 FileChanges: []safcm.FileChange{
623 New: safcm.FileChangeInfo{
634 `4: sync remote: files: "." (group): unchanged`,
635 `4: sync remote: files: "dir" (group): unchanged`,
636 `4: sync remote: files: "dir/file" (group): will create`,
637 `3: sync remote: files: "dir/file" (group): creating`,
638 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
639 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
640 `3: sync remote: files: "dir/file": queuing trigger on "."`,
641 `3: sync remote: files: "dir/file": queuing trigger on "dir"`,
642 `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
648 "triggers: multiple changes",
651 Files: map[string]*safcm.File{
654 Mode: fs.ModeDir | 0700,
658 TriggerCommands: []string{
664 Mode: fs.ModeDir | 0755,
668 TriggerCommands: []string{
677 Data: []byte("content\n"),
679 TriggerCommands: []string{
680 "echo trigger dir/file",
695 Mode: fs.ModeDir | 0755,
700 Data: []byte("content\n"),
704 FileChanges: []safcm.FileChange{
708 New: safcm.FileChangeInfo{
709 Mode: fs.ModeDir | 0755,
719 New: safcm.FileChangeInfo{
730 `4: sync remote: files: "." (group): unchanged`,
731 `4: sync remote: files: "dir" (group): will create`,
732 `3: sync remote: files: "dir" (group): creating`,
733 `4: sync remote: files: "dir" (group): creating directory`,
734 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
735 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
736 `3: sync remote: files: "dir": queuing trigger on "."`,
737 `3: sync remote: files: "dir": queuing trigger on "dir"`,
738 `4: sync remote: files: "dir/file" (group): will create`,
739 `3: sync remote: files: "dir/file" (group): creating`,
740 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
741 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
742 `4: sync remote: files: "dir/file": skipping trigger on ".", already active`,
743 `4: sync remote: files: "dir/file": skipping trigger on "dir", already active`,
744 `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
750 "triggers: absolute paths",
753 Files: map[string]*safcm.File{
756 Mode: fs.ModeDir | 0755,
760 TriggerCommands: []string{
766 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
770 TriggerCommands: []string{
775 Path: tmpTestFilePath,
780 TriggerCommands: []string{
781 "echo trigger /tmp/file",
790 // Don't use variable for more robust test
791 "/tmp/safcm-sync-files-test-file",
797 FileChanges: []safcm.FileChange{
799 Path: "/tmp/safcm-sync-files-test-file",
801 New: safcm.FileChangeInfo{
812 `4: sync remote: files: "/" (group): unchanged`,
813 `4: sync remote: files: "/tmp" (group): unchanged`,
814 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): will create`,
815 `3: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating`,
816 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating temporary file "/tmp/.safcm-sync-files-test-file*"`,
817 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): renaming "/tmp/.safcm-sync-files-test-fileRND"`,
818 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/"`,
819 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp"`,
820 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp/safcm-sync-files-test-file"`,
826 for _, tc := range tests {
827 t.Run(tc.name, func(t *testing.T) {
832 // Create separate test directory for each test case
833 path := filepath.Join(cwd, "testdata", "files-"+tc.name)
834 err := os.Mkdir(path, 0700)
843 if tc.prepare != nil {
847 s, res := prepareSync(tc.req, &testRunner{
850 err = s.setDefaults()
856 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
858 // Remove random file names from result
859 for i, x := range dbg {
860 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
862 testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
864 files, err := ft.WalkDir(path)
868 testutil.AssertEqual(t, "files", files, tc.expFiles)
870 testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
871 testutil.AssertEqual(t, "triggers",
872 s.triggers, tc.expTriggers)
876 os.Remove(tmpTestFilePath)
878 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
885 func TestSyncFile(t *testing.T) {
886 cwd, err := os.Getwd()
892 err = os.RemoveAll("testdata")
896 err = os.Mkdir("testdata", 0700)
903 Mode: fs.ModeDir | 0700,
905 user, uid, group, gid := ft.CurrentUserAndGroup()
914 expResp safcm.MsgSyncResp
919 // NOTE: Also update MsgSyncResp in safcm test cases when
920 // changing anything here!
922 // TODO: Add tests for chown and run them only as root
934 Data: []byte("content\n"),
944 Data: []byte("content\n"),
948 FileChanges: []safcm.FileChange{
952 New: safcm.FileChangeInfo{
963 `4: sync remote: files: "file" (group): will create`,
964 `3: sync remote: files: "file" (group): creating`,
965 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
966 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
971 "file: create (dry-run)",
980 Data: []byte("content\n"),
987 FileChanges: []safcm.FileChange{
991 New: safcm.FileChangeInfo{
1002 `4: sync remote: files: "file" (group): will create`,
1003 `3: sync remote: files: "file" (group): creating`,
1004 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
1017 Data: []byte("content\n"),
1021 ft.CreateFile("file", "content\n", 0644)
1029 Data: []byte("content\n"),
1032 safcm.MsgSyncResp{},
1034 `4: sync remote: files: "file" (group): unchanged`,
1040 "file: unchanged (non-default user-group)",
1049 Data: []byte("content\n"),
1053 ft.CreateFile("file", "content\n", 0644)
1061 Data: []byte("content\n"),
1064 safcm.MsgSyncResp{},
1066 `4: sync remote: files: "file" (group): unchanged`,
1076 Mode: 0755 | fs.ModeSetuid,
1079 Data: []byte("content\n"),
1083 ft.CreateFile("file", "content\n", 0755)
1090 Mode: 0755 | fs.ModeSetuid,
1091 Data: []byte("content\n"),
1095 FileChanges: []safcm.FileChange{
1098 Old: safcm.FileChangeInfo{
1105 New: safcm.FileChangeInfo{
1106 Mode: 0755 | fs.ModeSetuid,
1116 `4: sync remote: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
1117 `3: sync remote: files: "file" (group): updating`,
1118 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1119 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1132 Data: []byte("content\n"),
1136 ft.CreateFile("file", "old content\n", 0644)
1144 Data: []byte("content\n"),
1148 FileChanges: []safcm.FileChange{
1151 Old: safcm.FileChangeInfo{
1158 New: safcm.FileChangeInfo{
1165 DataDiff: `@@ -1,2 +1,2 @@
1174 `4: sync remote: files: "file" (group): content differs`,
1175 `3: sync remote: files: "file" (group): updating`,
1176 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1177 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1189 Mode: fs.ModeSymlink | 0777,
1192 Data: []byte("target"),
1201 Mode: fs.ModeSymlink | 0777,
1202 Data: []byte("target"),
1206 FileChanges: []safcm.FileChange{
1210 New: safcm.FileChangeInfo{
1211 Mode: fs.ModeSymlink | 0777,
1221 `4: sync remote: files: "link" (group): will create`,
1222 `3: sync remote: files: "link" (group): creating`,
1223 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1224 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1229 "symlink: create (conflict)",
1233 Mode: fs.ModeSymlink | 0777,
1236 Data: []byte("target"),
1240 ft.CreateFile(".link8717895732742165505", "", 0600)
1246 Path: ".link8717895732742165505",
1252 Mode: fs.ModeSymlink | 0777,
1253 Data: []byte("target"),
1257 FileChanges: []safcm.FileChange{
1261 New: safcm.FileChangeInfo{
1262 Mode: fs.ModeSymlink | 0777,
1272 `4: sync remote: files: "link" (group): will create`,
1273 `3: sync remote: files: "link" (group): creating`,
1274 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1275 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1276 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1281 "symlink: create (dry-run)",
1287 Mode: fs.ModeSymlink | 0777,
1290 Data: []byte("target"),
1297 FileChanges: []safcm.FileChange{
1301 New: safcm.FileChangeInfo{
1302 Mode: fs.ModeSymlink | 0777,
1312 `4: sync remote: files: "link" (group): will create`,
1313 `3: sync remote: files: "link" (group): creating`,
1314 `4: sync remote: files: "link" (group): dry-run, skipping changes`,
1320 "symlink: unchanged",
1324 Mode: fs.ModeSymlink | 0777,
1327 Data: []byte("target"),
1331 ft.CreateSymlink("link", "target")
1338 Mode: fs.ModeSymlink | 0777,
1339 Data: []byte("target"),
1342 safcm.MsgSyncResp{},
1344 `4: sync remote: files: "link" (group): unchanged`,
1354 Mode: fs.ModeSymlink | 0777,
1357 Data: []byte("target"),
1361 ft.CreateSymlink("link", "old-target")
1368 Mode: fs.ModeSymlink | 0777,
1369 Data: []byte("target"),
1373 FileChanges: []safcm.FileChange{
1376 Old: safcm.FileChangeInfo{
1377 Mode: fs.ModeSymlink | 0777,
1383 New: safcm.FileChangeInfo{
1384 Mode: fs.ModeSymlink | 0777,
1390 DataDiff: `@@ -1 +1 @@
1398 `4: sync remote: files: "link" (group): content differs`,
1399 `3: sync remote: files: "link" (group): updating`,
1400 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1401 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1409 "directory: create",
1413 Mode: fs.ModeDir | 0705,
1424 Mode: fs.ModeDir | 0705,
1428 FileChanges: []safcm.FileChange{
1432 New: safcm.FileChangeInfo{
1433 Mode: fs.ModeDir | 0705,
1443 `4: sync remote: files: "dir" (group): will create`,
1444 `3: sync remote: files: "dir" (group): creating`,
1445 `4: sync remote: files: "dir" (group): creating directory`,
1446 `4: sync remote: files: "dir" (group): chmodding drwx---r-x`,
1447 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
1452 "directory: create (dry-run)",
1458 Mode: fs.ModeDir | 0644,
1467 FileChanges: []safcm.FileChange{
1471 New: safcm.FileChangeInfo{
1472 Mode: fs.ModeDir | 0644,
1482 `4: sync remote: files: "dir" (group): will create`,
1483 `3: sync remote: files: "dir" (group): creating`,
1484 `4: sync remote: files: "dir" (group): dry-run, skipping changes`,
1490 "directory: unchanged",
1494 Mode: fs.ModeDir | 0755,
1500 ft.CreateDirectory("dir", 0755)
1507 Mode: fs.ModeDir | 0755,
1510 safcm.MsgSyncResp{},
1512 `4: sync remote: files: "dir" (group): unchanged`,
1518 "directory: permission",
1522 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1528 ft.CreateDirectory("dir", 0500|fs.ModeSticky)
1535 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1539 FileChanges: []safcm.FileChange{
1542 Old: safcm.FileChangeInfo{
1543 Mode: fs.ModeDir | 0500 | fs.ModeSticky,
1549 New: safcm.FileChangeInfo{
1550 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1560 `4: sync remote: files: "dir" (group): permission differs dtr-x------ -> dgrwxr-xr-x`,
1561 `3: sync remote: files: "dir" (group): updating`,
1562 `4: sync remote: files: "dir" (group): chmodding dgrwxr-xr-x`,
1570 "change: file to directory",
1574 Mode: fs.ModeDir | 0751,
1580 ft.CreateFile("path", "content\n", 0644)
1587 Mode: fs.ModeDir | 0751,
1591 FileChanges: []safcm.FileChange{
1594 Old: safcm.FileChangeInfo{
1601 New: safcm.FileChangeInfo{
1602 Mode: fs.ModeDir | 0751,
1608 DataDiff: `@@ -1,2 +1 @@
1616 `4: sync remote: files: "path" (group): type differs ---------- -> d---------`,
1617 `3: sync remote: files: "path" (group): updating`,
1618 `4: sync remote: files: "path" (group): removing (due to type change)`,
1619 `4: sync remote: files: "path" (group): creating directory`,
1620 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1621 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1627 "change: file to symlink",
1631 Mode: fs.ModeSymlink | 0777,
1635 Data: []byte("target"),
1638 ft.CreateFile("path", "content\n", 0644)
1645 Mode: fs.ModeSymlink | 0777,
1646 Data: []byte("target"),
1650 FileChanges: []safcm.FileChange{
1653 Old: safcm.FileChangeInfo{
1660 New: safcm.FileChangeInfo{
1661 Mode: fs.ModeSymlink | 0777,
1667 DataDiff: `@@ -1,2 +1 @@
1676 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
1677 `3: sync remote: files: "path" (group): updating`,
1678 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1679 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1685 "change: symlink to file",
1693 Data: []byte("content\n"),
1696 ft.CreateSymlink("path", "target")
1704 Data: []byte("content\n"),
1708 FileChanges: []safcm.FileChange{
1711 Old: safcm.FileChangeInfo{
1712 Mode: fs.ModeSymlink | 0777,
1718 New: safcm.FileChangeInfo{
1725 DataDiff: `@@ -1 +1,2 @@
1734 `4: sync remote: files: "path" (group): type differs L--------- -> ----------`,
1735 `3: sync remote: files: "path" (group): updating`,
1736 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1737 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1743 "change: symlink to directory",
1747 Mode: fs.ModeDir | 0751,
1753 ft.CreateSymlink("path", "target")
1760 Mode: fs.ModeDir | 0751,
1764 FileChanges: []safcm.FileChange{
1767 Old: safcm.FileChangeInfo{
1768 Mode: fs.ModeSymlink | 0777,
1774 New: safcm.FileChangeInfo{
1775 Mode: fs.ModeDir | 0751,
1781 DataDiff: `@@ -1 +1 @@
1789 `4: sync remote: files: "path" (group): type differs L--------- -> d---------`,
1790 `3: sync remote: files: "path" (group): updating`,
1791 `4: sync remote: files: "path" (group): removing (due to type change)`,
1792 `4: sync remote: files: "path" (group): creating directory`,
1793 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1794 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1800 "change: directory to file",
1808 Data: []byte("content\n"),
1811 ft.CreateDirectory("path", 0777)
1819 Data: []byte("content\n"),
1823 FileChanges: []safcm.FileChange{
1826 Old: safcm.FileChangeInfo{
1827 Mode: fs.ModeDir | 0777,
1833 New: safcm.FileChangeInfo{
1844 `4: sync remote: files: "path" (group): type differs d--------- -> ----------`,
1845 `3: sync remote: files: "path" (group): updating`,
1846 `4: sync remote: files: "path" (group): removing (due to type change)`,
1847 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1848 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1854 "change: directory to symlink",
1858 Mode: fs.ModeSymlink | 0777,
1862 Data: []byte("target"),
1865 ft.CreateDirectory("path", 0777)
1872 Mode: fs.ModeSymlink | 0777,
1873 Data: []byte("target"),
1877 FileChanges: []safcm.FileChange{
1880 Old: safcm.FileChangeInfo{
1881 Mode: fs.ModeDir | 0777,
1887 New: safcm.FileChangeInfo{
1888 Mode: fs.ModeSymlink | 0777,
1898 `4: sync remote: files: "path" (group): type differs d--------- -> L---------`,
1899 `3: sync remote: files: "path" (group): updating`,
1900 `4: sync remote: files: "path" (group): removing (due to type change)`,
1901 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1902 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1908 "change: other to file",
1916 Data: []byte("content\n"),
1919 ft.CreateFifo("path", 0666)
1927 Data: []byte("content\n"),
1931 FileChanges: []safcm.FileChange{
1934 Old: safcm.FileChangeInfo{
1935 Mode: fs.ModeNamedPipe | 0666,
1941 New: safcm.FileChangeInfo{
1952 `4: sync remote: files: "path" (group): type differs p--------- -> ----------`,
1953 `3: sync remote: files: "path" (group): updating`,
1954 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1955 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1961 "change: other to symlink",
1965 Mode: fs.ModeSymlink | 0777,
1969 Data: []byte("target"),
1972 ft.CreateFifo("path", 0666)
1979 Mode: fs.ModeSymlink | 0777,
1980 Data: []byte("target"),
1984 FileChanges: []safcm.FileChange{
1987 Old: safcm.FileChangeInfo{
1988 Mode: fs.ModeNamedPipe | 0666,
1994 New: safcm.FileChangeInfo{
1995 Mode: fs.ModeSymlink | 0777,
2005 `4: sync remote: files: "path" (group): type differs p--------- -> L---------`,
2006 `3: sync remote: files: "path" (group): updating`,
2007 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2008 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2014 "change: other to directory",
2018 Mode: fs.ModeDir | 0751,
2024 ft.CreateFifo("path", 0666)
2031 Mode: fs.ModeDir | 0751,
2035 FileChanges: []safcm.FileChange{
2038 Old: safcm.FileChangeInfo{
2039 Mode: fs.ModeNamedPipe | 0666,
2045 New: safcm.FileChangeInfo{
2046 Mode: fs.ModeDir | 0751,
2056 `4: sync remote: files: "path" (group): type differs p--------- -> d---------`,
2057 `3: sync remote: files: "path" (group): updating`,
2058 `4: sync remote: files: "path" (group): removing (due to type change)`,
2059 `4: sync remote: files: "path" (group): creating directory`,
2060 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
2061 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
2067 "change: file to symlink (same content)",
2071 Mode: fs.ModeSymlink | 0777,
2075 Data: []byte("target"),
2078 ft.CreateFile("path", "target", 0644)
2085 Mode: fs.ModeSymlink | 0777,
2086 Data: []byte("target"),
2090 FileChanges: []safcm.FileChange{
2093 Old: safcm.FileChangeInfo{
2100 New: safcm.FileChangeInfo{
2101 Mode: fs.ModeSymlink | 0777,
2111 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
2112 `3: sync remote: files: "path" (group): updating`,
2113 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2114 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2141 ft.CreateFile("file", `this
2161 FileChanges: []safcm.FileChange{
2164 Old: safcm.FileChangeInfo{
2171 New: safcm.FileChangeInfo{
2178 DataDiff: `@@ -1,5 +1,7 @@
2192 `4: sync remote: files: "file" (group): content differs`,
2193 `3: sync remote: files: "file" (group): updating`,
2194 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2200 "diff: binary both",
2209 Data: []byte("\x00\x01\x02\x03"),
2213 ft.CreateFile("file", "\x00\x01\x02", 0644)
2221 Data: []byte("\x00\x01\x02"),
2225 FileChanges: []safcm.FileChange{
2228 Old: safcm.FileChangeInfo{
2235 New: safcm.FileChangeInfo{
2242 DataDiff: "Binary files differ, cannot show diff",
2247 `4: sync remote: files: "file" (group): content differs`,
2248 `3: sync remote: files: "file" (group): updating`,
2249 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2264 Data: []byte("content\n"),
2268 ft.CreateFile("file", "\x00\x01\x02", 0644)
2276 Data: []byte("\x00\x01\x02"),
2280 FileChanges: []safcm.FileChange{
2283 Old: safcm.FileChangeInfo{
2290 New: safcm.FileChangeInfo{
2297 DataDiff: `@@ -1,2 +1,2 @@
2306 `4: sync remote: files: "file" (group): content differs`,
2307 `3: sync remote: files: "file" (group): updating`,
2308 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2323 Data: []byte("\x00\x01\x02\x03"),
2327 ft.CreateFile("file", "content\n", 0644)
2335 Data: []byte("content\n"),
2339 FileChanges: []safcm.FileChange{
2342 Old: safcm.FileChangeInfo{
2349 New: safcm.FileChangeInfo{
2356 DataDiff: `@@ -1,2 +1,2 @@
2365 `4: sync remote: files: "file" (group): content differs`,
2366 `3: sync remote: files: "file" (group): updating`,
2367 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2373 for _, tc := range tests {
2374 t.Run(tc.name, func(t *testing.T) {
2375 // Create separate test directory for each test case
2376 path := filepath.Join(cwd, "testdata", "file-"+tc.name)
2377 err := os.Mkdir(path, 0700)
2381 err = os.Chdir(path)
2386 if tc.prepare != nil {
2390 s, res := prepareSync(tc.req, &testRunner{
2393 err = s.setDefaults()
2398 // Deterministic temporary symlink names
2402 err = s.syncFile(tc.file, &changed)
2403 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
2405 // Remove random file names from result
2406 for i, x := range dbg {
2407 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
2409 testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
2411 files, err := ft.WalkDir(path)
2415 testutil.AssertEqual(t, "files", files, tc.expFiles)
2417 testutil.AssertEqual(t, "changed", changed, tc.expChanged)
2418 testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
2423 err = os.RemoveAll(filepath.Join(cwd, "testdata"))