forked from cloudfoundry/gorouter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ginkgomon.go
161 lines (135 loc) · 4.2 KB
/
ginkgomon.go
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*
Ginkgomon provides ginkgo test helpers.
*/
package ginkgomon
import (
"fmt"
"io"
"os"
"os/exec"
"time"
"github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/gexec"
)
// Config defines a ginkgomon Runner.
type Config struct {
Command *exec.Cmd // process to be executed
Name string // prefixes all output lines
AnsiColorCode string // colors the output
StartCheck string // text to match to indicate sucessful start.
StartCheckTimeout time.Duration // how long to wait to see StartCheck
Cleanup func() // invoked once the process exits
}
/*
The ginkgomon Runner invokes a new process using gomega's gexec package.
If a start check is defined, the runner will wait until it sees the start check
before declaring ready.
Runner implements gexec.Exiter and gbytes.BufferProvider, so you can test exit
codes and process output using the appropriate gomega matchers:
http://onsi.github.io/gomega/#gexec-testing-external-processes
*/
type Runner struct {
Command *exec.Cmd
Name string
AnsiColorCode string
StartCheck string
StartCheckTimeout time.Duration
Cleanup func()
session *gexec.Session
sessionReady chan struct{}
}
// New creates a ginkgomon Runner from a config object. Runners must be created
// with New to properly initialize their internal state.
func New(config Config) *Runner {
return &Runner{
Name: config.Name,
Command: config.Command,
AnsiColorCode: config.AnsiColorCode,
StartCheck: config.StartCheck,
StartCheckTimeout: config.StartCheckTimeout,
Cleanup: config.Cleanup,
sessionReady: make(chan struct{}),
}
}
// ExitCode returns the exit code of the process, or -1 if the process has not
// exited. It can be used with the gexec.Exit matcher.
func (r *Runner) ExitCode() int {
if r.sessionReady == nil {
ginkgo.Fail(fmt.Sprintf("ginkgomon.Runner '%s' improperly created without using New", r.Name))
}
<-r.sessionReady
return r.session.ExitCode()
}
// Buffer returns a gbytes.Buffer, for use with the gbytes.Say matcher.
func (r *Runner) Buffer() *gbytes.Buffer {
if r.sessionReady == nil {
ginkgo.Fail(fmt.Sprintf("ginkgomon.Runner '%s' improperly created without using New", r.Name))
}
<-r.sessionReady
return r.session.Buffer()
}
func (r *Runner) Run(sigChan <-chan os.Signal, ready chan<- struct{}) error {
defer ginkgo.GinkgoRecover()
allOutput := gbytes.NewBuffer()
debugWriter := gexec.NewPrefixedWriter(
fmt.Sprintf("\x1b[32m[d]\x1b[%s[%s]\x1b[0m ", r.AnsiColorCode, r.Name),
ginkgo.GinkgoWriter,
)
session, err := gexec.Start(
r.Command,
gexec.NewPrefixedWriter(
fmt.Sprintf("\x1b[32m[o]\x1b[%s[%s]\x1b[0m ", r.AnsiColorCode, r.Name),
io.MultiWriter(allOutput, ginkgo.GinkgoWriter),
),
gexec.NewPrefixedWriter(
fmt.Sprintf("\x1b[91m[e]\x1b[%s[%s]\x1b[0m ", r.AnsiColorCode, r.Name),
io.MultiWriter(allOutput, ginkgo.GinkgoWriter),
),
)
Ω(err).ShouldNot(HaveOccurred())
fmt.Fprintf(debugWriter, "spawned %s (pid: %d)\n", r.Command.Path, r.Command.Process.Pid)
r.session = session
if r.sessionReady != nil {
close(r.sessionReady)
}
startCheckDuration := r.StartCheckTimeout
if startCheckDuration == 0 {
startCheckDuration = 5 * time.Second
}
var startCheckTimeout <-chan time.Time
if r.StartCheck != "" {
startCheckTimeout = time.After(startCheckDuration)
}
detectStartCheck := allOutput.Detect(r.StartCheck)
for {
select {
case <-detectStartCheck: // works even with empty string
allOutput.CancelDetects()
startCheckTimeout = nil
detectStartCheck = nil
close(ready)
case <-startCheckTimeout:
// clean up hanging process
session.Kill().Wait()
// fail to start
return fmt.Errorf(
"did not see %s in command's output within %s. full output:\n\n%s",
r.StartCheck,
startCheckDuration,
string(allOutput.Contents()),
)
case signal := <-sigChan:
session.Signal(signal)
case <-session.Exited:
if r.Cleanup != nil {
r.Cleanup()
}
if session.ExitCode() == 0 {
return nil
}
return fmt.Errorf("exit status %d", session.ExitCode())
}
}
}