|  | /* | 
|  | Package gexec provides support for testing external processes. | 
|  | */ | 
|  | package gexec | 
|  |  | 
|  | import ( | 
|  | "io" | 
|  | "os" | 
|  | "os/exec" | 
|  | "reflect" | 
|  | "sync" | 
|  | "syscall" | 
|  |  | 
|  | . "github.com/onsi/gomega" | 
|  | "github.com/onsi/gomega/gbytes" | 
|  | ) | 
|  |  | 
|  | const INVALID_EXIT_CODE = 254 | 
|  |  | 
|  | type Session struct { | 
|  | //The wrapped command | 
|  | Command *exec.Cmd | 
|  |  | 
|  | //A *gbytes.Buffer connected to the command's stdout | 
|  | Out *gbytes.Buffer | 
|  |  | 
|  | //A *gbytes.Buffer connected to the command's stderr | 
|  | Err *gbytes.Buffer | 
|  |  | 
|  | //A channel that will close when the command exits | 
|  | Exited <-chan struct{} | 
|  |  | 
|  | lock     *sync.Mutex | 
|  | exitCode int | 
|  | } | 
|  |  | 
|  | /* | 
|  | Start starts the passed-in *exec.Cmd command.  It wraps the command in a *gexec.Session. | 
|  |  | 
|  | The session pipes the command's stdout and stderr to two *gbytes.Buffers available as properties on the session: session.Out and session.Err. | 
|  | These buffers can be used with the gbytes.Say matcher to match against unread output: | 
|  |  | 
|  | Ω(session.Out).Should(gbytes.Say("foo-out")) | 
|  | Ω(session.Err).Should(gbytes.Say("foo-err")) | 
|  |  | 
|  | In addition, Session satisfies the gbytes.BufferProvider interface and provides the stdout *gbytes.Buffer.  This allows you to replace the first line, above, with: | 
|  |  | 
|  | Ω(session).Should(gbytes.Say("foo-out")) | 
|  |  | 
|  | 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. | 
|  | 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: | 
|  |  | 
|  | session, err := Start(command, GinkgoWriter, GinkgoWriter) | 
|  |  | 
|  | This will log output when running tests in verbose mode, but - otherwise - will only log output when a test fails. | 
|  |  | 
|  | The session wrapper is responsible for waiting on the *exec.Cmd command.  You *should not* call command.Wait() yourself. | 
|  | Instead, to assert that the command has exited you can use the gexec.Exit matcher: | 
|  |  | 
|  | Ω(session).Should(gexec.Exit()) | 
|  |  | 
|  | When the session exits it closes the stdout and stderr gbytes buffers.  This will short circuit any | 
|  | Eventuallys waiting fo the buffers to Say something. | 
|  | */ | 
|  | func Start(command *exec.Cmd, outWriter io.Writer, errWriter io.Writer) (*Session, error) { | 
|  | exited := make(chan struct{}) | 
|  |  | 
|  | session := &Session{ | 
|  | Command:  command, | 
|  | Out:      gbytes.NewBuffer(), | 
|  | Err:      gbytes.NewBuffer(), | 
|  | Exited:   exited, | 
|  | lock:     &sync.Mutex{}, | 
|  | exitCode: -1, | 
|  | } | 
|  |  | 
|  | var commandOut, commandErr io.Writer | 
|  |  | 
|  | commandOut, commandErr = session.Out, session.Err | 
|  |  | 
|  | if outWriter != nil && !reflect.ValueOf(outWriter).IsNil() { | 
|  | commandOut = io.MultiWriter(commandOut, outWriter) | 
|  | } | 
|  |  | 
|  | if errWriter != nil && !reflect.ValueOf(errWriter).IsNil() { | 
|  | commandErr = io.MultiWriter(commandErr, errWriter) | 
|  | } | 
|  |  | 
|  | command.Stdout = commandOut | 
|  | command.Stderr = commandErr | 
|  |  | 
|  | err := command.Start() | 
|  | if err == nil { | 
|  | go session.monitorForExit(exited) | 
|  | } | 
|  |  | 
|  | return session, err | 
|  | } | 
|  |  | 
|  | /* | 
|  | Buffer implements the gbytes.BufferProvider interface and returns s.Out | 
|  | This allows you to make gbytes.Say matcher assertions against stdout without having to reference .Out: | 
|  |  | 
|  | Eventually(session).Should(gbytes.Say("foo")) | 
|  | */ | 
|  | func (s *Session) Buffer() *gbytes.Buffer { | 
|  | return s.Out | 
|  | } | 
|  |  | 
|  | /* | 
|  | ExitCode returns the wrapped command's exit code.  If the command hasn't exited yet, ExitCode returns -1. | 
|  |  | 
|  | To assert that the command has exited it is more convenient to use the Exit matcher: | 
|  |  | 
|  | Eventually(s).Should(gexec.Exit()) | 
|  |  | 
|  | When the process exits because it has received a particular signal, the exit code will be 128+signal-value | 
|  | (See http://www.tldp.org/LDP/abs/html/exitcodes.html and http://man7.org/linux/man-pages/man7/signal.7.html) | 
|  |  | 
|  | */ | 
|  | func (s *Session) ExitCode() int { | 
|  | s.lock.Lock() | 
|  | defer s.lock.Unlock() | 
|  | return s.exitCode | 
|  | } | 
|  |  | 
|  | /* | 
|  | Wait waits until the wrapped command exits.  It can be passed an optional timeout. | 
|  | If the command does not exit within the timeout, Wait will trigger a test failure. | 
|  |  | 
|  | Wait returns the session, making it possible to chain: | 
|  |  | 
|  | session.Wait().Out.Contents() | 
|  |  | 
|  | will wait for the command to exit then return the entirety of Out's contents. | 
|  |  | 
|  | Wait uses eventually under the hood and accepts the same timeout/polling intervals that eventually does. | 
|  | */ | 
|  | func (s *Session) Wait(timeout ...interface{}) *Session { | 
|  | EventuallyWithOffset(1, s, timeout...).Should(Exit()) | 
|  | return s | 
|  | } | 
|  |  | 
|  | /* | 
|  | Kill sends the running command a SIGKILL signal.  It does not wait for the process to exit. | 
|  |  | 
|  | If the command has already exited, Kill returns silently. | 
|  |  | 
|  | The session is returned to enable chaining. | 
|  | */ | 
|  | func (s *Session) Kill() *Session { | 
|  | if s.ExitCode() != -1 { | 
|  | return s | 
|  | } | 
|  | s.Command.Process.Kill() | 
|  | return s | 
|  | } | 
|  |  | 
|  | /* | 
|  | Interrupt sends the running command a SIGINT signal.  It does not wait for the process to exit. | 
|  |  | 
|  | If the command has already exited, Interrupt returns silently. | 
|  |  | 
|  | The session is returned to enable chaining. | 
|  | */ | 
|  | func (s *Session) Interrupt() *Session { | 
|  | return s.Signal(syscall.SIGINT) | 
|  | } | 
|  |  | 
|  | /* | 
|  | Terminate sends the running command a SIGTERM signal.  It does not wait for the process to exit. | 
|  |  | 
|  | If the command has already exited, Terminate returns silently. | 
|  |  | 
|  | The session is returned to enable chaining. | 
|  | */ | 
|  | func (s *Session) Terminate() *Session { | 
|  | return s.Signal(syscall.SIGTERM) | 
|  | } | 
|  |  | 
|  | /* | 
|  | Terminate sends the running command the passed in signal.  It does not wait for the process to exit. | 
|  |  | 
|  | If the command has already exited, Signal returns silently. | 
|  |  | 
|  | The session is returned to enable chaining. | 
|  | */ | 
|  | func (s *Session) Signal(signal os.Signal) *Session { | 
|  | if s.ExitCode() != -1 { | 
|  | return s | 
|  | } | 
|  | s.Command.Process.Signal(signal) | 
|  | return s | 
|  | } | 
|  |  | 
|  | func (s *Session) monitorForExit(exited chan<- struct{}) { | 
|  | err := s.Command.Wait() | 
|  | s.lock.Lock() | 
|  | s.Out.Close() | 
|  | s.Err.Close() | 
|  | status := s.Command.ProcessState.Sys().(syscall.WaitStatus) | 
|  | if status.Signaled() { | 
|  | s.exitCode = 128 + int(status.Signal()) | 
|  | } else { | 
|  | exitStatus := status.ExitStatus() | 
|  | if exitStatus == -1 && err != nil { | 
|  | s.exitCode = INVALID_EXIT_CODE | 
|  | } | 
|  | s.exitCode = exitStatus | 
|  | } | 
|  | s.lock.Unlock() | 
|  |  | 
|  | close(exited) | 
|  | } |