podman

Форк
0
305 строк · 8.3 Кб
1
/*
2
Package gexec provides support for testing external processes.
3
*/
4

5
// untested sections: 1
6

7
package gexec
8

9
import (
10
	"io"
11
	"os"
12
	"os/exec"
13
	"sync"
14
	"syscall"
15

16
	. "github.com/onsi/gomega"
17
	"github.com/onsi/gomega/gbytes"
18
)
19

20
const INVALID_EXIT_CODE = 254
21

22
type Session struct {
23
	//The wrapped command
24
	Command *exec.Cmd
25

26
	//A *gbytes.Buffer connected to the command's stdout
27
	Out *gbytes.Buffer
28

29
	//A *gbytes.Buffer connected to the command's stderr
30
	Err *gbytes.Buffer
31

32
	//A channel that will close when the command exits
33
	Exited <-chan struct{}
34

35
	lock     *sync.Mutex
36
	exitCode int
37
}
38

39
/*
40
Start starts the passed-in *exec.Cmd command.  It wraps the command in a *gexec.Session.
41

42
The session pipes the command's stdout and stderr to two *gbytes.Buffers available as properties on the session: session.Out and session.Err.
43
These buffers can be used with the gbytes.Say matcher to match against unread output:
44

45
	Expect(session.Out).Should(gbytes.Say("foo-out"))
46
	Expect(session.Err).Should(gbytes.Say("foo-err"))
47

48
In addition, Session satisfies the gbytes.BufferProvider interface and provides the stdout *gbytes.Buffer.  This allows you to replace the first line, above, with:
49

50
	Expect(session).Should(gbytes.Say("foo-out"))
51

52
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.
53
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:
54

55
	session, err := Start(command, GinkgoWriter, GinkgoWriter)
56

57
This will log output when running tests in verbose mode, but - otherwise - will only log output when a test fails.
58

59
The session wrapper is responsible for waiting on the *exec.Cmd command.  You *should not* call command.Wait() yourself.
60
Instead, to assert that the command has exited you can use the gexec.Exit matcher:
61

62
	Expect(session).Should(gexec.Exit())
63

64
When the session exits it closes the stdout and stderr gbytes buffers.  This will short circuit any
65
Eventuallys waiting for the buffers to Say something.
66
*/
67
func Start(command *exec.Cmd, outWriter io.Writer, errWriter io.Writer) (*Session, error) {
68
	exited := make(chan struct{})
69

70
	session := &Session{
71
		Command:  command,
72
		Out:      gbytes.NewBuffer(),
73
		Err:      gbytes.NewBuffer(),
74
		Exited:   exited,
75
		lock:     &sync.Mutex{},
76
		exitCode: -1,
77
	}
78

79
	var commandOut, commandErr io.Writer
80

81
	commandOut, commandErr = session.Out, session.Err
82

83
	if outWriter != nil {
84
		commandOut = io.MultiWriter(commandOut, outWriter)
85
	}
86

87
	if errWriter != nil {
88
		commandErr = io.MultiWriter(commandErr, errWriter)
89
	}
90

91
	command.Stdout = commandOut
92
	command.Stderr = commandErr
93

94
	err := command.Start()
95
	if err == nil {
96
		go session.monitorForExit(exited)
97
		trackedSessionsMutex.Lock()
98
		defer trackedSessionsMutex.Unlock()
99
		trackedSessions = append(trackedSessions, session)
100
	}
101

102
	return session, err
103
}
104

105
/*
106
Buffer implements the gbytes.BufferProvider interface and returns s.Out
107
This allows you to make gbytes.Say matcher assertions against stdout without having to reference .Out:
108

109
	Eventually(session).Should(gbytes.Say("foo"))
110
*/
111
func (s *Session) Buffer() *gbytes.Buffer {
112
	return s.Out
113
}
114

115
/*
116
ExitCode returns the wrapped command's exit code.  If the command hasn't exited yet, ExitCode returns -1.
117

118
To assert that the command has exited it is more convenient to use the Exit matcher:
119

120
	Eventually(s).Should(gexec.Exit())
121

122
When the process exits because it has received a particular signal, the exit code will be 128+signal-value
123
(See http://www.tldp.org/LDP/abs/html/exitcodes.html and http://man7.org/linux/man-pages/man7/signal.7.html)
124
*/
125
func (s *Session) ExitCode() int {
126
	s.lock.Lock()
127
	defer s.lock.Unlock()
128
	return s.exitCode
129
}
130

131
/*
132
Wait waits until the wrapped command exits.  It can be passed an optional timeout.
133
If the command does not exit within the timeout, Wait will trigger a test failure.
134

135
Wait returns the session, making it possible to chain:
136

137
	session.Wait().Out.Contents()
138

139
will wait for the command to exit then return the entirety of Out's contents.
140

141
Wait uses eventually under the hood and accepts the same timeout/polling intervals that eventually does.
142
*/
143
func (s *Session) Wait(timeout ...interface{}) *Session {
144
	EventuallyWithOffset(1, s, timeout...).Should(Exit())
145
	return s
146
}
147

148
/*
149
Kill sends the running command a SIGKILL signal.  It does not wait for the process to exit.
150

151
If the command has already exited, Kill returns silently.
152

153
The session is returned to enable chaining.
154
*/
155
func (s *Session) Kill() *Session {
156
	return s.Signal(syscall.SIGKILL)
157
}
158

159
/*
160
Interrupt sends the running command a SIGINT signal.  It does not wait for the process to exit.
161

162
If the command has already exited, Interrupt returns silently.
163

164
The session is returned to enable chaining.
165
*/
166
func (s *Session) Interrupt() *Session {
167
	return s.Signal(syscall.SIGINT)
168
}
169

170
/*
171
Terminate sends the running command a SIGTERM signal.  It does not wait for the process to exit.
172

173
If the command has already exited, Terminate returns silently.
174

175
The session is returned to enable chaining.
176
*/
177
func (s *Session) Terminate() *Session {
178
	return s.Signal(syscall.SIGTERM)
179
}
180

181
/*
182
Signal sends the running command the passed in signal.  It does not wait for the process to exit.
183

184
If the command has already exited, Signal returns silently.
185

186
The session is returned to enable chaining.
187
*/
188
func (s *Session) Signal(signal os.Signal) *Session {
189
	if s.processIsAlive() {
190
		s.Command.Process.Signal(signal)
191
	}
192
	return s
193
}
194

195
func (s *Session) monitorForExit(exited chan<- struct{}) {
196
	err := s.Command.Wait()
197
	s.lock.Lock()
198
	s.Out.Close()
199
	s.Err.Close()
200
	status := s.Command.ProcessState.Sys().(syscall.WaitStatus)
201
	if status.Signaled() {
202
		s.exitCode = 128 + int(status.Signal())
203
	} else {
204
		exitStatus := status.ExitStatus()
205
		if exitStatus == -1 && err != nil {
206
			s.exitCode = INVALID_EXIT_CODE
207
		}
208
		s.exitCode = exitStatus
209
	}
210
	s.lock.Unlock()
211

212
	close(exited)
213
}
214

215
func (s *Session) processIsAlive() bool {
216
	return s.ExitCode() == -1 && s.Command.Process != nil
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
}
306

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.