]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - cmd/safcm/sync_changes_test.go
changes: add tests for output with terminal
[safcm/safcm.git] / cmd / safcm / sync_changes_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 main
17
18 import (
19         "io/fs"
20         "testing"
21
22         "ruderich.org/simon/safcm"
23         "ruderich.org/simon/safcm/cmd/safcm/config"
24         "ruderich.org/simon/safcm/testutil"
25 )
26
27 func TestFormatFileChanges(t *testing.T) {
28         tests := []struct {
29                 name    string
30                 dryRun  bool
31                 isTTY   bool
32                 changes []safcm.FileChange
33                 exp     string
34         }{
35
36                 {
37                         "regular",
38                         false,
39                         false,
40                         []safcm.FileChange{
41                                 {
42                                         Path:    "created: file",
43                                         Created: true,
44                                         New: safcm.FileChangeInfo{
45                                                 Mode:  0644,
46                                                 User:  "user",
47                                                 Uid:   1000,
48                                                 Group: "group",
49                                                 Gid:   2000,
50                                         },
51                                 },
52                                 {
53                                         Path:    "created: link",
54                                         Created: true,
55                                         New: safcm.FileChangeInfo{
56                                                 Mode:  fs.ModeSymlink | 0777,
57                                                 User:  "user",
58                                                 Uid:   1000,
59                                                 Group: "group",
60                                                 Gid:   2000,
61                                         },
62                                 },
63                                 {
64                                         Path: "type change: file -> dir",
65                                         Old: safcm.FileChangeInfo{
66                                                 Mode:  0751,
67                                                 User:  "user",
68                                                 Uid:   1000,
69                                                 Group: "group",
70                                                 Gid:   2000,
71                                         },
72                                         New: safcm.FileChangeInfo{
73                                                 Mode:  fs.ModeDir | 0751,
74                                                 User:  "user",
75                                                 Uid:   1000,
76                                                 Group: "group",
77                                                 Gid:   2000,
78                                         },
79                                         DataDiff: `@@ -1,2 +1 @@
80 -content
81  
82 `,
83                                 },
84                                 {
85                                         Path: "user change",
86                                         Old: safcm.FileChangeInfo{
87                                                 Mode:  0755,
88                                                 User:  "user",
89                                                 Uid:   1000,
90                                                 Group: "group",
91                                                 Gid:   2000,
92                                         },
93                                         New: safcm.FileChangeInfo{
94                                                 Mode:  0755,
95                                                 User:  "user2",
96                                                 Uid:   1001,
97                                                 Group: "group",
98                                                 Gid:   2000,
99                                         },
100                                 },
101                                 {
102                                         Path: "group change",
103                                         Old: safcm.FileChangeInfo{
104                                                 Mode:  0755,
105                                                 User:  "user",
106                                                 Uid:   1000,
107                                                 Group: "group",
108                                                 Gid:   2000,
109                                         },
110                                         New: safcm.FileChangeInfo{
111                                                 Mode:  0755,
112                                                 User:  "user",
113                                                 Uid:   1000,
114                                                 Group: "group2",
115                                                 Gid:   2001,
116                                         },
117                                 },
118                                 {
119                                         Path: "mode change",
120                                         Old: safcm.FileChangeInfo{
121                                                 Mode:  0755,
122                                                 User:  "user",
123                                                 Uid:   1000,
124                                                 Group: "group",
125                                                 Gid:   2000,
126                                         },
127                                         New: safcm.FileChangeInfo{
128                                                 Mode:  0750,
129                                                 User:  "user",
130                                                 Uid:   1000,
131                                                 Group: "group",
132                                                 Gid:   2000,
133                                         },
134                                 },
135                                 {
136                                         Path: "mode change (setuid)",
137                                         Old: safcm.FileChangeInfo{
138                                                 Mode:  0755,
139                                                 User:  "user",
140                                                 Uid:   1000,
141                                                 Group: "group",
142                                                 Gid:   2000,
143                                         },
144                                         New: safcm.FileChangeInfo{
145                                                 Mode:  0755 | fs.ModeSetuid,
146                                                 User:  "user",
147                                                 Uid:   1000,
148                                                 Group: "group",
149                                                 Gid:   2000,
150                                         },
151                                 },
152                                 {
153                                         Path: "content change",
154                                         Old: safcm.FileChangeInfo{
155                                                 Mode:  0644,
156                                                 User:  "user",
157                                                 Uid:   1000,
158                                                 Group: "group",
159                                                 Gid:   2000,
160                                         },
161                                         New: safcm.FileChangeInfo{
162                                                 Mode:  0644,
163                                                 User:  "user",
164                                                 Uid:   1000,
165                                                 Group: "group",
166                                                 Gid:   2000,
167                                         },
168                                         DataDiff: `@@ -1,2 +1,2 @@
169 -old content
170 +content
171  
172 `,
173                                 },
174                                 {
175                                         Path: "multiple changes",
176                                         Old: safcm.FileChangeInfo{
177                                                 Mode:  0644,
178                                                 User:  "user",
179                                                 Uid:   1000,
180                                                 Group: "group",
181                                                 Gid:   2000,
182                                         },
183                                         New: safcm.FileChangeInfo{
184                                                 Mode:  fs.ModeDir | 0755,
185                                                 User:  "user2",
186                                                 Uid:   1001,
187                                                 Group: "group2",
188                                                 Gid:   2001,
189                                         },
190                                         DataDiff: `@@ -1,2 +1 @@
191 -content
192  
193 `,
194                                 },
195                         },
196                         `changed 9 file(s):
197 "created: file": created, file, user(1000) group(2000), 0644
198 "created: link": created, symlink, user(1000) group(2000), 0777
199 "type change: file -> dir": file -> dir
200    @@ -1,2 +1 @@
201    -content
202     
203 "user change": user(1000) group(2000) -> user2(1001) group(2000)
204 "group change": user(1000) group(2000) -> user(1000) group2(2001)
205 "mode change": 0755 -> 0750
206 "mode change (setuid)": 0755 -> 04755
207 "content change":
208    @@ -1,2 +1,2 @@
209    -old content
210    +content
211     
212 "multiple changes": file -> dir, user(1000) group(2000) -> user2(1001) group2(2001), 0644 -> 0755
213    @@ -1,2 +1 @@
214    -content
215     
216 `,
217                 },
218
219                 {
220                         "regular (tty)",
221                         false,
222                         true,
223                         []safcm.FileChange{
224                                 {
225                                         Path:    "created: file",
226                                         Created: true,
227                                         New: safcm.FileChangeInfo{
228                                                 Mode:  0644,
229                                                 User:  "user",
230                                                 Uid:   1000,
231                                                 Group: "group",
232                                                 Gid:   2000,
233                                         },
234                                 },
235                                 {
236                                         Path:    "created: link",
237                                         Created: true,
238                                         New: safcm.FileChangeInfo{
239                                                 Mode:  fs.ModeSymlink | 0777,
240                                                 User:  "user",
241                                                 Uid:   1000,
242                                                 Group: "group",
243                                                 Gid:   2000,
244                                         },
245                                 },
246                                 {
247                                         Path: "type change: file -> dir",
248                                         Old: safcm.FileChangeInfo{
249                                                 Mode:  0751,
250                                                 User:  "user",
251                                                 Uid:   1000,
252                                                 Group: "group",
253                                                 Gid:   2000,
254                                         },
255                                         New: safcm.FileChangeInfo{
256                                                 Mode:  fs.ModeDir | 0751,
257                                                 User:  "user",
258                                                 Uid:   1000,
259                                                 Group: "group",
260                                                 Gid:   2000,
261                                         },
262                                         DataDiff: `@@ -1,2 +1 @@
263 -content
264  
265 `,
266                                 },
267                                 {
268                                         Path: "user change",
269                                         Old: safcm.FileChangeInfo{
270                                                 Mode:  0755,
271                                                 User:  "user",
272                                                 Uid:   1000,
273                                                 Group: "group",
274                                                 Gid:   2000,
275                                         },
276                                         New: safcm.FileChangeInfo{
277                                                 Mode:  0755,
278                                                 User:  "user2",
279                                                 Uid:   1001,
280                                                 Group: "group",
281                                                 Gid:   2000,
282                                         },
283                                 },
284                                 {
285                                         Path: "group change",
286                                         Old: safcm.FileChangeInfo{
287                                                 Mode:  0755,
288                                                 User:  "user",
289                                                 Uid:   1000,
290                                                 Group: "group",
291                                                 Gid:   2000,
292                                         },
293                                         New: safcm.FileChangeInfo{
294                                                 Mode:  0755,
295                                                 User:  "user",
296                                                 Uid:   1000,
297                                                 Group: "group2",
298                                                 Gid:   2001,
299                                         },
300                                 },
301                                 {
302                                         Path: "mode change",
303                                         Old: safcm.FileChangeInfo{
304                                                 Mode:  0755,
305                                                 User:  "user",
306                                                 Uid:   1000,
307                                                 Group: "group",
308                                                 Gid:   2000,
309                                         },
310                                         New: safcm.FileChangeInfo{
311                                                 Mode:  0750,
312                                                 User:  "user",
313                                                 Uid:   1000,
314                                                 Group: "group",
315                                                 Gid:   2000,
316                                         },
317                                 },
318                                 {
319                                         Path: "mode change (setuid)",
320                                         Old: safcm.FileChangeInfo{
321                                                 Mode:  0755,
322                                                 User:  "user",
323                                                 Uid:   1000,
324                                                 Group: "group",
325                                                 Gid:   2000,
326                                         },
327                                         New: safcm.FileChangeInfo{
328                                                 Mode:  0755 | fs.ModeSetuid,
329                                                 User:  "user",
330                                                 Uid:   1000,
331                                                 Group: "group",
332                                                 Gid:   2000,
333                                         },
334                                 },
335                                 {
336                                         Path: "content change",
337                                         Old: safcm.FileChangeInfo{
338                                                 Mode:  0644,
339                                                 User:  "user",
340                                                 Uid:   1000,
341                                                 Group: "group",
342                                                 Gid:   2000,
343                                         },
344                                         New: safcm.FileChangeInfo{
345                                                 Mode:  0644,
346                                                 User:  "user",
347                                                 Uid:   1000,
348                                                 Group: "group",
349                                                 Gid:   2000,
350                                         },
351                                         DataDiff: `@@ -1,2 +1,2 @@
352 -old content
353 +content
354  
355 `,
356                                 },
357                                 {
358                                         Path: "multiple changes",
359                                         Old: safcm.FileChangeInfo{
360                                                 Mode:  0644,
361                                                 User:  "user",
362                                                 Uid:   1000,
363                                                 Group: "group",
364                                                 Gid:   2000,
365                                         },
366                                         New: safcm.FileChangeInfo{
367                                                 Mode:  fs.ModeDir | 0755,
368                                                 User:  "user2",
369                                                 Uid:   1001,
370                                                 Group: "group2",
371                                                 Gid:   2001,
372                                         },
373                                         DataDiff: `@@ -1,2 +1 @@
374 -content
375  
376 `,
377                                 },
378                         },
379                         "changed 9 file(s):\n\x1b[36m\"created: file\"\x1b[0m: \x1b[32mcreated\x1b[0m, file, user(1000) group(2000), 0644\n\x1b[36m\"created: link\"\x1b[0m: \x1b[32mcreated\x1b[0m, symlink, user(1000) group(2000), 0777\n\x1b[36m\"type change: file -> dir\"\x1b[0m: file -> dir\n   @@ -1,2 +1 @@\n\x1b[31m   -content\x1b[0m\n    \n\x1b[36m\"user change\"\x1b[0m: user(1000) group(2000) -> user2(1001) group(2000)\n\x1b[36m\"group change\"\x1b[0m: user(1000) group(2000) -> user(1000) group2(2001)\n\x1b[36m\"mode change\"\x1b[0m: 0755 -> 0750\n\x1b[36m\"mode change (setuid)\"\x1b[0m: 0755 -> 04755\n\x1b[36m\"content change\"\x1b[0m:\n   @@ -1,2 +1,2 @@\n\x1b[31m   -old content\x1b[0m\n\x1b[32m   +content\x1b[0m\n    \n\x1b[36m\"multiple changes\"\x1b[0m: file -> dir, user(1000) group(2000) -> user2(1001) group2(2001), 0644 -> 0755\n   @@ -1,2 +1 @@\n\x1b[31m   -content\x1b[0m\n    \n",
380                 },
381
382                 {
383                         "dry-run",
384                         true,
385                         false,
386                         []safcm.FileChange{
387                                 {
388                                         Path:    "file",
389                                         Created: true,
390                                         New: safcm.FileChangeInfo{
391                                                 Mode:  0644,
392                                                 User:  "user",
393                                                 Uid:   1000,
394                                                 Group: "group",
395                                                 Gid:   2000,
396                                         },
397                                 },
398                         },
399                         `changed 1 file(s): (dry-run)
400 "file": created, file, user(1000) group(2000), 0644
401 `,
402                 },
403
404                 {
405                         "dry-run (tty)",
406                         true,
407                         true,
408                         []safcm.FileChange{
409                                 {
410                                         Path:    "file",
411                                         Created: true,
412                                         New: safcm.FileChangeInfo{
413                                                 Mode:  0644,
414                                                 User:  "user",
415                                                 Uid:   1000,
416                                                 Group: "group",
417                                                 Gid:   2000,
418                                         },
419                                 },
420                         },
421                         "changed 1 file(s): (dry-run)\n\x1B[36m\"file\"\x1B[0m: \x1B[32mcreated\x1B[0m, file, user(1000) group(2000), 0644\n",
422                 },
423
424                 {
425                         "escaping",
426                         false,
427                         false,
428                         []safcm.FileChange{
429                                 {
430                                         Path:    "\x00",
431                                         Created: true,
432                                         New: safcm.FileChangeInfo{
433                                                 Mode:  0xFFFFFFFF,
434                                                 User:  "\x01",
435                                                 Uid:   -1,
436                                                 Group: "\x02",
437                                                 Gid:   -2,
438                                         },
439                                         DataDiff: "\x03",
440                                 },
441                                 {
442                                         Path: "\x00",
443                                         Old: safcm.FileChangeInfo{
444                                                 Mode:  0x00000000,
445                                                 User:  "\x01",
446                                                 Uid:   -1,
447                                                 Group: "\x02",
448                                                 Gid:   -2,
449                                         },
450                                         New: safcm.FileChangeInfo{
451                                                 Mode:  0xFFFFFFFF,
452                                                 User:  "\x03",
453                                                 Uid:   -3,
454                                                 Group: "\x04",
455                                                 Gid:   -4,
456                                         },
457                                         DataDiff: "\x05",
458                                 },
459                         },
460                         `changed 2 file(s):
461 "\x00": created, invalid type dLDpSc?---------, \x01(-1) \x02(-2), 07777
462    \x03
463    \ No newline at end of file
464 "\x00": file -> invalid type dLDpSc?---------, \x01(-1) \x02(-2) -> \x03(-3) \x04(-4), 0 -> 07777
465    \x05
466    \ No newline at end of file
467 `,
468                 },
469
470                 {
471                         "escaping (tty)",
472                         false,
473                         true,
474                         []safcm.FileChange{
475                                 {
476                                         Path:    "\x00",
477                                         Created: true,
478                                         New: safcm.FileChangeInfo{
479                                                 Mode:  0xFFFFFFFF,
480                                                 User:  "\x01",
481                                                 Uid:   -1,
482                                                 Group: "\x02",
483                                                 Gid:   -2,
484                                         },
485                                         DataDiff: "\x03",
486                                 },
487                                 {
488                                         Path: "\x00",
489                                         Old: safcm.FileChangeInfo{
490                                                 Mode:  0x00000000,
491                                                 User:  "\x01",
492                                                 Uid:   -1,
493                                                 Group: "\x02",
494                                                 Gid:   -2,
495                                         },
496                                         New: safcm.FileChangeInfo{
497                                                 Mode:  0xFFFFFFFF,
498                                                 User:  "\x03",
499                                                 Uid:   -3,
500                                                 Group: "\x04",
501                                                 Gid:   -4,
502                                         },
503                                         DataDiff: "\x05",
504                                 },
505                         },
506                         "changed 2 file(s):\n\x1b[36m\"\\x00\"\x1b[0m: \x1b[32mcreated\x1b[0m, invalid type dLDpSc?---------, \\x01(-1) \\x02(-2), 07777\n   \\x03\n   \\ No newline at end of file\n\x1b[36m\"\\x00\"\x1b[0m: file -> invalid type dLDpSc?---------, \\x01(-1) \\x02(-2) -> \\x03(-3) \\x04(-4), 0 -> 07777\n   \\x05\n   \\ No newline at end of file\n",
507                 },
508         }
509
510         for _, tc := range tests {
511                 t.Run(tc.name, func(t *testing.T) {
512                         s := &Sync{
513                                 config: &config.Config{
514                                         DryRun: tc.dryRun,
515                                 },
516                                 isTTY: tc.isTTY,
517                         }
518
519                         res := s.formatFileChanges(tc.changes)
520                         testutil.AssertEqual(t, "res", res, tc.exp)
521                 })
522         }
523 }
524
525 func TestFormatPackageChanges(t *testing.T) {
526         tests := []struct {
527                 name    string
528                 dryRun  bool
529                 isTTY   bool
530                 changes []safcm.PackageChange
531                 exp     string
532         }{
533
534                 {
535                         "regular",
536                         false,
537                         false,
538                         []safcm.PackageChange{
539                                 {
540                                         Name: "package-one",
541                                 },
542                                 {
543                                         Name: "package-two",
544                                 },
545                         },
546                         `installed 2 package(s):
547 "package-one"
548 "package-two"
549 `,
550                 },
551
552                 {
553                         "regular (tty)",
554                         false,
555                         true,
556                         []safcm.PackageChange{
557                                 {
558                                         Name: "package-one",
559                                 },
560                                 {
561                                         Name: "package-two",
562                                 },
563                         },
564                         "installed 2 package(s):\n\x1b[36m\"package-one\"\x1b[0m\n\x1b[36m\"package-two\"\x1b[0m\n",
565                 },
566
567                 {
568                         "dry-run",
569                         true,
570                         false,
571                         []safcm.PackageChange{
572                                 {
573                                         Name: "package-one",
574                                 },
575                                 {
576                                         Name: "package-two",
577                                 },
578                         },
579                         `installed 2 package(s): (dry-run)
580 "package-one"
581 "package-two"
582 `,
583                 },
584
585                 {
586                         "dry-run (tty)",
587                         true,
588                         true,
589                         []safcm.PackageChange{
590                                 {
591                                         Name: "package-one",
592                                 },
593                                 {
594                                         Name: "package-two",
595                                 },
596                         },
597                         "installed 2 package(s): (dry-run)\n\x1b[36m\"package-one\"\x1b[0m\n\x1b[36m\"package-two\"\x1b[0m\n",
598                 },
599
600                 {
601                         "escaping",
602                         false,
603                         false,
604                         []safcm.PackageChange{
605                                 {
606                                         Name: "\x00",
607                                 },
608                         },
609                         `installed 1 package(s):
610 "\x00"
611 `,
612                 },
613
614                 {
615                         "escaping (tty)",
616                         false,
617                         true,
618                         []safcm.PackageChange{
619                                 {
620                                         Name: "\x00",
621                                 },
622                         },
623                         "installed 1 package(s):\n\x1b[36m\"\\x00\"\x1b[0m\n",
624                 },
625         }
626
627         for _, tc := range tests {
628                 t.Run(tc.name, func(t *testing.T) {
629                         s := &Sync{
630                                 config: &config.Config{
631                                         DryRun: tc.dryRun,
632                                 },
633                                 isTTY: tc.isTTY,
634                         }
635
636                         res := s.formatPackageChanges(tc.changes)
637                         testutil.AssertEqual(t, "res", res, tc.exp)
638                 })
639         }
640 }
641
642 func TestFormatServiceChanges(t *testing.T) {
643         tests := []struct {
644                 name    string
645                 dryRun  bool
646                 isTTY   bool
647                 changes []safcm.ServiceChange
648                 exp     string
649         }{
650
651                 {
652                         "regular",
653                         false,
654                         false,
655                         []safcm.ServiceChange{
656                                 {
657                                         Name:    "service-one",
658                                         Started: true,
659                                 },
660                                 {
661                                         Name:    "service-two",
662                                         Enabled: true,
663                                 },
664                                 {
665                                         Name:    "service-three",
666                                         Started: true,
667                                         Enabled: true,
668                                 },
669                         },
670                         `modified 3 service(s):
671 "service-one": started
672 "service-two": enabled
673 "service-three": started, enabled
674 `,
675                 },
676
677                 {
678                         "regular (tty)",
679                         false,
680                         true,
681                         []safcm.ServiceChange{
682                                 {
683                                         Name:    "service-one",
684                                         Started: true,
685                                 },
686                                 {
687                                         Name:    "service-two",
688                                         Enabled: true,
689                                 },
690                                 {
691                                         Name:    "service-three",
692                                         Started: true,
693                                         Enabled: true,
694                                 },
695                         },
696                         "modified 3 service(s):\n\x1b[36m\"service-one\"\x1b[0m: started\n\x1b[36m\"service-two\"\x1b[0m: enabled\n\x1b[36m\"service-three\"\x1b[0m: started, enabled\n",
697                 },
698
699                 {
700                         "dry-run",
701                         true,
702                         false,
703                         []safcm.ServiceChange{
704                                 {
705                                         Name:    "service-one",
706                                         Started: true,
707                                 },
708                                 {
709                                         Name:    "service-two",
710                                         Enabled: true,
711                                 },
712                                 {
713                                         Name:    "service-three",
714                                         Started: true,
715                                         Enabled: true,
716                                 },
717                         },
718                         `modified 3 service(s): (dry-run)
719 "service-one": started
720 "service-two": enabled
721 "service-three": started, enabled
722 `,
723                 },
724
725                 {
726                         "dry-run (tty)",
727                         true,
728                         true,
729                         []safcm.ServiceChange{
730                                 {
731                                         Name:    "service-one",
732                                         Started: true,
733                                 },
734                                 {
735                                         Name:    "service-two",
736                                         Enabled: true,
737                                 },
738                                 {
739                                         Name:    "service-three",
740                                         Started: true,
741                                         Enabled: true,
742                                 },
743                         },
744                         "modified 3 service(s): (dry-run)\n\x1b[36m\"service-one\"\x1b[0m: started\n\x1b[36m\"service-two\"\x1b[0m: enabled\n\x1b[36m\"service-three\"\x1b[0m: started, enabled\n",
745                 },
746
747                 {
748                         "escaping",
749                         false,
750                         false,
751                         []safcm.ServiceChange{
752                                 {
753                                         Name: "\x00",
754                                 },
755                                 {
756                                         Name:    "\x01",
757                                         Started: true,
758                                         Enabled: true,
759                                 },
760                         },
761                         `modified 2 service(s):
762 "\x00": 
763 "\x01": started, enabled
764 `,
765                 },
766
767                 {
768                         "escaping (tty)",
769                         false,
770                         true,
771                         []safcm.ServiceChange{
772                                 {
773                                         Name: "\x00",
774                                 },
775                                 {
776                                         Name:    "\x01",
777                                         Started: true,
778                                         Enabled: true,
779                                 },
780                         },
781                         "modified 2 service(s):\n\x1b[36m\"\\x00\"\x1b[0m: \n\x1b[36m\"\\x01\"\x1b[0m: started, enabled\n",
782                 },
783         }
784
785         for _, tc := range tests {
786                 t.Run(tc.name, func(t *testing.T) {
787                         s := &Sync{
788                                 config: &config.Config{
789                                         DryRun: tc.dryRun,
790                                 },
791                                 isTTY: tc.isTTY,
792                         }
793
794                         res := s.formatServiceChanges(tc.changes)
795                         testutil.AssertEqual(t, "res", res, tc.exp)
796                 })
797         }
798 }
799
800 func TestFormatCommandChanges(t *testing.T) {
801         tests := []struct {
802                 name    string
803                 dryRun  bool
804                 quiet   bool
805                 isTTY   bool
806                 changes []safcm.CommandChange
807                 exp     string
808         }{
809
810                 {
811                         "regular",
812                         false,
813                         false,
814                         false,
815                         []safcm.CommandChange{
816                                 {
817                                         Command: "fake command",
818                                         Output:  "fake output",
819                                 },
820                                 {
821                                         Command: "fake command with no output",
822                                 },
823                                 {
824                                         Command: "fake command with newline",
825                                         Output:  "fake output\n",
826                                 },
827                                 {
828                                         Command: "fake command with more output",
829                                         Output:  "fake out\nfake put\nfake\n",
830                                 },
831                                 {
832                                         Command: "fake failed command",
833                                         Output:  "fake output",
834                                         Error:   "fake error",
835                                 },
836                         },
837                         `executed 5 command(s):
838 "fake command":
839    > fake output
840    > \ No newline at end of file
841 "fake command with no output"
842 "fake command with newline":
843    > fake output
844 "fake command with more output":
845    > fake out
846    > fake put
847    > fake
848 "fake failed command", failed: "fake error":
849    > fake output
850    > \ No newline at end of file
851 `,
852                 },
853
854                 {
855                         "regular (tty)",
856                         false,
857                         false,
858                         true,
859                         []safcm.CommandChange{
860                                 {
861                                         Command: "fake command",
862                                         Output:  "fake output",
863                                 },
864                                 {
865                                         Command: "fake command with no output",
866                                 },
867                                 {
868                                         Command: "fake command with newline",
869                                         Output:  "fake output\n",
870                                 },
871                                 {
872                                         Command: "fake command with more output",
873                                         Output:  "fake out\nfake put\nfake\n",
874                                 },
875                                 {
876                                         Command: "fake failed command",
877                                         Output:  "fake output",
878                                         Error:   "fake error",
879                                 },
880                         },
881                         "executed 5 command(s):\n\x1b[36m\"fake command\"\x1b[0m:\n   > fake output\n   > \\ No newline at end of file\n\x1b[36m\"fake command with no output\"\x1b[0m\n\x1b[36m\"fake command with newline\"\x1b[0m:\n   > fake output\n\x1b[36m\"fake command with more output\"\x1b[0m:\n   > fake out\n   > fake put\n   > fake\n\x1b[36m\"fake failed command\"\x1b[0m, failed: \"fake error\":\n   > fake output\n   > \\ No newline at end of file\n",
882                 },
883
884                 {
885                         "dry-run",
886                         true,
887                         false,
888                         false,
889                         []safcm.CommandChange{
890                                 {
891                                         Command: "fake command",
892                                         Output:  "fake output",
893                                 },
894                         },
895                         `executed 1 command(s): (dry-run)
896 "fake command":
897    > fake output
898    > \ No newline at end of file
899 `,
900                 },
901
902                 {
903                         "dry-run (tty)",
904                         true,
905                         false,
906                         true,
907                         []safcm.CommandChange{
908                                 {
909                                         Command: "fake command",
910                                         Output:  "fake output",
911                                 },
912                         },
913                         "executed 1 command(s): (dry-run)\n\x1b[36m\"fake command\"\x1b[0m:\n   > fake output\n   > \\ No newline at end of file\n",
914                 },
915
916                 {
917                         "quiet",
918                         false,
919                         true,
920                         false,
921                         []safcm.CommandChange{
922                                 {
923                                         Command: "fake command",
924                                         Output:  "fake output",
925                                 },
926                                 {
927                                         Command: "fake command with no output",
928                                 },
929                                 {
930                                         Command: "fake command with newline",
931                                         Output:  "fake output\n",
932                                 },
933                                 {
934                                         Command: "fake command with more output",
935                                         Output:  "fake out\nfake put\nfake\n",
936                                 },
937                                 {
938                                         Command: "fake failed command",
939                                         Output:  "fake output",
940                                         Error:   "fake error",
941                                 },
942                         },
943                         `executed 5 command(s), 1 with no output:
944 "fake command":
945    > fake output
946    > \ No newline at end of file
947 "fake command with newline":
948    > fake output
949 "fake command with more output":
950    > fake out
951    > fake put
952    > fake
953 "fake failed command", failed: "fake error":
954    > fake output
955    > \ No newline at end of file
956 `,
957                 },
958
959                 {
960                         "quiet (tty)",
961                         false,
962                         true,
963                         true,
964                         []safcm.CommandChange{
965                                 {
966                                         Command: "fake command",
967                                         Output:  "fake output",
968                                 },
969                                 {
970                                         Command: "fake command with no output",
971                                 },
972                                 {
973                                         Command: "fake command with newline",
974                                         Output:  "fake output\n",
975                                 },
976                                 {
977                                         Command: "fake command with more output",
978                                         Output:  "fake out\nfake put\nfake\n",
979                                 },
980                                 {
981                                         Command: "fake failed command",
982                                         Output:  "fake output",
983                                         Error:   "fake error",
984                                 },
985                         },
986                         "executed 5 command(s), 1 with no output:\n\x1b[36m\"fake command\"\x1b[0m:\n   > fake output\n   > \\ No newline at end of file\n\x1b[36m\"fake command with newline\"\x1b[0m:\n   > fake output\n\x1b[36m\"fake command with more output\"\x1b[0m:\n   > fake out\n   > fake put\n   > fake\n\x1b[36m\"fake failed command\"\x1b[0m, failed: \"fake error\":\n   > fake output\n   > \\ No newline at end of file\n",
987                 },
988
989                 {
990                         "quiet (only quiet commands)",
991                         false,
992                         true,
993                         false,
994                         []safcm.CommandChange{
995                                 {
996                                         Command: "fake command with no output",
997                                 },
998                                 {
999                                         Command: "fake command with no output",
1000                                 },
1001                         },
1002                         `executed 2 command(s), 2 with no output
1003 `,
1004                 },
1005
1006                 {
1007                         "quiet (quiet with errors)",
1008                         false,
1009                         true,
1010                         false,
1011                         []safcm.CommandChange{
1012                                 {
1013                                         Command: "fake command with no output but error",
1014                                         Error:   "fake error",
1015                                 },
1016                                 {
1017                                         Command: "fake command with no output",
1018                                 },
1019                         },
1020                         `executed 2 command(s), 1 with no output:
1021 "fake command with no output but error", failed: "fake error"
1022 `,
1023                 },
1024
1025                 {
1026                         "quiet & dry-run",
1027                         true,
1028                         true,
1029                         false,
1030                         []safcm.CommandChange{
1031                                 {
1032                                         Command: "fake command",
1033                                 },
1034                                 {
1035                                         Command: "fake command with no output",
1036                                 },
1037                                 {
1038                                         Command: "fake command with newline",
1039                                 },
1040                                 {
1041                                         Command: "fake command with more output",
1042                                 },
1043                                 {
1044                                         Command: "fake failed command",
1045                                 },
1046                         },
1047                         `executed 5 command(s): (dry-run)
1048 "fake command"
1049 "fake command with no output"
1050 "fake command with newline"
1051 "fake command with more output"
1052 "fake failed command"
1053 `,
1054                 },
1055
1056                 {
1057                         "escaping",
1058                         false,
1059                         false,
1060                         false,
1061                         []safcm.CommandChange{
1062                                 {
1063                                         Command: "\x00",
1064                                         Trigger: "\x01",
1065                                         Output:  "\x02",
1066                                         Error:   "\x03",
1067                                 },
1068                         },
1069                         `executed 1 command(s):
1070 "\x00", trigger for "\x01", failed: "\x03":
1071    > \x02
1072    > \ No newline at end of file
1073 `,
1074                 },
1075
1076                 {
1077                         "escaping (tty)",
1078                         false,
1079                         false,
1080                         true,
1081                         []safcm.CommandChange{
1082                                 {
1083                                         Command: "\x00",
1084                                         Trigger: "\x01",
1085                                         Output:  "\x02",
1086                                         Error:   "\x03",
1087                                 },
1088                         },
1089                         "executed 1 command(s):\n\x1b[36m\"\\x00\"\x1b[0m, trigger for \"\\x01\", failed: \"\\x03\":\n   > \x1b[35m\\x02\x1b[0m\n   > \\ No newline at end of file\n",
1090                 },
1091         }
1092
1093         for _, tc := range tests {
1094                 t.Run(tc.name, func(t *testing.T) {
1095                         s := &Sync{
1096                                 config: &config.Config{
1097                                         DryRun: tc.dryRun,
1098                                         Quiet:  tc.quiet,
1099                                 },
1100                                 isTTY: tc.isTTY,
1101                         }
1102
1103                         res := s.formatCommandChanges(tc.changes)
1104                         testutil.AssertEqual(t, "res", res, tc.exp)
1105                 })
1106         }
1107 }