initial commit
[govpp.git] / vendor / github.com / onsi / gomega / gexec / session.go
1 /*
2 Package gexec provides support for testing external processes.
3 */
4 package gexec
5
6 import (
7         "io"
8         "os"
9         "os/exec"
10         "reflect"
11         "sync"
12         "syscall"
13
14         . "github.com/onsi/gomega"
15         "github.com/onsi/gomega/gbytes"
16 )
17
18 const INVALID_EXIT_CODE = 254
19
20 type Session struct {
21         //The wrapped command
22         Command *exec.Cmd
23
24         //A *gbytes.Buffer connected to the command's stdout
25         Out *gbytes.Buffer
26
27         //A *gbytes.Buffer connected to the command's stderr
28         Err *gbytes.Buffer
29
30         //A channel that will close when the command exits
31         Exited <-chan struct{}
32
33         lock     *sync.Mutex
34         exitCode int
35 }
36
37 /*
38 Start starts the passed-in *exec.Cmd command.  It wraps the command in a *gexec.Session.
39
40 The session pipes the command's stdout and stderr to two *gbytes.Buffers available as properties on the session: session.Out and session.Err.
41 These buffers can be used with the gbytes.Say matcher to match against unread output:
42
43         Ω(session.Out).Should(gbytes.Say("foo-out"))
44         Ω(session.Err).Should(gbytes.Say("foo-err"))
45
46 In addition, Session satisfies the gbytes.BufferProvider interface and provides the stdout *gbytes.Buffer.  This allows you to replace the first line, above, with:
47
48         Ω(session).Should(gbytes.Say("foo-out"))
49
50 When outWriter and/or errWriter are non-nil, the session will pipe stdout and/or stderr output both into the session *gybtes.Buffers and to the passed-in outWriter/errWriter.
51 This is useful for capturing the process's output or logging it to screen.  In particular, when using Ginkgo it can be convenient to direct output to the GinkgoWriter:
52
53         session, err := Start(command, GinkgoWriter, GinkgoWriter)
54
55 This will log output when running tests in verbose mode, but - otherwise - will only log output when a test fails.
56
57 The session wrapper is responsible for waiting on the *exec.Cmd command.  You *should not* call command.Wait() yourself.
58 Instead, to assert that the command has exited you can use the gexec.Exit matcher:
59
60         Ω(session).Should(gexec.Exit())
61
62 When the session exits it closes the stdout and stderr gbytes buffers.  This will short circuit any
63 Eventuallys waiting for the buffers to Say something.
64 */
65 func Start(command *exec.Cmd, outWriter io.Writer, errWriter io.Writer) (*Session, error) {
66         exited := make(chan struct{})
67
68         session := &Session{
69                 Command:  command,
70                 Out:      gbytes.NewBuffer(),
71                 Err:      gbytes.NewBuffer(),
72                 Exited:   exited,
73                 lock:     &sync.Mutex{},
74                 exitCode: -1,
75         }
76
77         var commandOut, commandErr io.Writer
78
79         commandOut, commandErr = session.Out, session.Err
80
81         if outWriter != nil && !reflect.ValueOf(outWriter).IsNil() {
82                 commandOut = io.MultiWriter(commandOut, outWriter)
83         }
84
85         if errWriter != nil && !reflect.ValueOf(errWriter).IsNil() {
86                 commandErr = io.MultiWriter(commandErr, errWriter)
87         }
88
89         command.Stdout = commandOut
90         command.Stderr = commandErr
91
92         err := command.Start()
93         if err == nil {
94                 go session.monitorForExit(exited)
95                 trackedSessionsMutex.Lock()
96                 defer trackedSessionsMutex.Unlock()
97                 trackedSessions = append(trackedSessions, session)
98         }
99
100         return session, err
101 }
102
103 /*
104 Buffer implements the gbytes.BufferProvider interface and returns s.Out
105 This allows you to make gbytes.Say matcher assertions against stdout without having to reference .Out:
106
107         Eventually(session).Should(gbytes.Say("foo"))
108 */
109 func (s *Session) Buffer() *gbytes.Buffer {
110         return s.Out
111 }
112
113 /*
114 ExitCode returns the wrapped command's exit code.  If the command hasn't exited yet, ExitCode returns -1.
115
116 To assert that the command has exited it is more convenient to use the Exit matcher:
117
118         Eventually(s).Should(gexec.Exit())
119
120 When the process exits because it has received a particular signal, the exit code will be 128+signal-value
121 (See http://www.tldp.org/LDP/abs/html/exitcodes.html and http://man7.org/linux/man-pages/man7/signal.7.html)
122
123 */
124 func (s *Session) ExitCode() int {
125         s.lock.Lock()
126         defer s.lock.Unlock()
127         return s.exitCode
128 }
129
130 /*
131 Wait waits until the wrapped command exits.  It can be passed an optional timeout.
132 If the command does not exit within the timeout, Wait will trigger a test failure.
133
134 Wait returns the session, making it possible to chain:
135
136         session.Wait().Out.Contents()
137
138 will wait for the command to exit then return the entirety of Out's contents.
139
140 Wait uses eventually under the hood and accepts the same timeout/polling intervals that eventually does.
141 */
142 func (s *Session) Wait(timeout ...interface{}) *Session {
143         EventuallyWithOffset(1, s, timeout...).Should(Exit())
144         return s
145 }
146
147 /*
148 Kill sends the running command a SIGKILL signal.  It does not wait for the process to exit.
149
150 If the command has already exited, Kill returns silently.
151
152 The session is returned to enable chaining.
153 */
154 func (s *Session) Kill() *Session {
155         if s.ExitCode() != -1 {
156                 return s
157         }
158         s.Command.Process.Kill()
159         return s
160 }
161
162 /*
163 Interrupt sends the running command a SIGINT signal.  It does not wait for the process to exit.
164
165 If the command has already exited, Interrupt returns silently.
166
167 The session is returned to enable chaining.
168 */
169 func (s *Session) Interrupt() *Session {
170         return s.Signal(syscall.SIGINT)
171 }
172
173 /*
174 Terminate sends the running command a SIGTERM signal.  It does not wait for the process to exit.
175
176 If the command has already exited, Terminate returns silently.
177
178 The session is returned to enable chaining.
179 */
180 func (s *Session) Terminate() *Session {
181         return s.Signal(syscall.SIGTERM)
182 }
183
184 /*
185 Signal sends the running command the passed in signal.  It does not wait for the process to exit.
186
187 If the command has already exited, Signal returns silently.
188
189 The session is returned to enable chaining.
190 */
191 func (s *Session) Signal(signal os.Signal) *Session {
192         if s.ExitCode() != -1 {
193                 return s
194         }
195         s.Command.Process.Signal(signal)
196         return s
197 }
198
199 func (s *Session) monitorForExit(exited chan<- struct{}) {
200         err := s.Command.Wait()
201         s.lock.Lock()
202         s.Out.Close()
203         s.Err.Close()
204         status := s.Command.ProcessState.Sys().(syscall.WaitStatus)
205         if status.Signaled() {
206                 s.exitCode = 128 + int(status.Signal())
207         } else {
208                 exitStatus := status.ExitStatus()
209                 if exitStatus == -1 && err != nil {
210                         s.exitCode = INVALID_EXIT_CODE
211                 }
212                 s.exitCode = exitStatus
213         }
214         s.lock.Unlock()
215
216         close(exited)
217 }
218
219 var trackedSessions = []*Session{}
220 var trackedSessionsMutex = &sync.Mutex{}
221
222 /*
223 Kill sends a SIGKILL signal to all the processes started by Run, and waits for them to exit.
224 The timeout specified is applied to each process killed.
225
226 If any of the processes already exited, KillAndWait returns silently.
227 */
228 func KillAndWait(timeout ...interface{}) {
229         trackedSessionsMutex.Lock()
230         defer trackedSessionsMutex.Unlock()
231         for _, session := range trackedSessions {
232                 session.Kill().Wait(timeout...)
233         }
234         trackedSessions = []*Session{}
235 }
236
237 /*
238 Kill sends a SIGTERM signal to all the processes started by Run, and waits for them to exit.
239 The timeout specified is applied to each process killed.
240
241 If any of the processes already exited, TerminateAndWait returns silently.
242 */
243 func TerminateAndWait(timeout ...interface{}) {
244         trackedSessionsMutex.Lock()
245         defer trackedSessionsMutex.Unlock()
246         for _, session := range trackedSessions {
247                 session.Terminate().Wait(timeout...)
248         }
249 }
250
251 /*
252 Kill sends a SIGKILL signal to all the processes started by Run.
253 It does not wait for the processes to exit.
254
255 If any of the processes already exited, Kill returns silently.
256 */
257 func Kill() {
258         trackedSessionsMutex.Lock()
259         defer trackedSessionsMutex.Unlock()
260         for _, session := range trackedSessions {
261                 session.Kill()
262         }
263 }
264
265 /*
266 Terminate sends a SIGTERM signal to all the processes started by Run.
267 It does not wait for the processes to exit.
268
269 If any of the processes already exited, Terminate returns silently.
270 */
271 func Terminate() {
272         trackedSessionsMutex.Lock()
273         defer trackedSessionsMutex.Unlock()
274         for _, session := range trackedSessions {
275                 session.Terminate()
276         }
277 }
278
279 /*
280 Signal sends the passed in signal to all the processes started by Run.
281 It does not wait for the processes to exit.
282
283 If any of the processes already exited, Signal returns silently.
284 */
285 func Signal(signal os.Signal) {
286         trackedSessionsMutex.Lock()
287         defer trackedSessionsMutex.Unlock()
288         for _, session := range trackedSessions {
289                 session.Signal(signal)
290         }
291 }
292
293 /*
294 Interrupt sends the SIGINT signal to all the processes started by Run.
295 It does not wait for the processes to exit.
296
297 If any of the processes already exited, Interrupt returns silently.
298 */
299 func Interrupt() {
300         trackedSessionsMutex.Lock()
301         defer trackedSessionsMutex.Unlock()
302         for _, session := range trackedSessions {
303                 session.Interrupt()
304         }
305 }