--- /dev/null
+// Copyright (C) 2021 Simon Ruderich
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package sync
+
+import (
+ "bytes"
+ "fmt"
+ "os/exec"
+ "reflect"
+ "sync"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+
+ "ruderich.org/simon/safcm"
+ "ruderich.org/simon/safcm/cmd/safcm-remote/log"
+ "ruderich.org/simon/safcm/cmd/safcm-remote/run"
+)
+
+type testRunner struct {
+ t *testing.T
+ name string
+ expCmds []*exec.Cmd
+ resStdout [][]byte
+ resStderr [][]byte
+ resError []error
+}
+
+func (r *testRunner) Run(cmd *exec.Cmd) error {
+ stdout, stderr, resErr := r.check("run", cmd)
+ _, err := cmd.Stdout.Write(stdout)
+ if err != nil {
+ panic(err)
+ }
+ _, err = cmd.Stderr.Write(stderr)
+ if err != nil {
+ panic(err)
+ }
+ return resErr
+}
+func (r *testRunner) CombinedOutput(cmd *exec.Cmd) ([]byte, error) {
+ stdout, stderr, err := r.check("combinedOutput", cmd)
+ if stderr != nil {
+ // stdout also contains stderr
+ r.t.Fatalf("%s: CombinedOutput: stderr != nil, but %v",
+ r.name, stderr)
+ }
+ return stdout, err
+}
+func (r *testRunner) check(method string, cmd *exec.Cmd) (
+ []byte, []byte, error) {
+
+ if len(r.expCmds) == 0 {
+ r.t.Fatalf("%s: %s: empty expCmds", r.name, method)
+ }
+ if len(r.resStdout) == 0 {
+ r.t.Fatalf("%s: %s: empty resStdout", r.name, method)
+ }
+ if len(r.resStderr) == 0 {
+ r.t.Fatalf("%s: %s: empty resStderr", r.name, method)
+ }
+ if len(r.resError) == 0 {
+ r.t.Fatalf("%s: %s: empty resError", r.name, method)
+ }
+
+ exp := r.expCmds[0]
+ r.expCmds = r.expCmds[1:]
+ if !reflect.DeepEqual(exp, cmd) {
+ r.t.Errorf("%s: %s: %s", r.name, method,
+ cmp.Diff(exp, cmd, cmpopts.IgnoreUnexported(
+ exec.Cmd{},
+ bytes.Buffer{})))
+ }
+
+ var stdout, stderr []byte
+ var err error
+
+ stdout, r.resStdout = r.resStdout[0], r.resStdout[1:]
+ stderr, r.resStderr = r.resStderr[0], r.resStderr[1:]
+ err, r.resError = r.resError[0], r.resError[1:]
+
+ return stdout, stderr, err
+}
+
+type syncTestResult struct {
+ ch chan string
+ wg sync.WaitGroup
+ dbg []string
+ runner *testRunner
+}
+
+func prepareSync(req safcm.MsgSyncReq, runner *testRunner) (
+ *Sync, *syncTestResult) {
+
+ res := &syncTestResult{
+ ch: make(chan string),
+ runner: runner,
+ }
+ res.wg.Add(1)
+ go func() {
+ for {
+ x, ok := <-res.ch
+ if !ok {
+ break
+ }
+ res.dbg = append(res.dbg, x)
+ }
+ res.wg.Done()
+ }()
+
+ logger := log.NewLogger(logPrefix,
+ func(level safcm.LogLevel, format string, a ...interface{}) {
+ res.ch <- fmt.Sprintf("%d: %s", level,
+ fmt.Sprintf(format, a...))
+ })
+ return &Sync{
+ req: req,
+ cmd: run.NewCmd(runner, logger),
+ log: logger,
+ }, res
+}
+
+func (s *syncTestResult) Wait() []string {
+ close(s.ch)
+ s.wg.Wait()
+
+ // All expected commands must have been executed
+ if len(s.runner.expCmds) != 0 {
+ s.runner.t.Errorf("%s: expCmds left: %v",
+ s.runner.name, s.runner.expCmds)
+ }
+ if len(s.runner.resStdout) != 0 {
+ s.runner.t.Errorf("%s: resStdout left: %v",
+ s.runner.name, s.runner.resStdout)
+ }
+ if len(s.runner.resStderr) != 0 {
+ s.runner.t.Errorf("%s: resStderr left: %v",
+ s.runner.name, s.runner.resStderr)
+ }
+ if len(s.runner.resError) != 0 {
+ s.runner.t.Errorf("%s: resError left: %v",
+ s.runner.name, s.runner.resError)
+ }
+
+ return s.dbg
+}