]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - cmd/safcm-remote/sync/files_test.go
sync: tests: check return value of setDefaults()
[safcm/safcm.git] / cmd / safcm-remote / sync / files_test.go
1 // Copyright (C) 2021  Simon Ruderich
2 //
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.
7 //
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.
12 //
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/>.
15
16 package sync
17
18 import (
19         "fmt"
20         "io/fs"
21         "math/rand"
22         "os"
23         "path/filepath"
24         "regexp"
25         "testing"
26
27         "ruderich.org/simon/safcm"
28         ft "ruderich.org/simon/safcm/cmd/safcm-remote/sync/filetest"
29         "ruderich.org/simon/safcm/testutil"
30 )
31
32 var randFilesRegexp = regexp.MustCompile(`\d+"$`)
33
34 func TestSyncFiles(t *testing.T) {
35         cwd, err := os.Getwd()
36         if err != nil {
37                 t.Fatal(err)
38         }
39         defer os.Chdir(cwd)
40
41         err = os.RemoveAll("testdata")
42         if err != nil {
43                 t.Fatal(err)
44         }
45         err = os.Mkdir("testdata", 0700)
46         if err != nil {
47                 t.Fatal(err)
48         }
49
50         root := ft.File{
51                 Path: ".",
52                 Mode: fs.ModeDir | 0700,
53         }
54         user, uid, group, gid := ft.CurrentUserAndGroup()
55
56         skipUnlessCiRun := len(os.Getenv("SAFCM_CI_RUN")) == 0
57
58         tmpTestFilePath := "/tmp/safcm-sync-files-test-file"
59
60         tests := []struct {
61                 name        string
62                 skip        bool
63                 req         safcm.MsgSyncReq
64                 prepare     func()
65                 expTriggers []string
66                 expFiles    []ft.File
67                 expResp     safcm.MsgSyncResp
68                 expDbg      []string
69                 expErr      error
70         }{
71
72                 // NOTE: Also update MsgSyncResp in safcm test cases when
73                 // changing anything here!
74
75                 // See TestSyncFile() for most file related tests. This
76                 // function only tests the overall results and triggers.
77
78                 {
79                         "basic: create",
80                         false,
81                         safcm.MsgSyncReq{
82                                 Files: map[string]*safcm.File{
83                                         ".": {
84                                                 Path:      ".",
85                                                 Mode:      fs.ModeDir | 0700,
86                                                 Uid:       -1,
87                                                 Gid:       -1,
88                                                 OrigGroup: "group",
89                                         },
90                                         "dir": {
91                                                 Path:      "dir",
92                                                 Mode:      fs.ModeDir | 0755,
93                                                 Uid:       -1,
94                                                 Gid:       -1,
95                                                 OrigGroup: "group",
96                                         },
97                                         "dir/file": {
98                                                 Path:      "dir/file",
99                                                 Mode:      0644,
100                                                 Uid:       -1,
101                                                 Gid:       -1,
102                                                 Data:      []byte("content\n"),
103                                                 OrigGroup: "group",
104                                         },
105                                 },
106                         },
107                         nil,
108                         nil,
109                         []ft.File{
110                                 root,
111                                 {
112                                         Path: "dir",
113                                         Mode: fs.ModeDir | 0755,
114                                 },
115                                 {
116                                         Path: "dir/file",
117                                         Mode: 0644,
118                                         Data: []byte("content\n"),
119                                 },
120                         },
121                         safcm.MsgSyncResp{
122                                 FileChanges: []safcm.FileChange{
123                                         {
124                                                 Path:    "dir",
125                                                 Created: true,
126                                                 New: safcm.FileChangeInfo{
127                                                         Mode:  fs.ModeDir | 0755,
128                                                         User:  user,
129                                                         Uid:   uid,
130                                                         Group: group,
131                                                         Gid:   gid,
132                                                 },
133                                         },
134                                         {
135                                                 Path:    "dir/file",
136                                                 Created: true,
137                                                 New: safcm.FileChangeInfo{
138                                                         Mode:  0644,
139                                                         User:  user,
140                                                         Uid:   uid,
141                                                         Group: group,
142                                                         Gid:   gid,
143                                                 },
144                                         },
145                                 },
146                         },
147                         []string{
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"`,
158                         },
159                         nil,
160                 },
161
162                 {
163                         "basic: no change",
164                         false,
165                         safcm.MsgSyncReq{
166                                 Files: map[string]*safcm.File{
167                                         ".": {
168                                                 Path:      ".",
169                                                 Mode:      fs.ModeDir | 0700,
170                                                 Uid:       -1,
171                                                 Gid:       -1,
172                                                 OrigGroup: "group",
173                                         },
174                                         "dir": {
175                                                 Path:      "dir",
176                                                 Mode:      fs.ModeDir | 0755,
177                                                 Uid:       -1,
178                                                 Gid:       -1,
179                                                 OrigGroup: "group",
180                                         },
181                                         "dir/file": {
182                                                 Path:      "dir/file",
183                                                 Mode:      0644,
184                                                 Uid:       -1,
185                                                 Gid:       -1,
186                                                 Data:      []byte("content\n"),
187                                                 OrigGroup: "group",
188                                         },
189                                 },
190                         },
191                         func() {
192                                 ft.CreateDirectory("dir", 0755)
193                                 ft.CreateFile("dir/file", "content\n", 0644)
194                         },
195                         nil,
196                         []ft.File{
197                                 root,
198                                 {
199                                         Path: "dir",
200                                         Mode: fs.ModeDir | 0755,
201                                 },
202                                 {
203                                         Path: "dir/file",
204                                         Mode: 0644,
205                                         Data: []byte("content\n"),
206                                 },
207                         },
208                         safcm.MsgSyncResp{},
209                         []string{
210                                 `4: sync remote: files: "." (group): unchanged`,
211                                 `4: sync remote: files: "dir" (group): unchanged`,
212                                 `4: sync remote: files: "dir/file" (group): unchanged`,
213                         },
214                         nil,
215                 },
216
217                 {
218                         "invalid File: user",
219                         false,
220                         safcm.MsgSyncReq{
221                                 Files: map[string]*safcm.File{
222                                         ".": {
223                                                 Path:      ".",
224                                                 Mode:      fs.ModeDir | 0700,
225                                                 User:      "user",
226                                                 Uid:       1,
227                                                 Gid:       -1,
228                                                 OrigGroup: "group",
229                                         },
230                                 },
231                         },
232                         nil,
233                         nil,
234                         []ft.File{
235                                 root,
236                         },
237                         safcm.MsgSyncResp{},
238                         nil,
239                         fmt.Errorf("\".\": cannot set both User (\"user\") and Uid (1)"),
240                 },
241                 {
242                         "invalid File: group",
243                         false,
244                         safcm.MsgSyncReq{
245                                 Files: map[string]*safcm.File{
246                                         ".": {
247                                                 Path:      ".",
248                                                 Mode:      fs.ModeDir | 0700,
249                                                 Uid:       -1,
250                                                 Group:     "group",
251                                                 Gid:       1,
252                                                 OrigGroup: "group",
253                                         },
254                                 },
255                         },
256                         nil,
257                         nil,
258                         []ft.File{
259                                 root,
260                         },
261                         safcm.MsgSyncResp{},
262                         nil,
263                         fmt.Errorf("\".\": cannot set both Group (\"group\") and Gid (1)"),
264                 },
265
266                 {
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.
271                         //
272                         // Use numeric IDs as not all systems use root/root;
273                         // for example BSDs use root/wheel.
274                         "absolute paths: no change",
275                         skipUnlessCiRun,
276                         safcm.MsgSyncReq{
277                                 Files: map[string]*safcm.File{
278                                         "/": {
279                                                 Path:      "/",
280                                                 Mode:      fs.ModeDir | 0755,
281                                                 Uid:       0,
282                                                 Gid:       0,
283                                                 OrigGroup: "group",
284                                         },
285                                         "/etc": {
286                                                 Path:      "/etc",
287                                                 Mode:      fs.ModeDir | 0755,
288                                                 Uid:       0,
289                                                 Gid:       0,
290                                                 OrigGroup: "group",
291                                         },
292                                         "/tmp": {
293                                                 Path:      "/tmp",
294                                                 Mode:      fs.ModeDir | 0777 | fs.ModeSticky,
295                                                 Uid:       0,
296                                                 Gid:       0,
297                                                 OrigGroup: "group",
298                                         },
299                                 },
300                         },
301                         nil,
302                         nil,
303                         []ft.File{
304                                 root,
305                         },
306                         safcm.MsgSyncResp{},
307                         []string{
308                                 `4: sync remote: files: "/" (group): unchanged`,
309                                 `4: sync remote: files: "/etc" (group): unchanged`,
310                                 `4: sync remote: files: "/tmp" (group): unchanged`,
311                         },
312                         nil,
313                 },
314
315                 {
316                         "triggers: no change",
317                         false,
318                         safcm.MsgSyncReq{
319                                 Files: map[string]*safcm.File{
320                                         ".": {
321                                                 Path:      ".",
322                                                 Mode:      fs.ModeDir | 0700,
323                                                 Uid:       -1,
324                                                 Gid:       -1,
325                                                 OrigGroup: "group",
326                                                 TriggerCommands: []string{
327                                                         "echo trigger .",
328                                                 },
329                                         },
330                                         "dir": {
331                                                 Path:      "dir",
332                                                 Mode:      fs.ModeDir | 0755,
333                                                 Uid:       -1,
334                                                 Gid:       -1,
335                                                 OrigGroup: "group",
336                                                 TriggerCommands: []string{
337                                                         "echo trigger dir",
338                                                 },
339                                         },
340                                         "dir/file": {
341                                                 Path:      "dir/file",
342                                                 Mode:      0644,
343                                                 Uid:       -1,
344                                                 Gid:       -1,
345                                                 Data:      []byte("content\n"),
346                                                 OrigGroup: "group",
347                                                 TriggerCommands: []string{
348                                                         "echo trigger dir/file",
349                                                 },
350                                         },
351                                 },
352                         },
353                         func() {
354                                 ft.CreateDirectory("dir", 0755)
355                                 ft.CreateFile("dir/file", "content\n", 0644)
356                         },
357                         nil,
358                         []ft.File{
359                                 root,
360                                 {
361                                         Path: "dir",
362                                         Mode: fs.ModeDir | 0755,
363                                 },
364                                 {
365                                         Path: "dir/file",
366                                         Mode: 0644,
367                                         Data: []byte("content\n"),
368                                 },
369                         },
370                         safcm.MsgSyncResp{},
371                         []string{
372                                 `4: sync remote: files: "." (group): unchanged`,
373                                 `4: sync remote: files: "dir" (group): unchanged`,
374                                 `4: sync remote: files: "dir/file" (group): unchanged`,
375                         },
376                         nil,
377                 },
378
379                 {
380                         "triggers: change root",
381                         false,
382                         safcm.MsgSyncReq{
383                                 Files: map[string]*safcm.File{
384                                         ".": {
385                                                 Path:      ".",
386                                                 Mode:      fs.ModeDir | 0700,
387                                                 Uid:       -1,
388                                                 Gid:       -1,
389                                                 OrigGroup: "group",
390                                                 TriggerCommands: []string{
391                                                         "echo trigger .",
392                                                 },
393                                         },
394                                         "dir": {
395                                                 Path:      "dir",
396                                                 Mode:      fs.ModeDir | 0755,
397                                                 Uid:       -1,
398                                                 Gid:       -1,
399                                                 OrigGroup: "group",
400                                                 TriggerCommands: []string{
401                                                         "echo trigger dir",
402                                                 },
403                                         },
404                                         "dir/file": {
405                                                 Path:      "dir/file",
406                                                 Mode:      0644,
407                                                 Uid:       -1,
408                                                 Gid:       -1,
409                                                 Data:      []byte("content\n"),
410                                                 OrigGroup: "group",
411                                                 TriggerCommands: []string{
412                                                         "echo trigger dir/file",
413                                                 },
414                                         },
415                                 },
416                         },
417                         func() {
418                                 err = os.Chmod(".", 0750)
419                                 if err != nil {
420                                         panic(err)
421                                 }
422                                 ft.CreateDirectory("dir", 0755)
423                                 ft.CreateFile("dir/file", "content\n", 0644)
424                         },
425                         []string{
426                                 ".",
427                         },
428                         []ft.File{
429                                 root,
430                                 {
431                                         Path: "dir",
432                                         Mode: fs.ModeDir | 0755,
433                                 },
434                                 {
435                                         Path: "dir/file",
436                                         Mode: 0644,
437                                         Data: []byte("content\n"),
438                                 },
439                         },
440                         safcm.MsgSyncResp{
441                                 FileChanges: []safcm.FileChange{
442                                         {
443                                                 Path: ".",
444                                                 Old: safcm.FileChangeInfo{
445                                                         Mode:  fs.ModeDir | 0750,
446                                                         User:  user,
447                                                         Uid:   uid,
448                                                         Group: group,
449                                                         Gid:   gid,
450                                                 },
451                                                 New: safcm.FileChangeInfo{
452                                                         Mode:  fs.ModeDir | 0700,
453                                                         User:  user,
454                                                         Uid:   uid,
455                                                         Group: group,
456                                                         Gid:   gid,
457                                                 },
458                                         },
459                                 },
460                         },
461                         []string{
462                                 `4: sync remote: files: "." (group): permission differs drwxr-x--- -> drwx------`,
463                                 `3: sync remote: files: "." (group): updating`,
464                                 `4: sync remote: files: "." (group): chmodding drwx------`,
465                                 `3: sync remote: files: ".": queuing trigger on "."`,
466                                 `4: sync remote: files: "dir" (group): unchanged`,
467                                 `4: sync remote: files: "dir/file" (group): unchanged`,
468                         },
469                         nil,
470                 },
471
472                 {
473                         "triggers: change middle",
474                         false,
475                         safcm.MsgSyncReq{
476                                 Files: map[string]*safcm.File{
477                                         ".": {
478                                                 Path:      ".",
479                                                 Mode:      fs.ModeDir | 0700,
480                                                 Uid:       -1,
481                                                 Gid:       -1,
482                                                 OrigGroup: "group",
483                                                 TriggerCommands: []string{
484                                                         "echo trigger .",
485                                                 },
486                                         },
487                                         "dir": {
488                                                 Path:      "dir",
489                                                 Mode:      fs.ModeDir | 0755,
490                                                 Uid:       -1,
491                                                 Gid:       -1,
492                                                 OrigGroup: "group",
493                                                 TriggerCommands: []string{
494                                                         "echo trigger dir",
495                                                 },
496                                         },
497                                         "dir/file": {
498                                                 Path:      "dir/file",
499                                                 Mode:      0644,
500                                                 Uid:       -1,
501                                                 Gid:       -1,
502                                                 Data:      []byte("content\n"),
503                                                 OrigGroup: "group",
504                                                 TriggerCommands: []string{
505                                                         "echo trigger dir/file",
506                                                 },
507                                         },
508                                 },
509                         },
510                         func() {
511                                 ft.CreateDirectory("dir", 0750)
512                                 ft.CreateFile("dir/file", "content\n", 0644)
513                         },
514                         []string{
515                                 ".",
516                                 "dir",
517                         },
518                         []ft.File{
519                                 root,
520                                 {
521                                         Path: "dir",
522                                         Mode: fs.ModeDir | 0755,
523                                 },
524                                 {
525                                         Path: "dir/file",
526                                         Mode: 0644,
527                                         Data: []byte("content\n"),
528                                 },
529                         },
530                         safcm.MsgSyncResp{
531                                 FileChanges: []safcm.FileChange{
532                                         {
533                                                 Path: "dir",
534                                                 Old: safcm.FileChangeInfo{
535                                                         Mode:  fs.ModeDir | 0750,
536                                                         User:  user,
537                                                         Uid:   uid,
538                                                         Group: group,
539                                                         Gid:   gid,
540                                                 },
541                                                 New: safcm.FileChangeInfo{
542                                                         Mode:  fs.ModeDir | 0755,
543                                                         User:  user,
544                                                         Uid:   uid,
545                                                         Group: group,
546                                                         Gid:   gid,
547                                                 },
548                                         },
549                                 },
550                         },
551                         []string{
552                                 `4: sync remote: files: "." (group): unchanged`,
553                                 `4: sync remote: files: "dir" (group): permission differs drwxr-x--- -> drwxr-xr-x`,
554                                 `3: sync remote: files: "dir" (group): updating`,
555                                 `4: sync remote: files: "dir" (group): chmodding drwxr-xr-x`,
556                                 `3: sync remote: files: "dir": queuing trigger on "."`,
557                                 `3: sync remote: files: "dir": queuing trigger on "dir"`,
558                                 `4: sync remote: files: "dir/file" (group): unchanged`,
559                         },
560                         nil,
561                 },
562
563                 {
564                         "triggers: change leaf",
565                         false,
566                         safcm.MsgSyncReq{
567                                 Files: map[string]*safcm.File{
568                                         ".": {
569                                                 Path:      ".",
570                                                 Mode:      fs.ModeDir | 0700,
571                                                 Uid:       -1,
572                                                 Gid:       -1,
573                                                 OrigGroup: "group",
574                                                 TriggerCommands: []string{
575                                                         "echo trigger .",
576                                                 },
577                                         },
578                                         "dir": {
579                                                 Path:      "dir",
580                                                 Mode:      fs.ModeDir | 0755,
581                                                 Uid:       -1,
582                                                 Gid:       -1,
583                                                 OrigGroup: "group",
584                                                 TriggerCommands: []string{
585                                                         "echo trigger dir",
586                                                 },
587                                         },
588                                         "dir/file": {
589                                                 Path:      "dir/file",
590                                                 Mode:      0644,
591                                                 Uid:       -1,
592                                                 Gid:       -1,
593                                                 Data:      []byte("content\n"),
594                                                 OrigGroup: "group",
595                                                 TriggerCommands: []string{
596                                                         "echo trigger dir/file",
597                                                 },
598                                         },
599                                 },
600                         },
601                         func() {
602                                 ft.CreateDirectory("dir", 0755)
603                         },
604                         []string{
605                                 ".",
606                                 "dir",
607                                 "dir/file",
608                         },
609                         []ft.File{
610                                 root,
611                                 {
612                                         Path: "dir",
613                                         Mode: fs.ModeDir | 0755,
614                                 },
615                                 {
616                                         Path: "dir/file",
617                                         Mode: 0644,
618                                         Data: []byte("content\n"),
619                                 },
620                         },
621                         safcm.MsgSyncResp{
622                                 FileChanges: []safcm.FileChange{
623                                         {
624                                                 Path:    "dir/file",
625                                                 Created: true,
626                                                 New: safcm.FileChangeInfo{
627                                                         Mode:  0644,
628                                                         User:  user,
629                                                         Uid:   uid,
630                                                         Group: group,
631                                                         Gid:   gid,
632                                                 },
633                                         },
634                                 },
635                         },
636                         []string{
637                                 `4: sync remote: files: "." (group): unchanged`,
638                                 `4: sync remote: files: "dir" (group): unchanged`,
639                                 `4: sync remote: files: "dir/file" (group): will create`,
640                                 `3: sync remote: files: "dir/file" (group): creating`,
641                                 `4: sync remote: files: "dir/file" (group): creating temporary file "dir/.file*"`,
642                                 `4: sync remote: files: "dir/file" (group): renaming "dir/.fileRND"`,
643                                 `3: sync remote: files: "dir/file": queuing trigger on "."`,
644                                 `3: sync remote: files: "dir/file": queuing trigger on "dir"`,
645                                 `3: sync remote: files: "dir/file": queuing trigger on "dir/file"`,
646                         },
647                         nil,
648                 },
649
650                 {
651                         "triggers: multiple changes",
652                         false,
653                         safcm.MsgSyncReq{
654                                 Files: map[string]*safcm.File{
655                                         ".": {
656                                                 Path:      ".",
657                                                 Mode:      fs.ModeDir | 0700,
658                                                 Uid:       -1,
659                                                 Gid:       -1,
660                                                 OrigGroup: "group",
661                                                 TriggerCommands: []string{
662                                                         "echo trigger .",
663                                                 },
664                                         },
665                                         "dir": {
666                                                 Path:      "dir",
667                                                 Mode:      fs.ModeDir | 0755,
668                                                 Uid:       -1,
669                                                 Gid:       -1,
670                                                 OrigGroup: "group",
671                                                 TriggerCommands: []string{
672                                                         "echo trigger dir",
673                                                 },
674                                         },
675                                         "dir/file": {
676                                                 Path:      "dir/file",
677                                                 Mode:      0644,
678                                                 Uid:       -1,
679                                                 Gid:       -1,
680                                                 Data:      []byte("content\n"),
681                                                 OrigGroup: "group",
682                                                 TriggerCommands: []string{
683                                                         "echo trigger dir/file",
684                                                 },
685                                         },
686                                 },
687                         },
688                         nil,
689                         []string{
690                                 ".",
691                                 "dir",
692                                 "dir/file",
693                         },
694                         []ft.File{
695                                 root,
696                                 {
697                                         Path: "dir",
698                                         Mode: fs.ModeDir | 0755,
699                                 },
700                                 {
701                                         Path: "dir/file",
702                                         Mode: 0644,
703                                         Data: []byte("content\n"),
704                                 },
705                         },
706                         safcm.MsgSyncResp{
707                                 FileChanges: []safcm.FileChange{
708                                         {
709                                                 Path:    "dir",
710                                                 Created: true,
711                                                 New: safcm.FileChangeInfo{
712                                                         Mode:  fs.ModeDir | 0755,
713                                                         User:  user,
714                                                         Uid:   uid,
715                                                         Group: group,
716                                                         Gid:   gid,
717                                                 },
718                                         },
719                                         {
720                                                 Path:    "dir/file",
721                                                 Created: true,
722                                                 New: safcm.FileChangeInfo{
723                                                         Mode:  0644,
724                                                         User:  user,
725                                                         Uid:   uid,
726                                                         Group: group,
727                                                         Gid:   gid,
728                                                 },
729                                         },
730                                 },
731                         },
732                         []string{
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"`,
748                         },
749                         nil,
750                 },
751
752                 {
753                         "triggers: absolute paths",
754                         skipUnlessCiRun,
755                         safcm.MsgSyncReq{
756                                 Files: map[string]*safcm.File{
757                                         "/": {
758                                                 Path:      "/",
759                                                 Mode:      fs.ModeDir | 0755,
760                                                 Uid:       0,
761                                                 Gid:       0,
762                                                 OrigGroup: "group",
763                                                 TriggerCommands: []string{
764                                                         "echo trigger /",
765                                                 },
766                                         },
767                                         "/tmp": {
768                                                 Path:      "/tmp",
769                                                 Mode:      fs.ModeDir | 0777 | fs.ModeSticky,
770                                                 Uid:       0,
771                                                 Gid:       0,
772                                                 OrigGroup: "group",
773                                                 TriggerCommands: []string{
774                                                         "echo trigger /tmp",
775                                                 },
776                                         },
777                                         tmpTestFilePath: {
778                                                 Path:      tmpTestFilePath,
779                                                 Mode:      0600,
780                                                 Uid:       -1,
781                                                 Gid:       -1,
782                                                 OrigGroup: "group",
783                                                 TriggerCommands: []string{
784                                                         "echo trigger /tmp/file",
785                                                 },
786                                         },
787                                 },
788                         },
789                         nil,
790                         []string{
791                                 "/",
792                                 "/tmp",
793                                 // Don't use variable for more robust test
794                                 "/tmp/safcm-sync-files-test-file",
795                         },
796                         []ft.File{
797                                 root,
798                         },
799                         safcm.MsgSyncResp{
800                                 FileChanges: []safcm.FileChange{
801                                         {
802                                                 Path:    "/tmp/safcm-sync-files-test-file",
803                                                 Created: true,
804                                                 New: safcm.FileChangeInfo{
805                                                         Mode:  0600,
806                                                         User:  user,
807                                                         Uid:   uid,
808                                                         Group: group,
809                                                         Gid:   gid,
810                                                 },
811                                         },
812                                 },
813                         },
814                         []string{
815                                 `4: sync remote: files: "/" (group): unchanged`,
816                                 `4: sync remote: files: "/tmp" (group): unchanged`,
817                                 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): will create`,
818                                 `3: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating`,
819                                 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): creating temporary file "/tmp/.safcm-sync-files-test-file*"`,
820                                 `4: sync remote: files: "/tmp/safcm-sync-files-test-file" (group): renaming "/tmp/.safcm-sync-files-test-fileRND"`,
821                                 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/"`,
822                                 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp"`,
823                                 `3: sync remote: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp/safcm-sync-files-test-file"`,
824                         },
825                         nil,
826                 },
827         }
828
829         for _, tc := range tests {
830                 t.Run(tc.name, func(t *testing.T) {
831                         if tc.skip {
832                                 t.SkipNow()
833                         }
834
835                         // Create separate test directory for each test case
836                         path := filepath.Join(cwd, "testdata", "files-"+tc.name)
837                         err := os.Mkdir(path, 0700)
838                         if err != nil {
839                                 t.Fatal(err)
840                         }
841                         err = os.Chdir(path)
842                         if err != nil {
843                                 t.Fatal(err)
844                         }
845
846                         if tc.prepare != nil {
847                                 tc.prepare()
848                         }
849
850                         s, res := prepareSync(tc.req, &testRunner{
851                                 t: t,
852                         })
853                         err = s.setDefaults()
854                         if err != nil {
855                                 t.Fatal(err)
856                         }
857
858                         err = s.syncFiles()
859                         testutil.AssertErrorEqual(t, "err", err, tc.expErr)
860                         dbg := res.Wait()
861                         // Remove random file names from result
862                         for i, x := range dbg {
863                                 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
864                         }
865                         testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
866
867                         files, err := ft.WalkDir(path)
868                         if err != nil {
869                                 t.Fatal(err)
870                         }
871                         testutil.AssertEqual(t, "files", files, tc.expFiles)
872
873                         testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
874                         testutil.AssertEqual(t, "triggers",
875                                 s.triggers, tc.expTriggers)
876                 })
877         }
878
879         os.Remove(tmpTestFilePath)
880         if !t.Failed() {
881                 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
882                 if err != nil {
883                         t.Fatal(err)
884                 }
885         }
886 }
887
888 func TestSyncFile(t *testing.T) {
889         cwd, err := os.Getwd()
890         if err != nil {
891                 t.Fatal(err)
892         }
893         defer os.Chdir(cwd)
894
895         err = os.RemoveAll("testdata")
896         if err != nil {
897                 t.Fatal(err)
898         }
899         err = os.Mkdir("testdata", 0700)
900         if err != nil {
901                 t.Fatal(err)
902         }
903
904         root := ft.File{
905                 Path: ".",
906                 Mode: fs.ModeDir | 0700,
907         }
908         user, uid, group, gid := ft.CurrentUserAndGroup()
909
910         tests := []struct {
911                 name       string
912                 req        safcm.MsgSyncReq
913                 file       *safcm.File
914                 prepare    func()
915                 expChanged bool
916                 expFiles   []ft.File
917                 expResp    safcm.MsgSyncResp
918                 expDbg     []string
919                 expErr     error
920         }{
921
922                 // NOTE: Also update MsgSyncResp in safcm test cases when
923                 // changing anything here!
924
925                 // TODO: Add tests for chown and run them only as root
926
927                 // Regular file
928
929                 {
930                         "file: create",
931                         safcm.MsgSyncReq{},
932                         &safcm.File{
933                                 Path:      "file",
934                                 Mode:      0644,
935                                 Uid:       -1,
936                                 Gid:       -1,
937                                 Data:      []byte("content\n"),
938                                 OrigGroup: "group",
939                         },
940                         nil,
941                         true,
942                         []ft.File{
943                                 root,
944                                 {
945                                         Path: "file",
946                                         Mode: 0644,
947                                         Data: []byte("content\n"),
948                                 },
949                         },
950                         safcm.MsgSyncResp{
951                                 FileChanges: []safcm.FileChange{
952                                         {
953                                                 Path:    "file",
954                                                 Created: true,
955                                                 New: safcm.FileChangeInfo{
956                                                         Mode:  0644,
957                                                         User:  user,
958                                                         Uid:   uid,
959                                                         Group: group,
960                                                         Gid:   gid,
961                                                 },
962                                         },
963                                 },
964                         },
965                         []string{
966                                 `4: sync remote: files: "file" (group): will create`,
967                                 `3: sync remote: files: "file" (group): creating`,
968                                 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
969                                 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
970                         },
971                         nil,
972                 },
973                 {
974                         "file: create (dry-run)",
975                         safcm.MsgSyncReq{
976                                 DryRun: true,
977                         },
978                         &safcm.File{
979                                 Path:      "file",
980                                 Mode:      0644,
981                                 Uid:       -1,
982                                 Gid:       -1,
983                                 Data:      []byte("content\n"),
984                                 OrigGroup: "group",
985                         },
986                         nil,
987                         true,
988                         []ft.File{root},
989                         safcm.MsgSyncResp{
990                                 FileChanges: []safcm.FileChange{
991                                         {
992                                                 Path:    "file",
993                                                 Created: true,
994                                                 New: safcm.FileChangeInfo{
995                                                         Mode:  0644,
996                                                         User:  user,
997                                                         Uid:   uid,
998                                                         Group: group,
999                                                         Gid:   gid,
1000                                                 },
1001                                         },
1002                                 },
1003                         },
1004                         []string{
1005                                 `4: sync remote: files: "file" (group): will create`,
1006                                 `3: sync remote: files: "file" (group): creating`,
1007                                 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
1008                         },
1009                         nil,
1010                 },
1011
1012                 {
1013                         "file: unchanged",
1014                         safcm.MsgSyncReq{},
1015                         &safcm.File{
1016                                 Path:      "file",
1017                                 Mode:      0644,
1018                                 Uid:       -1,
1019                                 Gid:       -1,
1020                                 Data:      []byte("content\n"),
1021                                 OrigGroup: "group",
1022                         },
1023                         func() {
1024                                 ft.CreateFile("file", "content\n", 0644)
1025                         },
1026                         false,
1027                         []ft.File{
1028                                 root,
1029                                 {
1030                                         Path: "file",
1031                                         Mode: 0644,
1032                                         Data: []byte("content\n"),
1033                                 },
1034                         },
1035                         safcm.MsgSyncResp{},
1036                         []string{
1037                                 `4: sync remote: files: "file" (group): unchanged`,
1038                         },
1039                         nil,
1040                 },
1041
1042                 {
1043                         "file: unchanged (non-default user-group)",
1044                         safcm.MsgSyncReq{},
1045                         &safcm.File{
1046                                 Path:      "file",
1047                                 Mode:      0644,
1048                                 User:      user,
1049                                 Uid:       -1,
1050                                 Group:     group,
1051                                 Gid:       -1,
1052                                 Data:      []byte("content\n"),
1053                                 OrigGroup: "group",
1054                         },
1055                         func() {
1056                                 ft.CreateFile("file", "content\n", 0644)
1057                         },
1058                         false,
1059                         []ft.File{
1060                                 root,
1061                                 {
1062                                         Path: "file",
1063                                         Mode: 0644,
1064                                         Data: []byte("content\n"),
1065                                 },
1066                         },
1067                         safcm.MsgSyncResp{},
1068                         []string{
1069                                 `4: sync remote: files: "file" (group): unchanged`,
1070                         },
1071                         nil,
1072                 },
1073
1074                 {
1075                         "file: permission",
1076                         safcm.MsgSyncReq{},
1077                         &safcm.File{
1078                                 Path:      "file",
1079                                 Mode:      0755 | fs.ModeSetuid,
1080                                 Uid:       -1,
1081                                 Gid:       -1,
1082                                 Data:      []byte("content\n"),
1083                                 OrigGroup: "group",
1084                         },
1085                         func() {
1086                                 ft.CreateFile("file", "content\n", 0755)
1087                         },
1088                         true,
1089                         []ft.File{
1090                                 root,
1091                                 {
1092                                         Path: "file",
1093                                         Mode: 0755 | fs.ModeSetuid,
1094                                         Data: []byte("content\n"),
1095                                 },
1096                         },
1097                         safcm.MsgSyncResp{
1098                                 FileChanges: []safcm.FileChange{
1099                                         {
1100                                                 Path: "file",
1101                                                 Old: safcm.FileChangeInfo{
1102                                                         Mode:  0755,
1103                                                         User:  user,
1104                                                         Uid:   uid,
1105                                                         Group: group,
1106                                                         Gid:   gid,
1107                                                 },
1108                                                 New: safcm.FileChangeInfo{
1109                                                         Mode:  0755 | fs.ModeSetuid,
1110                                                         User:  user,
1111                                                         Uid:   uid,
1112                                                         Group: group,
1113                                                         Gid:   gid,
1114                                                 },
1115                                         },
1116                                 },
1117                         },
1118                         []string{
1119                                 `4: sync remote: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
1120                                 `3: sync remote: files: "file" (group): updating`,
1121                                 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1122                                 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1123                         },
1124                         nil,
1125                 },
1126
1127                 {
1128                         "file: content",
1129                         safcm.MsgSyncReq{},
1130                         &safcm.File{
1131                                 Path:      "file",
1132                                 Mode:      0644,
1133                                 Uid:       -1,
1134                                 Gid:       -1,
1135                                 Data:      []byte("content\n"),
1136                                 OrigGroup: "group",
1137                         },
1138                         func() {
1139                                 ft.CreateFile("file", "old content\n", 0644)
1140                         },
1141                         true,
1142                         []ft.File{
1143                                 root,
1144                                 {
1145                                         Path: "file",
1146                                         Mode: 0644,
1147                                         Data: []byte("content\n"),
1148                                 },
1149                         },
1150                         safcm.MsgSyncResp{
1151                                 FileChanges: []safcm.FileChange{
1152                                         {
1153                                                 Path: "file",
1154                                                 Old: safcm.FileChangeInfo{
1155                                                         Mode:  0644,
1156                                                         User:  user,
1157                                                         Uid:   uid,
1158                                                         Group: group,
1159                                                         Gid:   gid,
1160                                                 },
1161                                                 New: safcm.FileChangeInfo{
1162                                                         Mode:  0644,
1163                                                         User:  user,
1164                                                         Uid:   uid,
1165                                                         Group: group,
1166                                                         Gid:   gid,
1167                                                 },
1168                                                 DataDiff: `@@ -1,2 +1,2 @@
1169 -old content
1170 +content
1171  
1172 `,
1173                                         },
1174                                 },
1175                         },
1176                         []string{
1177                                 `4: sync remote: files: "file" (group): content differs`,
1178                                 `3: sync remote: files: "file" (group): updating`,
1179                                 `4: sync remote: files: "file" (group): creating temporary file ".file*"`,
1180                                 `4: sync remote: files: "file" (group): renaming "./.fileRND"`,
1181                         },
1182                         nil,
1183                 },
1184
1185                 // Symbolic link
1186
1187                 {
1188                         "symlink: create",
1189                         safcm.MsgSyncReq{},
1190                         &safcm.File{
1191                                 Path:      "link",
1192                                 Mode:      fs.ModeSymlink | 0777,
1193                                 Uid:       -1,
1194                                 Gid:       -1,
1195                                 Data:      []byte("target"),
1196                                 OrigGroup: "group",
1197                         },
1198                         nil,
1199                         true,
1200                         []ft.File{
1201                                 root,
1202                                 {
1203                                         Path: "link",
1204                                         Mode: fs.ModeSymlink | 0777,
1205                                         Data: []byte("target"),
1206                                 },
1207                         },
1208                         safcm.MsgSyncResp{
1209                                 FileChanges: []safcm.FileChange{
1210                                         {
1211                                                 Path:    "link",
1212                                                 Created: true,
1213                                                 New: safcm.FileChangeInfo{
1214                                                         Mode:  fs.ModeSymlink | 0777,
1215                                                         User:  user,
1216                                                         Uid:   uid,
1217                                                         Group: group,
1218                                                         Gid:   gid,
1219                                                 },
1220                                         },
1221                                 },
1222                         },
1223                         []string{
1224                                 `4: sync remote: files: "link" (group): will create`,
1225                                 `3: sync remote: files: "link" (group): creating`,
1226                                 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1227                                 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1228                         },
1229                         nil,
1230                 },
1231                 {
1232                         "symlink: create (conflict)",
1233                         safcm.MsgSyncReq{},
1234                         &safcm.File{
1235                                 Path:      "link",
1236                                 Mode:      fs.ModeSymlink | 0777,
1237                                 Uid:       -1,
1238                                 Gid:       -1,
1239                                 Data:      []byte("target"),
1240                                 OrigGroup: "group",
1241                         },
1242                         func() {
1243                                 ft.CreateFile(".link8717895732742165505", "", 0600)
1244                         },
1245                         true,
1246                         []ft.File{
1247                                 root,
1248                                 {
1249                                         Path: ".link8717895732742165505",
1250                                         Mode: 0600,
1251                                         Data: []byte(""),
1252                                 },
1253                                 {
1254                                         Path: "link",
1255                                         Mode: fs.ModeSymlink | 0777,
1256                                         Data: []byte("target"),
1257                                 },
1258                         },
1259                         safcm.MsgSyncResp{
1260                                 FileChanges: []safcm.FileChange{
1261                                         {
1262                                                 Path:    "link",
1263                                                 Created: true,
1264                                                 New: safcm.FileChangeInfo{
1265                                                         Mode:  fs.ModeSymlink | 0777,
1266                                                         User:  user,
1267                                                         Uid:   uid,
1268                                                         Group: group,
1269                                                         Gid:   gid,
1270                                                 },
1271                                         },
1272                                 },
1273                         },
1274                         []string{
1275                                 `4: sync remote: files: "link" (group): will create`,
1276                                 `3: sync remote: files: "link" (group): creating`,
1277                                 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1278                                 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1279                                 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1280                         },
1281                         nil,
1282                 },
1283                 {
1284                         "symlink: create (dry-run)",
1285                         safcm.MsgSyncReq{
1286                                 DryRun: true,
1287                         },
1288                         &safcm.File{
1289                                 Path:      "link",
1290                                 Mode:      fs.ModeSymlink | 0777,
1291                                 Uid:       -1,
1292                                 Gid:       -1,
1293                                 Data:      []byte("target"),
1294                                 OrigGroup: "group",
1295                         },
1296                         nil,
1297                         true,
1298                         []ft.File{root},
1299                         safcm.MsgSyncResp{
1300                                 FileChanges: []safcm.FileChange{
1301                                         {
1302                                                 Path:    "link",
1303                                                 Created: true,
1304                                                 New: safcm.FileChangeInfo{
1305                                                         Mode:  fs.ModeSymlink | 0777,
1306                                                         User:  user,
1307                                                         Uid:   uid,
1308                                                         Group: group,
1309                                                         Gid:   gid,
1310                                                 },
1311                                         },
1312                                 },
1313                         },
1314                         []string{
1315                                 `4: sync remote: files: "link" (group): will create`,
1316                                 `3: sync remote: files: "link" (group): creating`,
1317                                 `4: sync remote: files: "link" (group): dry-run, skipping changes`,
1318                         },
1319                         nil,
1320                 },
1321
1322                 {
1323                         "symlink: unchanged",
1324                         safcm.MsgSyncReq{},
1325                         &safcm.File{
1326                                 Path:      "link",
1327                                 Mode:      fs.ModeSymlink | 0777,
1328                                 Uid:       -1,
1329                                 Gid:       -1,
1330                                 Data:      []byte("target"),
1331                                 OrigGroup: "group",
1332                         },
1333                         func() {
1334                                 ft.CreateSymlink("link", "target")
1335                         },
1336                         false,
1337                         []ft.File{
1338                                 root,
1339                                 {
1340                                         Path: "link",
1341                                         Mode: fs.ModeSymlink | 0777,
1342                                         Data: []byte("target"),
1343                                 },
1344                         },
1345                         safcm.MsgSyncResp{},
1346                         []string{
1347                                 `4: sync remote: files: "link" (group): unchanged`,
1348                         },
1349                         nil,
1350                 },
1351
1352                 {
1353                         "symlink: content",
1354                         safcm.MsgSyncReq{},
1355                         &safcm.File{
1356                                 Path:      "link",
1357                                 Mode:      fs.ModeSymlink | 0777,
1358                                 Uid:       -1,
1359                                 Gid:       -1,
1360                                 Data:      []byte("target"),
1361                                 OrigGroup: "group",
1362                         },
1363                         func() {
1364                                 ft.CreateSymlink("link", "old-target")
1365                         },
1366                         true,
1367                         []ft.File{
1368                                 root,
1369                                 {
1370                                         Path: "link",
1371                                         Mode: fs.ModeSymlink | 0777,
1372                                         Data: []byte("target"),
1373                                 },
1374                         },
1375                         safcm.MsgSyncResp{
1376                                 FileChanges: []safcm.FileChange{
1377                                         {
1378                                                 Path: "link",
1379                                                 Old: safcm.FileChangeInfo{
1380                                                         Mode:  fs.ModeSymlink | 0777,
1381                                                         User:  user,
1382                                                         Uid:   uid,
1383                                                         Group: group,
1384                                                         Gid:   gid,
1385                                                 },
1386                                                 New: safcm.FileChangeInfo{
1387                                                         Mode:  fs.ModeSymlink | 0777,
1388                                                         User:  user,
1389                                                         Uid:   uid,
1390                                                         Group: group,
1391                                                         Gid:   gid,
1392                                                 },
1393                                                 DataDiff: `@@ -1 +1 @@
1394 -old-target
1395 +target
1396 `,
1397                                         },
1398                                 },
1399                         },
1400                         []string{
1401                                 `4: sync remote: files: "link" (group): content differs`,
1402                                 `3: sync remote: files: "link" (group): updating`,
1403                                 `4: sync remote: files: "link" (group): creating temporary symlink ".linkRND"`,
1404                                 `4: sync remote: files: "link" (group): renaming ".linkRND"`,
1405                         },
1406                         nil,
1407                 },
1408
1409                 // Directory
1410
1411                 {
1412                         "directory: create",
1413                         safcm.MsgSyncReq{},
1414                         &safcm.File{
1415                                 Path:      "dir",
1416                                 Mode:      fs.ModeDir | 0705,
1417                                 Uid:       -1,
1418                                 Gid:       -1,
1419                                 OrigGroup: "group",
1420                         },
1421                         nil,
1422                         true,
1423                         []ft.File{
1424                                 root,
1425                                 {
1426                                         Path: "dir",
1427                                         Mode: fs.ModeDir | 0705,
1428                                 },
1429                         },
1430                         safcm.MsgSyncResp{
1431                                 FileChanges: []safcm.FileChange{
1432                                         {
1433                                                 Path:    "dir",
1434                                                 Created: true,
1435                                                 New: safcm.FileChangeInfo{
1436                                                         Mode:  fs.ModeDir | 0705,
1437                                                         User:  user,
1438                                                         Uid:   uid,
1439                                                         Group: group,
1440                                                         Gid:   gid,
1441                                                 },
1442                                         },
1443                                 },
1444                         },
1445                         []string{
1446                                 `4: sync remote: files: "dir" (group): will create`,
1447                                 `3: sync remote: files: "dir" (group): creating`,
1448                                 `4: sync remote: files: "dir" (group): creating directory`,
1449                                 `4: sync remote: files: "dir" (group): chmodding drwx---r-x`,
1450                                 fmt.Sprintf(`4: sync remote: files: "dir" (group): chowning %d/%d`, uid, gid),
1451                         },
1452                         nil,
1453                 },
1454                 {
1455                         "directory: create (dry-run)",
1456                         safcm.MsgSyncReq{
1457                                 DryRun: true,
1458                         },
1459                         &safcm.File{
1460                                 Path:      "dir",
1461                                 Mode:      fs.ModeDir | 0644,
1462                                 Uid:       -1,
1463                                 Gid:       -1,
1464                                 OrigGroup: "group",
1465                         },
1466                         nil,
1467                         true,
1468                         []ft.File{root},
1469                         safcm.MsgSyncResp{
1470                                 FileChanges: []safcm.FileChange{
1471                                         {
1472                                                 Path:    "dir",
1473                                                 Created: true,
1474                                                 New: safcm.FileChangeInfo{
1475                                                         Mode:  fs.ModeDir | 0644,
1476                                                         User:  user,
1477                                                         Uid:   uid,
1478                                                         Group: group,
1479                                                         Gid:   gid,
1480                                                 },
1481                                         },
1482                                 },
1483                         },
1484                         []string{
1485                                 `4: sync remote: files: "dir" (group): will create`,
1486                                 `3: sync remote: files: "dir" (group): creating`,
1487                                 `4: sync remote: files: "dir" (group): dry-run, skipping changes`,
1488                         },
1489                         nil,
1490                 },
1491
1492                 {
1493                         "directory: unchanged",
1494                         safcm.MsgSyncReq{},
1495                         &safcm.File{
1496                                 Path:      "dir",
1497                                 Mode:      fs.ModeDir | 0755,
1498                                 Uid:       -1,
1499                                 Gid:       -1,
1500                                 OrigGroup: "group",
1501                         },
1502                         func() {
1503                                 ft.CreateDirectory("dir", 0755)
1504                         },
1505                         false,
1506                         []ft.File{
1507                                 root,
1508                                 {
1509                                         Path: "dir",
1510                                         Mode: fs.ModeDir | 0755,
1511                                 },
1512                         },
1513                         safcm.MsgSyncResp{},
1514                         []string{
1515                                 `4: sync remote: files: "dir" (group): unchanged`,
1516                         },
1517                         nil,
1518                 },
1519
1520                 {
1521                         "directory: permission",
1522                         safcm.MsgSyncReq{},
1523                         &safcm.File{
1524                                 Path:      "dir",
1525                                 Mode:      fs.ModeDir | 0755 | fs.ModeSetgid,
1526                                 Uid:       -1,
1527                                 Gid:       -1,
1528                                 OrigGroup: "group",
1529                         },
1530                         func() {
1531                                 ft.CreateDirectory("dir", 0500|fs.ModeSticky)
1532                         },
1533                         true,
1534                         []ft.File{
1535                                 root,
1536                                 {
1537                                         Path: "dir",
1538                                         Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1539                                 },
1540                         },
1541                         safcm.MsgSyncResp{
1542                                 FileChanges: []safcm.FileChange{
1543                                         {
1544                                                 Path: "dir",
1545                                                 Old: safcm.FileChangeInfo{
1546                                                         Mode:  fs.ModeDir | 0500 | fs.ModeSticky,
1547                                                         User:  user,
1548                                                         Uid:   uid,
1549                                                         Group: group,
1550                                                         Gid:   gid,
1551                                                 },
1552                                                 New: safcm.FileChangeInfo{
1553                                                         Mode:  fs.ModeDir | 0755 | fs.ModeSetgid,
1554                                                         User:  user,
1555                                                         Uid:   uid,
1556                                                         Group: group,
1557                                                         Gid:   gid,
1558                                                 },
1559                                         },
1560                                 },
1561                         },
1562                         []string{
1563                                 `4: sync remote: files: "dir" (group): permission differs dtr-x------ -> dgrwxr-xr-x`,
1564                                 `3: sync remote: files: "dir" (group): updating`,
1565                                 `4: sync remote: files: "dir" (group): chmodding dgrwxr-xr-x`,
1566                         },
1567                         nil,
1568                 },
1569
1570                 // Type changes
1571
1572                 {
1573                         "change: file to directory",
1574                         safcm.MsgSyncReq{},
1575                         &safcm.File{
1576                                 Path:      "path",
1577                                 Mode:      fs.ModeDir | 0751,
1578                                 Uid:       -1,
1579                                 Gid:       -1,
1580                                 OrigGroup: "group",
1581                         },
1582                         func() {
1583                                 ft.CreateFile("path", "content\n", 0644)
1584                         },
1585                         true,
1586                         []ft.File{
1587                                 root,
1588                                 {
1589                                         Path: "path",
1590                                         Mode: fs.ModeDir | 0751,
1591                                 },
1592                         },
1593                         safcm.MsgSyncResp{
1594                                 FileChanges: []safcm.FileChange{
1595                                         {
1596                                                 Path: "path",
1597                                                 Old: safcm.FileChangeInfo{
1598                                                         Mode:  0644,
1599                                                         User:  user,
1600                                                         Uid:   uid,
1601                                                         Group: group,
1602                                                         Gid:   gid,
1603                                                 },
1604                                                 New: safcm.FileChangeInfo{
1605                                                         Mode:  fs.ModeDir | 0751,
1606                                                         User:  user,
1607                                                         Uid:   uid,
1608                                                         Group: group,
1609                                                         Gid:   gid,
1610                                                 },
1611                                                 DataDiff: `@@ -1,2 +1 @@
1612 -content
1613  
1614 `,
1615                                         },
1616                                 },
1617                         },
1618                         []string{
1619                                 `4: sync remote: files: "path" (group): type differs ---------- -> d---------`,
1620                                 `3: sync remote: files: "path" (group): updating`,
1621                                 `4: sync remote: files: "path" (group): removing (due to type change)`,
1622                                 `4: sync remote: files: "path" (group): creating directory`,
1623                                 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1624                                 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1625                         },
1626                         nil,
1627                 },
1628
1629                 {
1630                         "change: file to symlink",
1631                         safcm.MsgSyncReq{},
1632                         &safcm.File{
1633                                 Path:      "path",
1634                                 Mode:      fs.ModeSymlink | 0777,
1635                                 Uid:       -1,
1636                                 Gid:       -1,
1637                                 OrigGroup: "group",
1638                                 Data:      []byte("target"),
1639                         },
1640                         func() {
1641                                 ft.CreateFile("path", "content\n", 0644)
1642                         },
1643                         true,
1644                         []ft.File{
1645                                 root,
1646                                 {
1647                                         Path: "path",
1648                                         Mode: fs.ModeSymlink | 0777,
1649                                         Data: []byte("target"),
1650                                 },
1651                         },
1652                         safcm.MsgSyncResp{
1653                                 FileChanges: []safcm.FileChange{
1654                                         {
1655                                                 Path: "path",
1656                                                 Old: safcm.FileChangeInfo{
1657                                                         Mode:  0644,
1658                                                         User:  user,
1659                                                         Uid:   uid,
1660                                                         Group: group,
1661                                                         Gid:   gid,
1662                                                 },
1663                                                 New: safcm.FileChangeInfo{
1664                                                         Mode:  fs.ModeSymlink | 0777,
1665                                                         User:  user,
1666                                                         Uid:   uid,
1667                                                         Group: group,
1668                                                         Gid:   gid,
1669                                                 },
1670                                                 DataDiff: `@@ -1,2 +1 @@
1671 -content
1672 -
1673 +target
1674 `,
1675                                         },
1676                                 },
1677                         },
1678                         []string{
1679                                 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
1680                                 `3: sync remote: files: "path" (group): updating`,
1681                                 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1682                                 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1683                         },
1684                         nil,
1685                 },
1686
1687                 {
1688                         "change: symlink to file",
1689                         safcm.MsgSyncReq{},
1690                         &safcm.File{
1691                                 Path:      "path",
1692                                 Mode:      0640,
1693                                 Uid:       -1,
1694                                 Gid:       -1,
1695                                 OrigGroup: "group",
1696                                 Data:      []byte("content\n"),
1697                         },
1698                         func() {
1699                                 ft.CreateSymlink("path", "target")
1700                         },
1701                         true,
1702                         []ft.File{
1703                                 root,
1704                                 {
1705                                         Path: "path",
1706                                         Mode: 0640,
1707                                         Data: []byte("content\n"),
1708                                 },
1709                         },
1710                         safcm.MsgSyncResp{
1711                                 FileChanges: []safcm.FileChange{
1712                                         {
1713                                                 Path: "path",
1714                                                 Old: safcm.FileChangeInfo{
1715                                                         Mode:  fs.ModeSymlink | 0777,
1716                                                         User:  user,
1717                                                         Uid:   uid,
1718                                                         Group: group,
1719                                                         Gid:   gid,
1720                                                 },
1721                                                 New: safcm.FileChangeInfo{
1722                                                         Mode:  0640,
1723                                                         User:  user,
1724                                                         Uid:   uid,
1725                                                         Group: group,
1726                                                         Gid:   gid,
1727                                                 },
1728                                                 DataDiff: `@@ -1 +1,2 @@
1729 -target
1730 +content
1731 +
1732 `,
1733                                         },
1734                                 },
1735                         },
1736                         []string{
1737                                 `4: sync remote: files: "path" (group): type differs L--------- -> ----------`,
1738                                 `3: sync remote: files: "path" (group): updating`,
1739                                 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1740                                 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1741                         },
1742                         nil,
1743                 },
1744
1745                 {
1746                         "change: symlink to directory",
1747                         safcm.MsgSyncReq{},
1748                         &safcm.File{
1749                                 Path:      "path",
1750                                 Mode:      fs.ModeDir | 0751,
1751                                 Uid:       -1,
1752                                 Gid:       -1,
1753                                 OrigGroup: "group",
1754                         },
1755                         func() {
1756                                 ft.CreateSymlink("path", "target")
1757                         },
1758                         true,
1759                         []ft.File{
1760                                 root,
1761                                 {
1762                                         Path: "path",
1763                                         Mode: fs.ModeDir | 0751,
1764                                 },
1765                         },
1766                         safcm.MsgSyncResp{
1767                                 FileChanges: []safcm.FileChange{
1768                                         {
1769                                                 Path: "path",
1770                                                 Old: safcm.FileChangeInfo{
1771                                                         Mode:  fs.ModeSymlink | 0777,
1772                                                         User:  user,
1773                                                         Uid:   uid,
1774                                                         Group: group,
1775                                                         Gid:   gid,
1776                                                 },
1777                                                 New: safcm.FileChangeInfo{
1778                                                         Mode:  fs.ModeDir | 0751,
1779                                                         User:  user,
1780                                                         Uid:   uid,
1781                                                         Group: group,
1782                                                         Gid:   gid,
1783                                                 },
1784                                                 DataDiff: `@@ -1 +1 @@
1785 -target
1786 +
1787 `,
1788                                         },
1789                                 },
1790                         },
1791                         []string{
1792                                 `4: sync remote: files: "path" (group): type differs L--------- -> d---------`,
1793                                 `3: sync remote: files: "path" (group): updating`,
1794                                 `4: sync remote: files: "path" (group): removing (due to type change)`,
1795                                 `4: sync remote: files: "path" (group): creating directory`,
1796                                 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
1797                                 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
1798                         },
1799                         nil,
1800                 },
1801
1802                 {
1803                         "change: directory to file",
1804                         safcm.MsgSyncReq{},
1805                         &safcm.File{
1806                                 Path:      "path",
1807                                 Mode:      0666,
1808                                 Uid:       -1,
1809                                 Gid:       -1,
1810                                 OrigGroup: "group",
1811                                 Data:      []byte("content\n"),
1812                         },
1813                         func() {
1814                                 ft.CreateDirectory("path", 0777)
1815                         },
1816                         true,
1817                         []ft.File{
1818                                 root,
1819                                 {
1820                                         Path: "path",
1821                                         Mode: 0666,
1822                                         Data: []byte("content\n"),
1823                                 },
1824                         },
1825                         safcm.MsgSyncResp{
1826                                 FileChanges: []safcm.FileChange{
1827                                         {
1828                                                 Path: "path",
1829                                                 Old: safcm.FileChangeInfo{
1830                                                         Mode:  fs.ModeDir | 0777,
1831                                                         User:  user,
1832                                                         Uid:   uid,
1833                                                         Group: group,
1834                                                         Gid:   gid,
1835                                                 },
1836                                                 New: safcm.FileChangeInfo{
1837                                                         Mode:  0666,
1838                                                         User:  user,
1839                                                         Uid:   uid,
1840                                                         Group: group,
1841                                                         Gid:   gid,
1842                                                 },
1843                                         },
1844                                 },
1845                         },
1846                         []string{
1847                                 `4: sync remote: files: "path" (group): type differs d--------- -> ----------`,
1848                                 `3: sync remote: files: "path" (group): updating`,
1849                                 `4: sync remote: files: "path" (group): removing (due to type change)`,
1850                                 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1851                                 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1852                         },
1853                         nil,
1854                 },
1855
1856                 {
1857                         "change: directory to symlink",
1858                         safcm.MsgSyncReq{},
1859                         &safcm.File{
1860                                 Path:      "path",
1861                                 Mode:      fs.ModeSymlink | 0777,
1862                                 Uid:       -1,
1863                                 Gid:       -1,
1864                                 OrigGroup: "group",
1865                                 Data:      []byte("target"),
1866                         },
1867                         func() {
1868                                 ft.CreateDirectory("path", 0777)
1869                         },
1870                         true,
1871                         []ft.File{
1872                                 root,
1873                                 {
1874                                         Path: "path",
1875                                         Mode: fs.ModeSymlink | 0777,
1876                                         Data: []byte("target"),
1877                                 },
1878                         },
1879                         safcm.MsgSyncResp{
1880                                 FileChanges: []safcm.FileChange{
1881                                         {
1882                                                 Path: "path",
1883                                                 Old: safcm.FileChangeInfo{
1884                                                         Mode:  fs.ModeDir | 0777,
1885                                                         User:  user,
1886                                                         Uid:   uid,
1887                                                         Group: group,
1888                                                         Gid:   gid,
1889                                                 },
1890                                                 New: safcm.FileChangeInfo{
1891                                                         Mode:  fs.ModeSymlink | 0777,
1892                                                         User:  user,
1893                                                         Uid:   uid,
1894                                                         Group: group,
1895                                                         Gid:   gid,
1896                                                 },
1897                                         },
1898                                 },
1899                         },
1900                         []string{
1901                                 `4: sync remote: files: "path" (group): type differs d--------- -> L---------`,
1902                                 `3: sync remote: files: "path" (group): updating`,
1903                                 `4: sync remote: files: "path" (group): removing (due to type change)`,
1904                                 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
1905                                 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
1906                         },
1907                         nil,
1908                 },
1909
1910                 {
1911                         "change: other to file",
1912                         safcm.MsgSyncReq{},
1913                         &safcm.File{
1914                                 Path:      "path",
1915                                 Mode:      0640,
1916                                 Uid:       -1,
1917                                 Gid:       -1,
1918                                 OrigGroup: "group",
1919                                 Data:      []byte("content\n"),
1920                         },
1921                         func() {
1922                                 ft.CreateFifo("path", 0666)
1923                         },
1924                         true,
1925                         []ft.File{
1926                                 root,
1927                                 {
1928                                         Path: "path",
1929                                         Mode: 0640,
1930                                         Data: []byte("content\n"),
1931                                 },
1932                         },
1933                         safcm.MsgSyncResp{
1934                                 FileChanges: []safcm.FileChange{
1935                                         {
1936                                                 Path: "path",
1937                                                 Old: safcm.FileChangeInfo{
1938                                                         Mode:  fs.ModeNamedPipe | 0666,
1939                                                         User:  user,
1940                                                         Uid:   uid,
1941                                                         Group: group,
1942                                                         Gid:   gid,
1943                                                 },
1944                                                 New: safcm.FileChangeInfo{
1945                                                         Mode:  0640,
1946                                                         User:  user,
1947                                                         Uid:   uid,
1948                                                         Group: group,
1949                                                         Gid:   gid,
1950                                                 },
1951                                         },
1952                                 },
1953                         },
1954                         []string{
1955                                 `4: sync remote: files: "path" (group): type differs p--------- -> ----------`,
1956                                 `3: sync remote: files: "path" (group): updating`,
1957                                 `4: sync remote: files: "path" (group): creating temporary file ".path*"`,
1958                                 `4: sync remote: files: "path" (group): renaming "./.pathRND"`,
1959                         },
1960                         nil,
1961                 },
1962
1963                 {
1964                         "change: other to symlink",
1965                         safcm.MsgSyncReq{},
1966                         &safcm.File{
1967                                 Path:      "path",
1968                                 Mode:      fs.ModeSymlink | 0777,
1969                                 Uid:       -1,
1970                                 Gid:       -1,
1971                                 OrigGroup: "group",
1972                                 Data:      []byte("target"),
1973                         },
1974                         func() {
1975                                 ft.CreateFifo("path", 0666)
1976                         },
1977                         true,
1978                         []ft.File{
1979                                 root,
1980                                 {
1981                                         Path: "path",
1982                                         Mode: fs.ModeSymlink | 0777,
1983                                         Data: []byte("target"),
1984                                 },
1985                         },
1986                         safcm.MsgSyncResp{
1987                                 FileChanges: []safcm.FileChange{
1988                                         {
1989                                                 Path: "path",
1990                                                 Old: safcm.FileChangeInfo{
1991                                                         Mode:  fs.ModeNamedPipe | 0666,
1992                                                         User:  user,
1993                                                         Uid:   uid,
1994                                                         Group: group,
1995                                                         Gid:   gid,
1996                                                 },
1997                                                 New: safcm.FileChangeInfo{
1998                                                         Mode:  fs.ModeSymlink | 0777,
1999                                                         User:  user,
2000                                                         Uid:   uid,
2001                                                         Group: group,
2002                                                         Gid:   gid,
2003                                                 },
2004                                         },
2005                                 },
2006                         },
2007                         []string{
2008                                 `4: sync remote: files: "path" (group): type differs p--------- -> L---------`,
2009                                 `3: sync remote: files: "path" (group): updating`,
2010                                 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2011                                 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2012                         },
2013                         nil,
2014                 },
2015
2016                 {
2017                         "change: other to directory",
2018                         safcm.MsgSyncReq{},
2019                         &safcm.File{
2020                                 Path:      "path",
2021                                 Mode:      fs.ModeDir | 0751,
2022                                 Uid:       -1,
2023                                 Gid:       -1,
2024                                 OrigGroup: "group",
2025                         },
2026                         func() {
2027                                 ft.CreateFifo("path", 0666)
2028                         },
2029                         true,
2030                         []ft.File{
2031                                 root,
2032                                 {
2033                                         Path: "path",
2034                                         Mode: fs.ModeDir | 0751,
2035                                 },
2036                         },
2037                         safcm.MsgSyncResp{
2038                                 FileChanges: []safcm.FileChange{
2039                                         {
2040                                                 Path: "path",
2041                                                 Old: safcm.FileChangeInfo{
2042                                                         Mode:  fs.ModeNamedPipe | 0666,
2043                                                         User:  user,
2044                                                         Uid:   uid,
2045                                                         Group: group,
2046                                                         Gid:   gid,
2047                                                 },
2048                                                 New: safcm.FileChangeInfo{
2049                                                         Mode:  fs.ModeDir | 0751,
2050                                                         User:  user,
2051                                                         Uid:   uid,
2052                                                         Group: group,
2053                                                         Gid:   gid,
2054                                                 },
2055                                         },
2056                                 },
2057                         },
2058                         []string{
2059                                 `4: sync remote: files: "path" (group): type differs p--------- -> d---------`,
2060                                 `3: sync remote: files: "path" (group): updating`,
2061                                 `4: sync remote: files: "path" (group): removing (due to type change)`,
2062                                 `4: sync remote: files: "path" (group): creating directory`,
2063                                 `4: sync remote: files: "path" (group): chmodding drwxr-x--x`,
2064                                 fmt.Sprintf(`4: sync remote: files: "path" (group): chowning %d/%d`, uid, gid),
2065                         },
2066                         nil,
2067                 },
2068
2069                 {
2070                         "change: file to symlink (same content)",
2071                         safcm.MsgSyncReq{},
2072                         &safcm.File{
2073                                 Path:      "path",
2074                                 Mode:      fs.ModeSymlink | 0777,
2075                                 Uid:       -1,
2076                                 Gid:       -1,
2077                                 OrigGroup: "group",
2078                                 Data:      []byte("target"),
2079                         },
2080                         func() {
2081                                 ft.CreateFile("path", "target", 0644)
2082                         },
2083                         true,
2084                         []ft.File{
2085                                 root,
2086                                 {
2087                                         Path: "path",
2088                                         Mode: fs.ModeSymlink | 0777,
2089                                         Data: []byte("target"),
2090                                 },
2091                         },
2092                         safcm.MsgSyncResp{
2093                                 FileChanges: []safcm.FileChange{
2094                                         {
2095                                                 Path: "path",
2096                                                 Old: safcm.FileChangeInfo{
2097                                                         Mode:  0644,
2098                                                         User:  user,
2099                                                         Uid:   uid,
2100                                                         Group: group,
2101                                                         Gid:   gid,
2102                                                 },
2103                                                 New: safcm.FileChangeInfo{
2104                                                         Mode:  fs.ModeSymlink | 0777,
2105                                                         User:  user,
2106                                                         Uid:   uid,
2107                                                         Group: group,
2108                                                         Gid:   gid,
2109                                                 },
2110                                         },
2111                                 },
2112                         },
2113                         []string{
2114                                 `4: sync remote: files: "path" (group): type differs ---------- -> L---------`,
2115                                 `3: sync remote: files: "path" (group): updating`,
2116                                 `4: sync remote: files: "path" (group): creating temporary symlink ".pathRND"`,
2117                                 `4: sync remote: files: "path" (group): renaming ".pathRND"`,
2118                         },
2119                         nil,
2120                 },
2121
2122                 // Diffs
2123
2124                 {
2125                         "diff: textual",
2126                         safcm.MsgSyncReq{
2127                                 DryRun: true,
2128                         },
2129                         &safcm.File{
2130                                 Path: "file",
2131                                 Mode: 0644,
2132                                 Uid:  -1,
2133                                 Gid:  -1,
2134                                 Data: []byte(`
2135 this
2136 is
2137 a
2138 simple
2139 file
2140 `),
2141                                 OrigGroup: "group",
2142                         },
2143                         func() {
2144                                 ft.CreateFile("file", `this
2145 is
2146 file
2147 !
2148 `, 0644)
2149                         },
2150                         true,
2151                         []ft.File{
2152                                 root,
2153                                 {
2154                                         Path: "file",
2155                                         Mode: 0644,
2156                                         Data: []byte(`this
2157 is
2158 file
2159 !
2160 `),
2161                                 },
2162                         },
2163                         safcm.MsgSyncResp{
2164                                 FileChanges: []safcm.FileChange{
2165                                         {
2166                                                 Path: "file",
2167                                                 Old: safcm.FileChangeInfo{
2168                                                         Mode:  0644,
2169                                                         User:  user,
2170                                                         Uid:   uid,
2171                                                         Group: group,
2172                                                         Gid:   gid,
2173                                                 },
2174                                                 New: safcm.FileChangeInfo{
2175                                                         Mode:  0644,
2176                                                         User:  user,
2177                                                         Uid:   uid,
2178                                                         Group: group,
2179                                                         Gid:   gid,
2180                                                 },
2181                                                 DataDiff: `@@ -1,5 +1,7 @@
2182 +
2183  this
2184  is
2185 +a
2186 +simple
2187  file
2188 -!
2189  
2190 `,
2191                                         },
2192                                 },
2193                         },
2194                         []string{
2195                                 `4: sync remote: files: "file" (group): content differs`,
2196                                 `3: sync remote: files: "file" (group): updating`,
2197                                 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2198                         },
2199                         nil,
2200                 },
2201
2202                 {
2203                         "diff: binary both",
2204                         safcm.MsgSyncReq{
2205                                 DryRun: true,
2206                         },
2207                         &safcm.File{
2208                                 Path:      "file",
2209                                 Mode:      0644,
2210                                 Uid:       -1,
2211                                 Gid:       -1,
2212                                 Data:      []byte("\x00\x01\x02\x03"),
2213                                 OrigGroup: "group",
2214                         },
2215                         func() {
2216                                 ft.CreateFile("file", "\x00\x01\x02", 0644)
2217                         },
2218                         true,
2219                         []ft.File{
2220                                 root,
2221                                 {
2222                                         Path: "file",
2223                                         Mode: 0644,
2224                                         Data: []byte("\x00\x01\x02"),
2225                                 },
2226                         },
2227                         safcm.MsgSyncResp{
2228                                 FileChanges: []safcm.FileChange{
2229                                         {
2230                                                 Path: "file",
2231                                                 Old: safcm.FileChangeInfo{
2232                                                         Mode:  0644,
2233                                                         User:  user,
2234                                                         Uid:   uid,
2235                                                         Group: group,
2236                                                         Gid:   gid,
2237                                                 },
2238                                                 New: safcm.FileChangeInfo{
2239                                                         Mode:  0644,
2240                                                         User:  user,
2241                                                         Uid:   uid,
2242                                                         Group: group,
2243                                                         Gid:   gid,
2244                                                 },
2245                                                 DataDiff: "Binary files differ, cannot show diff",
2246                                         },
2247                                 },
2248                         },
2249                         []string{
2250                                 `4: sync remote: files: "file" (group): content differs`,
2251                                 `3: sync remote: files: "file" (group): updating`,
2252                                 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2253                         },
2254                         nil,
2255                 },
2256
2257                 {
2258                         "diff: binary old",
2259                         safcm.MsgSyncReq{
2260                                 DryRun: true,
2261                         },
2262                         &safcm.File{
2263                                 Path:      "file",
2264                                 Mode:      0644,
2265                                 Uid:       -1,
2266                                 Gid:       -1,
2267                                 Data:      []byte("content\n"),
2268                                 OrigGroup: "group",
2269                         },
2270                         func() {
2271                                 ft.CreateFile("file", "\x00\x01\x02", 0644)
2272                         },
2273                         true,
2274                         []ft.File{
2275                                 root,
2276                                 {
2277                                         Path: "file",
2278                                         Mode: 0644,
2279                                         Data: []byte("\x00\x01\x02"),
2280                                 },
2281                         },
2282                         safcm.MsgSyncResp{
2283                                 FileChanges: []safcm.FileChange{
2284                                         {
2285                                                 Path: "file",
2286                                                 Old: safcm.FileChangeInfo{
2287                                                         Mode:  0644,
2288                                                         User:  user,
2289                                                         Uid:   uid,
2290                                                         Group: group,
2291                                                         Gid:   gid,
2292                                                 },
2293                                                 New: safcm.FileChangeInfo{
2294                                                         Mode:  0644,
2295                                                         User:  user,
2296                                                         Uid:   uid,
2297                                                         Group: group,
2298                                                         Gid:   gid,
2299                                                 },
2300                                                 DataDiff: `@@ -1,2 +1,2 @@
2301 -<binary content>
2302 +content
2303  
2304 `,
2305                                         },
2306                                 },
2307                         },
2308                         []string{
2309                                 `4: sync remote: files: "file" (group): content differs`,
2310                                 `3: sync remote: files: "file" (group): updating`,
2311                                 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2312                         },
2313                         nil,
2314                 },
2315
2316                 {
2317                         "diff: binary new",
2318                         safcm.MsgSyncReq{
2319                                 DryRun: true,
2320                         },
2321                         &safcm.File{
2322                                 Path:      "file",
2323                                 Mode:      0644,
2324                                 Uid:       -1,
2325                                 Gid:       -1,
2326                                 Data:      []byte("\x00\x01\x02\x03"),
2327                                 OrigGroup: "group",
2328                         },
2329                         func() {
2330                                 ft.CreateFile("file", "content\n", 0644)
2331                         },
2332                         true,
2333                         []ft.File{
2334                                 root,
2335                                 {
2336                                         Path: "file",
2337                                         Mode: 0644,
2338                                         Data: []byte("content\n"),
2339                                 },
2340                         },
2341                         safcm.MsgSyncResp{
2342                                 FileChanges: []safcm.FileChange{
2343                                         {
2344                                                 Path: "file",
2345                                                 Old: safcm.FileChangeInfo{
2346                                                         Mode:  0644,
2347                                                         User:  user,
2348                                                         Uid:   uid,
2349                                                         Group: group,
2350                                                         Gid:   gid,
2351                                                 },
2352                                                 New: safcm.FileChangeInfo{
2353                                                         Mode:  0644,
2354                                                         User:  user,
2355                                                         Uid:   uid,
2356                                                         Group: group,
2357                                                         Gid:   gid,
2358                                                 },
2359                                                 DataDiff: `@@ -1,2 +1,2 @@
2360 -content
2361 +<binary content>
2362  
2363 `,
2364                                         },
2365                                 },
2366                         },
2367                         []string{
2368                                 `4: sync remote: files: "file" (group): content differs`,
2369                                 `3: sync remote: files: "file" (group): updating`,
2370                                 `4: sync remote: files: "file" (group): dry-run, skipping changes`,
2371                         },
2372                         nil,
2373                 },
2374         }
2375
2376         for _, tc := range tests {
2377                 t.Run(tc.name, func(t *testing.T) {
2378                         // Create separate test directory for each test case
2379                         path := filepath.Join(cwd, "testdata", "file-"+tc.name)
2380                         err := os.Mkdir(path, 0700)
2381                         if err != nil {
2382                                 t.Fatal(err)
2383                         }
2384                         err = os.Chdir(path)
2385                         if err != nil {
2386                                 t.Fatal(err)
2387                         }
2388
2389                         if tc.prepare != nil {
2390                                 tc.prepare()
2391                         }
2392
2393                         s, res := prepareSync(tc.req, &testRunner{
2394                                 t: t,
2395                         })
2396                         err = s.setDefaults()
2397                         if err != nil {
2398                                 t.Fatal(err)
2399                         }
2400
2401                         // Deterministic temporary symlink names
2402                         rand.Seed(0)
2403
2404                         var changed bool
2405                         err = s.syncFile(tc.file, &changed)
2406                         testutil.AssertErrorEqual(t, "err", err, tc.expErr)
2407                         dbg := res.Wait()
2408                         // Remove random file names from result
2409                         for i, x := range dbg {
2410                                 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
2411                         }
2412                         testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
2413
2414                         files, err := ft.WalkDir(path)
2415                         if err != nil {
2416                                 t.Fatal(err)
2417                         }
2418                         testutil.AssertEqual(t, "files", files, tc.expFiles)
2419
2420                         testutil.AssertEqual(t, "changed", changed, tc.expChanged)
2421                         testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
2422                 })
2423         }
2424
2425         if !t.Failed() {
2426                 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
2427                 if err != nil {
2428                         t.Fatal(err)
2429                 }
2430         }
2431 }