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