forked from cli/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstub.go
More file actions
116 lines (104 loc) · 2.78 KB
/
stub.go
File metadata and controls
116 lines (104 loc) · 2.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package run
import (
"fmt"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
type T interface {
Helper()
Errorf(string, ...interface{})
}
// Stub installs a catch-all for all external commands invoked from gh. It returns a restore func that, when
// invoked from tests, fails the current test if some stubs that were registered were never matched.
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("unmatched stubs (%d): %s", len(unmatched), strings.Join(unmatched, ", "))
}
}
func setPrepareCmd(fn func(*exec.Cmd) Runnable) func() {
origPrepare := PrepareCmd
PrepareCmd = func(cmd *exec.Cmd) Runnable {
// normalize git executable name for consistency in tests
if baseName := filepath.Base(cmd.Args[0]); baseName == "git" || baseName == "git.exe" {
cmd.Args[0] = "git"
}
return fn(cmd)
}
return func() {
PrepareCmd = origPrepare
}
}
// CommandStubber stubs out invocations to external commands.
type CommandStubber struct {
stubs []*commandStub
}
// Register a stub for an external command. Pattern is a regular expression, output is the standard output
// from a command. Pass callbacks to inspect raw arguments that the command was invoked with.
func (cs *CommandStubber) Register(pattern string, exitStatus int, output string, callbacks ...CommandCallback) {
if len(pattern) < 1 {
panic("cannot use empty regexp pattern")
}
cs.stubs = append(cs.stubs, &commandStub{
pattern: regexp.MustCompile(pattern),
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
}
// Run satisfies Runnable
func (s *commandStub) Run() error {
if s.exitStatus != 0 {
return fmt.Errorf("%s exited with status %d", s.pattern, s.exitStatus)
}
return nil
}
// Output satisfies Runnable
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
}