package main // Tests of kit.go, the same in each workstation module. import ( "strings" "testing" "time" ) // fake records the commands asked and answers each from a function of the command line. type fake struct { asked []Cmd answer func(line string, c Cmd) Result } func (f *fake) runner() Runner { return func(c Cmd) Result { f.asked = append(f.asked, c) name, args := argv(c) line := strings.TrimSpace(name + " " + strings.Join(args, " ")) if f.answer == nil { return Result{} } return f.answer(line, c) } } func (f *fake) lines() []string { out := []string{} for _, c := range f.asked { name, args := argv(c) out = append(out, strings.TrimSpace(name+" "+strings.Join(args, " "))) } return out } // using installs a fake runner and a non-root uid for one test. func using(t *testing.T, answer func(line string, c Cmd) Result) *fake { t.Helper() f := &fake{answer: answer} wasRun, wasUID := run, euid run, euid = f.runner(), func() int { return 1000 } t.Cleanup(func() { run, euid = wasRun, wasUID }) return f } func ok(stdout string) Result { return Result{Stdout: stdout} } func TestKitAnActThatNeedsRootGoesThroughSudoWithoutAPromptUnlessAlreadyRoot(t *testing.T) { was := euid defer func() { euid = was }() euid = func() int { return 1000 } if name, args := argv(Cmd{Name: "x", Args: []string{"a"}, Root: true}); name != "sudo" || strings.Join(args, " ") != "-n x a" { t.Fatalf("not root: %s %v", name, args) } if name, _ := argv(Cmd{Name: "x"}); name != "x" { t.Fatalf("a read is run as the account: %s", name) } euid = func() int { return 0 } if name, _ := argv(Cmd{Name: "x", Root: true}); name != "x" { t.Fatalf("as root no sudo: %s", name) } } func TestKitAFailureIsNamedByHowItFailed(t *testing.T) { was := euid defer func() { euid = was }() euid = func() int { return 1000 } cases := []struct { c Cmd r Result want string }{ {Cmd{Name: "nothere"}, Result{Status: 127, Error: "not-found"}, "not installed"}, {Cmd{Name: "x", Root: true}, Result{Status: 127, Error: "not-found"}, "sudo is not installed"}, {Cmd{Name: "x", Root: true}, Result{Status: 1, Stderr: "sudo: a password is required\n"}, "sudo -n refused"}, {Cmd{Name: "x", Root: true}, Result{Status: 1, Stderr: "sudo: x: command not found\n"}, "x is not installed"}, {Cmd{Name: "x"}, Result{Status: 124, Error: "timeout"}, "within 20s"}, {Cmd{Name: "x", Args: []string{"y"}}, Result{Status: 3, Stderr: "boom\n"}, "x y exited 3: boom"}, {Cmd{Name: "x"}, Result{Status: 3}, "said nothing"}, } for _, k := range cases { err := failure(k.c, k.r) if err == nil || !strings.Contains(err.Error(), k.want) { t.Errorf("%+v: %v, want %q", k.r, err, k.want) } } } func TestKitOutputIsBoundedAndSaysSo(t *testing.T) { var w bounded big := strings.Repeat("a", MostOutput+10) n, _ := w.Write([]byte(big)) if n != len(big) || w.b.Len() != MostOutput || !w.cut { t.Fatalf("kept %d of %d, cut %v", w.b.Len(), len(big), w.cut) } } func TestKitTheRealRunnerRunsEndsAndReportsAMissingProgram(t *testing.T) { r := execRun(Cmd{Name: "sh", Args: []string{"-c", "echo out; echo err >&2; exit 3"}}) if r.Status != 3 || strings.TrimSpace(r.Stdout) != "out" || strings.TrimSpace(r.Stderr) != "err" { t.Fatalf("%+v", r) } r = execRun(Cmd{Name: "sh", Args: []string{"-c", "sleep 5 & sleep 5"}, Timeout: 200 * time.Millisecond}) if r.Error != "timeout" { t.Fatalf("a slow command: %+v", r) } r = execRun(Cmd{Name: "no-such-program-anywhere"}) if r.Error != "not-found" { t.Fatalf("a missing program: %+v", r) } r = execRun(Cmd{Name: "cat", Stdin: "given"}) if r.Stdout != "given" { t.Fatalf("stdin: %+v", r) } start := time.Now() r = execRun(Cmd{Name: "sh", Args: []string{"-c", "echo kept; (sleep 3 &) ; exit 0"}, Detached: true}) if r.Status != 0 || strings.TrimSpace(r.Stdout) != "kept" || time.Since(start) > 2*time.Second { t.Fatalf("a detached command returns when it exits, not when its child does: %+v after %s", r, time.Since(start)) } } func TestKitArgumentsAreReadStrictly(t *testing.T) { args := map[string]any{"s": "x", "n": float64(5), "f": 1.5, "b": true, "l": []any{"a", "b"}} if _, err := text(args, "missing"); err == nil { t.Error("a missing required string") } if n, _ := optWhole(args, "n", 1, 1, 3); n != 3 { t.Errorf("held to most: %d", n) } if _, err := optWhole(args, "n", 1, 6, 9); err == nil { t.Error("below least") } if _, err := optWhole(args, "f", 1, 0, 9); err == nil { t.Error("a fraction") } if l, _ := optList(args, "l"); len(l) != 2 { t.Errorf("list: %v", l) } if b, _ := optFlag(args, "b", false); !b { t.Error("flag") } if err := plainName("name", "--all"); err == nil { t.Error("an option as a name") } }