Add exec.Command stub mechanism that matches by arguments

Our previous command stub mechanism matches stubs sequentially, which leads to brittle tests when the exec calls get reordered or removed in the implementation.
This commit is contained in:
Mislav Marohnić 2020-09-23 18:38:29 +02:00
parent f17d9672f5
commit f9783fe812

91
internal/run/stub.go Normal file
View file

@ -0,0 +1,91 @@
package run
import (
"fmt"
"os/exec"
"regexp"
"strings"
)
type T interface {
Helper()
Errorf(string, ...interface{})
}
func Stub() (*CommandStubber, func(T)) {
cs := &CommandStubber{}
teardown := SetPrepareCmd(func(cmd *exec.Cmd) Runnable {
s := cs.find(cmd.Args)
if s == nil {
panic(fmt.Sprintf("no exec stub for `%s`", strings.Join(cmd.Args, " ")))
}
for _, c := range s.callbacks {
c(cmd.Args)
}
s.matched = true
return s
})
return cs, func(t T) {
defer teardown()
var unmatched []string
for _, s := range cs.stubs {
if s.matched {
continue
}
unmatched = append(unmatched, s.pattern.String())
}
if len(unmatched) == 0 {
return
}
t.Helper()
t.Errorf("umatched stubs (%d): %s", len(unmatched), strings.Join(unmatched, ", "))
}
}
type CommandStubber struct {
stubs []*commandStub
}
func (cs *CommandStubber) Register(p string, exitStatus int, output string, callbacks ...CommandCallback) {
cs.stubs = append(cs.stubs, &commandStub{
pattern: regexp.MustCompile(p),
exitStatus: exitStatus,
stdout: output,
callbacks: callbacks,
})
}
func (cs *CommandStubber) find(args []string) *commandStub {
line := strings.Join(args, " ")
for _, s := range cs.stubs {
if !s.matched && s.pattern.MatchString(line) {
return s
}
}
return nil
}
type CommandCallback func([]string)
type commandStub struct {
pattern *regexp.Regexp
matched bool
exitStatus int
stdout string
callbacks []CommandCallback
}
func (s *commandStub) Run() error {
if s.exitStatus != 0 {
return fmt.Errorf("%s exited with status %d", s.pattern, s.exitStatus)
}
return nil
}
func (s *commandStub) Output() ([]byte, error) {
if s.exitStatus != 0 {
return []byte(nil), fmt.Errorf("%s exited with status %d", s.pattern, s.exitStatus)
}
return []byte(s.stdout), nil
}