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