| /* | 
 | 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) | 
 | } |