]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - remote/sync/sync_test.go
remote: support creating files with missing parents in dry-run
[safcm/safcm.git] / remote / sync / sync_test.go
1 // Copyright (C) 2021-2023  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         "bytes"
20         "fmt"
21         "os/exec"
22         "reflect"
23         "sync"
24         "testing"
25
26         "github.com/google/go-cmp/cmp"
27         "github.com/google/go-cmp/cmp/cmpopts"
28
29         "ruderich.org/simon/safcm"
30         "ruderich.org/simon/safcm/remote/log"
31         "ruderich.org/simon/safcm/remote/run"
32 )
33
34 // testRunner implements run.Runner to test commands without actually running
35 // them.
36 type testRunner struct {
37         t         *testing.T
38         expCmds   []*exec.Cmd
39         resStdout [][]byte
40         resStderr [][]byte
41         resError  []error
42 }
43
44 func (r *testRunner) Run(cmd *exec.Cmd) error {
45         stdout, stderr, resErr := r.check("run", cmd)
46         _, err := cmd.Stdout.Write(stdout)
47         if err != nil {
48                 panic(err)
49         }
50         _, err = cmd.Stderr.Write(stderr)
51         if err != nil {
52                 panic(err)
53         }
54         return resErr
55 }
56 func (r *testRunner) CombinedOutput(cmd *exec.Cmd) ([]byte, error) {
57         r.t.Helper()
58
59         stdout, stderr, err := r.check("combinedOutput", cmd)
60         if stderr != nil {
61                 // stdout also contains stderr
62                 r.t.Fatalf("CombinedOutput: stderr != nil, but %v", stderr)
63         }
64         return stdout, err
65 }
66 func (r *testRunner) check(method string, cmd *exec.Cmd) (
67         []byte, []byte, error) {
68         r.t.Helper()
69
70         if len(r.expCmds) == 0 {
71                 r.t.Fatalf("%s: empty expCmds", method)
72         }
73         if len(r.resStdout) == 0 {
74                 r.t.Fatalf("%s: empty resStdout", method)
75         }
76         if len(r.resStderr) == 0 {
77                 r.t.Fatalf("%s: empty resStderr", method)
78         }
79         if len(r.resError) == 0 {
80                 r.t.Fatalf("%s: empty resError", method)
81         }
82
83         exp := r.expCmds[0]
84         r.expCmds = r.expCmds[1:]
85         if !reflect.DeepEqual(exp, cmd) {
86                 opts := cmpopts.IgnoreUnexported(exec.Cmd{}, bytes.Buffer{})
87                 r.t.Errorf("%s: %s", method, cmp.Diff(exp, cmd, opts))
88         }
89
90         var stdout, stderr []byte
91         var err error
92
93         stdout, r.resStdout = r.resStdout[0], r.resStdout[1:]
94         stderr, r.resStderr = r.resStderr[0], r.resStderr[1:]
95         err, r.resError = r.resError[0], r.resError[1:]
96
97         return stdout, stderr, err
98 }
99
100 type syncTestResult struct {
101         ch     chan string
102         wg     sync.WaitGroup
103         dbg    []string
104         runner *testRunner
105 }
106
107 func prepareSync(req safcm.MsgSyncReq, runner *testRunner) (
108         *Sync, *syncTestResult) {
109
110         res := &syncTestResult{
111                 ch:     make(chan string),
112                 runner: runner,
113         }
114         res.wg.Add(1)
115         go func() {
116                 for {
117                         x, ok := <-res.ch
118                         if !ok {
119                                 break
120                         }
121                         res.dbg = append(res.dbg, x)
122                 }
123                 res.wg.Done()
124         }()
125
126         logger := log.NewLogger(func(level safcm.LogLevel, msg string) {
127                 res.ch <- fmt.Sprintf("%d: %s", level, msg)
128         })
129         return &Sync{
130                 req: req,
131                 cmd: run.NewCmd(runner, logger),
132                 log: logger,
133         }, res
134 }
135
136 func (s *syncTestResult) Wait() []string {
137         s.runner.t.Helper()
138
139         close(s.ch)
140         s.wg.Wait()
141
142         // All expected commands must have been executed
143         if len(s.runner.expCmds) != 0 {
144                 s.runner.t.Errorf("expCmds left: %v", s.runner.expCmds)
145         }
146         if len(s.runner.resStdout) != 0 {
147                 s.runner.t.Errorf("resStdout left: %v", s.runner.resStdout)
148         }
149         if len(s.runner.resStderr) != 0 {
150                 s.runner.t.Errorf("resStderr left: %v", s.runner.resStderr)
151         }
152         if len(s.runner.resError) != 0 {
153                 s.runner.t.Errorf("resError left: %v", s.runner.resError)
154         }
155
156         return s.dbg
157 }