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 // Create separate test directory for each test case
845 path := filepath.Join(cwd, "testdata", "files-"+tc.name)
846 err = os.Mkdir(path, 0700)
855 if tc.prepare != nil {
859 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("%s: err = %#v, want %#v",
869 tc.name, err, tc.expErr)
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) {
877 t.Errorf("%s: dbg: %s", tc.name,
878 cmp.Diff(tc.expDbg, dbg))
881 files, err := ft.WalkDir(path)
885 if !reflect.DeepEqual(tc.expFiles, files) {
886 t.Errorf("%s: files: %s", tc.name,
887 cmp.Diff(tc.expFiles, files))
890 if !reflect.DeepEqual(tc.expResp, s.resp) {
891 t.Errorf("%s: resp: %s", tc.name,
892 cmp.Diff(tc.expResp, s.resp))
894 if !reflect.DeepEqual(tc.triggers, s.triggers) {
895 t.Errorf("%s: triggers: %s", tc.name,
896 cmp.Diff(tc.triggers, s.triggers))
900 os.Remove(tmpTestFilePath)
902 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
909 func TestSyncFile(t *testing.T) {
910 cwd, err := os.Getwd()
916 err = os.RemoveAll("testdata")
920 err = os.Mkdir("testdata", 0700)
927 Mode: fs.ModeDir | 0700,
929 user, uid, group, gid := ft.CurrentUserAndGroup()
938 expResp safcm.MsgSyncResp
943 // NOTE: Also update MsgSyncResp in safcm test cases when
944 // changing anything here!
946 // TODO: Add tests for chown and run them only as root
958 Data: []byte("content\n"),
968 Data: []byte("content\n"),
972 FileChanges: []safcm.FileChange{
976 New: safcm.FileChangeInfo{
987 `4: sync remote: files: "file" (group): will create`,
988 `3: sync remote: files: "file" (group): creating`,
989 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
990 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
995 "file: create (dry-run)",
1004 Data: []byte("content\n"),
1011 FileChanges: []safcm.FileChange{
1015 New: safcm.FileChangeInfo{
1026 `4: sync remote: files: "file" (group): will create`,
1027 `3: sync remote: files: "file" (group): creating`,
1028 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
1041 Data: []byte("content\n"),
1045 ft.CreateFile("file", "content\n", 0644)
1053 Data: []byte("content\n"),
1056 safcm.MsgSyncResp{},
1058 `4: sync remote: files: "file" (group): unchanged`,
1064 "file: unchanged (non-default user-group)",
1073 Data: []byte("content\n"),
1077 ft.CreateFile("file", "content\n", 0644)
1085 Data: []byte("content\n"),
1088 safcm.MsgSyncResp{},
1090 `4: sync remote: files: "file" (group): unchanged`,
1100 Mode: 0755 | fs.ModeSetuid,
1103 Data: []byte("content\n"),
1107 ft.CreateFile("file", "content\n", 0755)
1114 Mode: 0755 | fs.ModeSetuid,
1115 Data: []byte("content\n"),
1119 FileChanges: []safcm.FileChange{
1122 Old: safcm.FileChangeInfo{
1129 New: safcm.FileChangeInfo{
1130 Mode: 0755 | fs.ModeSetuid,
1140 `4: sync remote: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
1141 `3: sync remote: files: "file" (group): updating`,
1142 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1143 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1156 Data: []byte("content\n"),
1160 ft.CreateFile("file", "old content\n", 0644)
1168 Data: []byte("content\n"),
1172 FileChanges: []safcm.FileChange{
1175 Old: safcm.FileChangeInfo{
1182 New: safcm.FileChangeInfo{
1189 DataDiff: `@@ -1,2 +1,2 @@
1198 `4: sync remote: files: "file" (group): content differs`,
1199 `3: sync remote: files: "file" (group): updating`,
1200 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1201 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1213 Mode: fs.ModeSymlink | 0777,
1216 Data: []byte("target"),
1225 Mode: fs.ModeSymlink | 0777,
1226 Data: []byte("target"),
1230 FileChanges: []safcm.FileChange{
1234 New: safcm.FileChangeInfo{
1235 Mode: fs.ModeSymlink | 0777,
1245 `4: sync remote: files: "link" (group): will create`,
1246 `3: sync remote: files: "link" (group): creating`,
1247 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1248 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1253 "symlink: create (conflict)",
1257 Mode: fs.ModeSymlink | 0777,
1260 Data: []byte("target"),
1264 ft.CreateFile(".link8717895732742165505", "", 0600)
1270 Path: ".link8717895732742165505",
1276 Mode: fs.ModeSymlink | 0777,
1277 Data: []byte("target"),
1281 FileChanges: []safcm.FileChange{
1285 New: safcm.FileChangeInfo{
1286 Mode: fs.ModeSymlink | 0777,
1296 `4: sync remote: files: "link" (group): will create`,
1297 `3: sync remote: files: "link" (group): creating`,
1298 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1299 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1300 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1305 "symlink: create (dry-run)",
1311 Mode: fs.ModeSymlink | 0777,
1314 Data: []byte("target"),
1321 FileChanges: []safcm.FileChange{
1325 New: safcm.FileChangeInfo{
1326 Mode: fs.ModeSymlink | 0777,
1336 `4: sync remote: files: "link" (group): will create`,
1337 `3: sync remote: files: "link" (group): creating`,
1338 `4: sync remote: files: "link" (group): dry-run, skipping changes`,
1344 "symlink: unchanged",
1348 Mode: fs.ModeSymlink | 0777,
1351 Data: []byte("target"),
1355 ft.CreateSymlink("link", "target")
1362 Mode: fs.ModeSymlink | 0777,
1363 Data: []byte("target"),
1366 safcm.MsgSyncResp{},
1368 `4: sync remote: files: "link" (group): unchanged`,
1378 Mode: fs.ModeSymlink | 0777,
1381 Data: []byte("target"),
1385 ft.CreateSymlink("link", "old-target")
1392 Mode: fs.ModeSymlink | 0777,
1393 Data: []byte("target"),
1397 FileChanges: []safcm.FileChange{
1400 Old: safcm.FileChangeInfo{
1401 Mode: fs.ModeSymlink | 0777,
1407 New: safcm.FileChangeInfo{
1408 Mode: fs.ModeSymlink | 0777,
1414 DataDiff: `@@ -1 +1 @@
1422 `4: sync remote: files: "link" (group): content differs`,
1423 `3: sync remote: files: "link" (group): updating`,
1424 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1425 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1433 "directory: create",
1437 Mode: fs.ModeDir | 0705,
1448 Mode: fs.ModeDir | 0705,
1452 FileChanges: []safcm.FileChange{
1456 New: safcm.FileChangeInfo{
1457 Mode: fs.ModeDir | 0705,
1467 `4: sync remote: files: "dir" (group): will create`,
1468 `3: sync remote: files: "dir" (group): creating`,
1469 `4: sync remote: files: "dir" (group): creating directory`,
1470 `4: sync remote: files: "dir" (group): chmodding drwx---r-x`,
1471 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
1476 "directory: create (dry-run)",
1482 Mode: fs.ModeDir | 0644,
1491 FileChanges: []safcm.FileChange{
1495 New: safcm.FileChangeInfo{
1496 Mode: fs.ModeDir | 0644,
1506 `4: sync remote: files: "dir" (group): will create`,
1507 `3: sync remote: files: "dir" (group): creating`,
1508 `4: sync remote: files: "dir" (group): dry-run, skipping changes`,
1514 "directory: unchanged",
1518 Mode: fs.ModeDir | 0755,
1524 ft.CreateDirectory("dir", 0755)
1531 Mode: fs.ModeDir | 0755,
1534 safcm.MsgSyncResp{},
1536 `4: sync remote: files: "dir" (group): unchanged`,
1542 "directory: permission",
1546 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1552 ft.CreateDirectory("dir", 0500|fs.ModeSticky)
1559 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1563 FileChanges: []safcm.FileChange{
1566 Old: safcm.FileChangeInfo{
1567 Mode: fs.ModeDir | 0500 | fs.ModeSticky,
1573 New: safcm.FileChangeInfo{
1574 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1584 `4: sync remote: files: "dir" (group): permission differs dtr-x------ -> dgrwxr-xr-x`,
1585 `3: sync remote: files: "dir" (group): updating`,
1586 `4: sync remote: files: "dir" (group): chmodding dgrwxr-xr-x`,
1594 "change: file to directory",
1598 Mode: fs.ModeDir | 0751,
1604 ft.CreateFile("path", "content\n", 0644)
1611 Mode: fs.ModeDir | 0751,
1615 FileChanges: []safcm.FileChange{
1618 Old: safcm.FileChangeInfo{
1625 New: safcm.FileChangeInfo{
1626 Mode: fs.ModeDir | 0751,
1632 DataDiff: `@@ -1,2 +1 @@
1640 `4: sync remote: files: "path" (group): type differs ---------- -> d---------`,
1641 `3: sync remote: files: "path" (group): updating`,
1642 `4: sync remote: files: "path" (group): removing (due to type change)`,
1643 `4: sync remote: files: "path" (group): creating directory`,
1644 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1645 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1651 "change: file to symlink",
1655 Mode: fs.ModeSymlink | 0777,
1659 Data: []byte("target"),
1662 ft.CreateFile("path", "content\n", 0644)
1669 Mode: fs.ModeSymlink | 0777,
1670 Data: []byte("target"),
1674 FileChanges: []safcm.FileChange{
1677 Old: safcm.FileChangeInfo{
1684 New: safcm.FileChangeInfo{
1685 Mode: fs.ModeSymlink | 0777,
1691 DataDiff: `@@ -1,2 +1 @@
1700 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
1701 `3: sync remote: files: "path" (group): updating`,
1702 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1703 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1709 "change: symlink to file",
1717 Data: []byte("content\n"),
1720 ft.CreateSymlink("path", "target")
1728 Data: []byte("content\n"),
1732 FileChanges: []safcm.FileChange{
1735 Old: safcm.FileChangeInfo{
1736 Mode: fs.ModeSymlink | 0777,
1742 New: safcm.FileChangeInfo{
1749 DataDiff: `@@ -1 +1,2 @@
1758 `4: sync remote: files: "path" (group): type differs L--------- -> ----------`,
1759 `3: sync remote: files: "path" (group): updating`,
1760 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1761 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1767 "change: symlink to directory",
1771 Mode: fs.ModeDir | 0751,
1777 ft.CreateSymlink("path", "target")
1784 Mode: fs.ModeDir | 0751,
1788 FileChanges: []safcm.FileChange{
1791 Old: safcm.FileChangeInfo{
1792 Mode: fs.ModeSymlink | 0777,
1798 New: safcm.FileChangeInfo{
1799 Mode: fs.ModeDir | 0751,
1805 DataDiff: `@@ -1 +1 @@
1813 `4: sync remote: files: "path" (group): type differs L--------- -> d---------`,
1814 `3: sync remote: files: "path" (group): updating`,
1815 `4: sync remote: files: "path" (group): removing (due to type change)`,
1816 `4: sync remote: files: "path" (group): creating directory`,
1817 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1818 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1824 "change: directory to file",
1832 Data: []byte("content\n"),
1835 ft.CreateDirectory("path", 0777)
1843 Data: []byte("content\n"),
1847 FileChanges: []safcm.FileChange{
1850 Old: safcm.FileChangeInfo{
1851 Mode: fs.ModeDir | 0777,
1857 New: safcm.FileChangeInfo{
1868 `4: sync remote: files: "path" (group): type differs d--------- -> ----------`,
1869 `3: sync remote: files: "path" (group): updating`,
1870 `4: sync remote: files: "path" (group): removing (due to type change)`,
1871 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1872 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1878 "change: directory to symlink",
1882 Mode: fs.ModeSymlink | 0777,
1886 Data: []byte("target"),
1889 ft.CreateDirectory("path", 0777)
1896 Mode: fs.ModeSymlink | 0777,
1897 Data: []byte("target"),
1901 FileChanges: []safcm.FileChange{
1904 Old: safcm.FileChangeInfo{
1905 Mode: fs.ModeDir | 0777,
1911 New: safcm.FileChangeInfo{
1912 Mode: fs.ModeSymlink | 0777,
1922 `4: sync remote: files: "path" (group): type differs d--------- -> L---------`,
1923 `3: sync remote: files: "path" (group): updating`,
1924 `4: sync remote: files: "path" (group): removing (due to type change)`,
1925 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1926 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1932 "change: other to file",
1940 Data: []byte("content\n"),
1943 ft.CreateFifo("path", 0666)
1951 Data: []byte("content\n"),
1955 FileChanges: []safcm.FileChange{
1958 Old: safcm.FileChangeInfo{
1959 Mode: fs.ModeNamedPipe | 0666,
1965 New: safcm.FileChangeInfo{
1976 `4: sync remote: files: "path" (group): type differs p--------- -> ----------`,
1977 `3: sync remote: files: "path" (group): updating`,
1978 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1979 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1985 "change: other to symlink",
1989 Mode: fs.ModeSymlink | 0777,
1993 Data: []byte("target"),
1996 ft.CreateFifo("path", 0666)
2003 Mode: fs.ModeSymlink | 0777,
2004 Data: []byte("target"),
2008 FileChanges: []safcm.FileChange{
2011 Old: safcm.FileChangeInfo{
2012 Mode: fs.ModeNamedPipe | 0666,
2018 New: safcm.FileChangeInfo{
2019 Mode: fs.ModeSymlink | 0777,
2029 `4: sync remote: files: "path" (group): type differs p--------- -> L---------`,
2030 `3: sync remote: files: "path" (group): updating`,
2031 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2032 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2038 "change: other to directory",
2042 Mode: fs.ModeDir | 0751,
2048 ft.CreateFifo("path", 0666)
2055 Mode: fs.ModeDir | 0751,
2059 FileChanges: []safcm.FileChange{
2062 Old: safcm.FileChangeInfo{
2063 Mode: fs.ModeNamedPipe | 0666,
2069 New: safcm.FileChangeInfo{
2070 Mode: fs.ModeDir | 0751,
2080 `4: sync remote: files: "path" (group): type differs p--------- -> d---------`,
2081 `3: sync remote: files: "path" (group): updating`,
2082 `4: sync remote: files: "path" (group): removing (due to type change)`,
2083 `4: sync remote: files: "path" (group): creating directory`,
2084 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
2085 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
2091 "change: file to symlink (same content)",
2095 Mode: fs.ModeSymlink | 0777,
2099 Data: []byte("target"),
2102 ft.CreateFile("path", "target", 0644)
2109 Mode: fs.ModeSymlink | 0777,
2110 Data: []byte("target"),
2114 FileChanges: []safcm.FileChange{
2117 Old: safcm.FileChangeInfo{
2124 New: safcm.FileChangeInfo{
2125 Mode: fs.ModeSymlink | 0777,
2135 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
2136 `3: sync remote: files: "path" (group): updating`,
2137 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2138 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2165 ft.CreateFile("file", `this
2185 FileChanges: []safcm.FileChange{
2188 Old: safcm.FileChangeInfo{
2195 New: safcm.FileChangeInfo{
2202 DataDiff: `@@ -1,5 +1,7 @@
2216 `4: sync remote: files: "file" (group): content differs`,
2217 `3: sync remote: files: "file" (group): updating`,
2218 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2224 "diff: binary both",
2233 Data: []byte("\x00\x01\x02\x03"),
2237 ft.CreateFile("file", "\x00\x01\x02", 0644)
2245 Data: []byte("\x00\x01\x02"),
2249 FileChanges: []safcm.FileChange{
2252 Old: safcm.FileChangeInfo{
2259 New: safcm.FileChangeInfo{
2266 DataDiff: "Binary files differ, cannot show diff",
2271 `4: sync remote: files: "file" (group): content differs`,
2272 `3: sync remote: files: "file" (group): updating`,
2273 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2288 Data: []byte("content\n"),
2292 ft.CreateFile("file", "\x00\x01\x02", 0644)
2300 Data: []byte("\x00\x01\x02"),
2304 FileChanges: []safcm.FileChange{
2307 Old: safcm.FileChangeInfo{
2314 New: safcm.FileChangeInfo{
2321 DataDiff: `@@ -1,2 +1,2 @@
2330 `4: sync remote: files: "file" (group): content differs`,
2331 `3: sync remote: files: "file" (group): updating`,
2332 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2347 Data: []byte("\x00\x01\x02\x03"),
2351 ft.CreateFile("file", "content\n", 0644)
2359 Data: []byte("content\n"),
2363 FileChanges: []safcm.FileChange{
2366 Old: safcm.FileChangeInfo{
2373 New: safcm.FileChangeInfo{
2380 DataDiff: `@@ -1,2 +1,2 @@
2389 `4: sync remote: files: "file" (group): content differs`,
2390 `3: sync remote: files: "file" (group): updating`,
2391 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2397 for _, tc := range tests {
2398 // Create separate test directory for each test case
2399 path := filepath.Join(cwd, "testdata", "file-"+tc.name)
2400 err = os.Mkdir(path, 0700)
2404 err = os.Chdir(path)
2409 if tc.prepare != nil {
2413 s, res := prepareSync(tc.req, &testRunner{
2419 // Deterministic temporary symlink names
2423 err := s.syncFile(tc.file, &changed)
2424 // Ugly but the simplest way to compare errors (including nil)
2425 if fmt.Sprintf("%s", err) != fmt.Sprintf("%s", tc.expErr) {
2426 t.Errorf("%s: err = %#v, want %#v",
2427 tc.name, err, tc.expErr)
2430 // Remove random file names from result
2431 for i, x := range dbg {
2432 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
2434 if !reflect.DeepEqual(tc.expDbg, dbg) {
2435 t.Errorf("%s: dbg: %s", tc.name,
2436 cmp.Diff(tc.expDbg, dbg))
2439 files, err := ft.WalkDir(path)
2443 if !reflect.DeepEqual(tc.expFiles, files) {
2444 t.Errorf("%s: files: %s", tc.name,
2445 cmp.Diff(tc.expFiles, files))
2448 if tc.expChanged != changed {
2449 t.Errorf("%s: changed = %#v, want %#v",
2450 tc.name, changed, tc.expChanged)
2452 if !reflect.DeepEqual(tc.expResp, s.resp) {
2453 t.Errorf("%s: resp: %s", tc.name,
2454 cmp.Diff(tc.expResp, s.resp))
2459 err = os.RemoveAll(filepath.Join(cwd, "testdata"))