vendor: add gomega/gbytes and gomega/gexec
authorDan Williams <dcbw@redhat.com>
Wed, 10 May 2017 03:41:15 +0000 (22:41 -0500)
committerDan Williams <dcbw@redhat.com>
Wed, 10 May 2017 03:45:44 +0000 (22:45 -0500)
Godeps/Godeps.json
vendor/github.com/onsi/gomega/gbytes/buffer.go [new file with mode: 0644]
vendor/github.com/onsi/gomega/gbytes/say_matcher.go [new file with mode: 0644]
vendor/github.com/onsi/gomega/gexec/build.go [new file with mode: 0644]
vendor/github.com/onsi/gomega/gexec/exit_matcher.go [new file with mode: 0644]
vendor/github.com/onsi/gomega/gexec/prefixed_writer.go [new file with mode: 0644]
vendor/github.com/onsi/gomega/gexec/session.go [new file with mode: 0644]

index a284553..88cac42 100644 (file)
                        "Comment": "v1.0-71-g2152b45",
                        "Rev": "2152b45fa28a361beba9aab0885972323a444e28"
                },
+               {
+                       "ImportPath": "github.com/onsi/gomega/gbytes",
+                       "Comment": "v1.0-71-g2152b45",
+                       "Rev": "2152b45fa28a361beba9aab0885972323a444e28"
+               },
+               {
+                       "ImportPath": "github.com/onsi/gomega/gexec",
+                       "Comment": "v1.0-71-g2152b45",
+                       "Rev": "2152b45fa28a361beba9aab0885972323a444e28"
+               },
                {
                        "ImportPath": "github.com/onsi/gomega/internal/assertion",
                        "Comment": "v1.0-71-g2152b45",
diff --git a/vendor/github.com/onsi/gomega/gbytes/buffer.go b/vendor/github.com/onsi/gomega/gbytes/buffer.go
new file mode 100644 (file)
index 0000000..8775b86
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+Package gbytes provides a buffer that supports incrementally detecting input.
+
+You use gbytes.Buffer with the gbytes.Say matcher.  When Say finds a match, it fastforwards the buffer's read cursor to the end of that match.
+
+Subsequent matches against the buffer will only operate against data that appears *after* the read cursor.
+
+The read cursor is an opaque implementation detail that you cannot access.  You should use the Say matcher to sift through the buffer.  You can always
+access the entire buffer's contents with Contents().
+
+*/
+package gbytes
+
+import (
+       "errors"
+       "fmt"
+       "io"
+       "regexp"
+       "sync"
+       "time"
+)
+
+/*
+gbytes.Buffer implements an io.Writer and can be used with the gbytes.Say matcher.
+
+You should only use a gbytes.Buffer in test code.  It stores all writes in an in-memory buffer - behavior that is inappropriate for production code!
+*/
+type Buffer struct {
+       contents     []byte
+       readCursor   uint64
+       lock         *sync.Mutex
+       detectCloser chan interface{}
+       closed       bool
+}
+
+/*
+NewBuffer returns a new gbytes.Buffer
+*/
+func NewBuffer() *Buffer {
+       return &Buffer{
+               lock: &sync.Mutex{},
+       }
+}
+
+/*
+BufferWithBytes returns a new gbytes.Buffer seeded with the passed in bytes
+*/
+func BufferWithBytes(bytes []byte) *Buffer {
+       return &Buffer{
+               lock:     &sync.Mutex{},
+               contents: bytes,
+       }
+}
+
+/*
+Write implements the io.Writer interface
+*/
+func (b *Buffer) Write(p []byte) (n int, err error) {
+       b.lock.Lock()
+       defer b.lock.Unlock()
+
+       if b.closed {
+               return 0, errors.New("attempt to write to closed buffer")
+       }
+
+       b.contents = append(b.contents, p...)
+       return len(p), nil
+}
+
+/*
+Read implements the io.Reader interface. It advances the
+cursor as it reads.
+
+Returns an error if called after Close.
+*/
+func (b *Buffer) Read(d []byte) (int, error) {
+       b.lock.Lock()
+       defer b.lock.Unlock()
+
+       if b.closed {
+               return 0, errors.New("attempt to read from closed buffer")
+       }
+
+       if uint64(len(b.contents)) <= b.readCursor {
+               return 0, io.EOF
+       }
+
+       n := copy(d, b.contents[b.readCursor:])
+       b.readCursor += uint64(n)
+
+       return n, nil
+}
+
+/*
+Close signifies that the buffer will no longer be written to
+*/
+func (b *Buffer) Close() error {
+       b.lock.Lock()
+       defer b.lock.Unlock()
+
+       b.closed = true
+
+       return nil
+}
+
+/*
+Closed returns true if the buffer has been closed
+*/
+func (b *Buffer) Closed() bool {
+       b.lock.Lock()
+       defer b.lock.Unlock()
+
+       return b.closed
+}
+
+/*
+Contents returns all data ever written to the buffer.
+*/
+func (b *Buffer) Contents() []byte {
+       b.lock.Lock()
+       defer b.lock.Unlock()
+
+       contents := make([]byte, len(b.contents))
+       copy(contents, b.contents)
+       return contents
+}
+
+/*
+Detect takes a regular expression and returns a channel.
+
+The channel will receive true the first time data matching the regular expression is written to the buffer.
+The channel is subsequently closed and the buffer's read-cursor is fast-forwarded to just after the matching region.
+
+You typically don't need to use Detect and should use the ghttp.Say matcher instead.  Detect is useful, however, in cases where your code must
+be branch and handle different outputs written to the buffer.
+
+For example, consider a buffer hooked up to the stdout of a client library.  You may (or may not, depending on state outside of your control) need to authenticate the client library.
+
+You could do something like:
+
+select {
+case <-buffer.Detect("You are not logged in"):
+       //log in
+case <-buffer.Detect("Success"):
+       //carry on
+case <-time.After(time.Second):
+       //welp
+}
+buffer.CancelDetects()
+
+You should always call CancelDetects after using Detect.  This will close any channels that have not detected and clean up the goroutines that were spawned to support them.
+
+Finally, you can pass detect a format string followed by variadic arguments.  This will construct the regexp using fmt.Sprintf.
+*/
+func (b *Buffer) Detect(desired string, args ...interface{}) chan bool {
+       formattedRegexp := desired
+       if len(args) > 0 {
+               formattedRegexp = fmt.Sprintf(desired, args...)
+       }
+       re := regexp.MustCompile(formattedRegexp)
+
+       b.lock.Lock()
+       defer b.lock.Unlock()
+
+       if b.detectCloser == nil {
+               b.detectCloser = make(chan interface{})
+       }
+
+       closer := b.detectCloser
+       response := make(chan bool)
+       go func() {
+               ticker := time.NewTicker(10 * time.Millisecond)
+               defer ticker.Stop()
+               defer close(response)
+               for {
+                       select {
+                       case <-ticker.C:
+                               b.lock.Lock()
+                               data, cursor := b.contents[b.readCursor:], b.readCursor
+                               loc := re.FindIndex(data)
+                               b.lock.Unlock()
+
+                               if loc != nil {
+                                       response <- true
+                                       b.lock.Lock()
+                                       newCursorPosition := cursor + uint64(loc[1])
+                                       if newCursorPosition >= b.readCursor {
+                                               b.readCursor = newCursorPosition
+                                       }
+                                       b.lock.Unlock()
+                                       return
+                               }
+                       case <-closer:
+                               return
+                       }
+               }
+       }()
+
+       return response
+}
+
+/*
+CancelDetects cancels any pending detects and cleans up their goroutines.  You should always call this when you're done with a set of Detect channels.
+*/
+func (b *Buffer) CancelDetects() {
+       b.lock.Lock()
+       defer b.lock.Unlock()
+
+       close(b.detectCloser)
+       b.detectCloser = nil
+}
+
+func (b *Buffer) didSay(re *regexp.Regexp) (bool, []byte) {
+       b.lock.Lock()
+       defer b.lock.Unlock()
+
+       unreadBytes := b.contents[b.readCursor:]
+       copyOfUnreadBytes := make([]byte, len(unreadBytes))
+       copy(copyOfUnreadBytes, unreadBytes)
+
+       loc := re.FindIndex(unreadBytes)
+
+       if loc != nil {
+               b.readCursor += uint64(loc[1])
+               return true, copyOfUnreadBytes
+       } else {
+               return false, copyOfUnreadBytes
+       }
+}
diff --git a/vendor/github.com/onsi/gomega/gbytes/say_matcher.go b/vendor/github.com/onsi/gomega/gbytes/say_matcher.go
new file mode 100644 (file)
index 0000000..ce5ebcb
--- /dev/null
@@ -0,0 +1,105 @@
+package gbytes
+
+import (
+       "fmt"
+       "regexp"
+
+       "github.com/onsi/gomega/format"
+)
+
+//Objects satisfying the BufferProvider can be used with the Say matcher.
+type BufferProvider interface {
+       Buffer() *Buffer
+}
+
+/*
+Say is a Gomega matcher that operates on gbytes.Buffers:
+
+       Ω(buffer).Should(Say("something"))
+
+will succeed if the unread portion of the buffer matches the regular expression "something".
+
+When Say succeeds, it fast forwards the gbytes.Buffer's read cursor to just after the succesful match.
+Thus, subsequent calls to Say will only match against the unread portion of the buffer
+
+Say pairs very well with Eventually.  To asser that a buffer eventually receives data matching "[123]-star" within 3 seconds you can:
+
+       Eventually(buffer, 3).Should(Say("[123]-star"))
+
+Ditto with consistently.  To assert that a buffer does not receive data matching "never-see-this" for 1 second you can:
+
+       Consistently(buffer, 1).ShouldNot(Say("never-see-this"))
+
+In addition to bytes.Buffers, Say can operate on objects that implement the gbytes.BufferProvider interface.
+In such cases, Say simply operates on the *gbytes.Buffer returned by Buffer()
+
+If the buffer is closed, the Say matcher will tell Eventually to abort.
+*/
+func Say(expected string, args ...interface{}) *sayMatcher {
+       formattedRegexp := expected
+       if len(args) > 0 {
+               formattedRegexp = fmt.Sprintf(expected, args...)
+       }
+       return &sayMatcher{
+               re: regexp.MustCompile(formattedRegexp),
+       }
+}
+
+type sayMatcher struct {
+       re              *regexp.Regexp
+       receivedSayings []byte
+}
+
+func (m *sayMatcher) buffer(actual interface{}) (*Buffer, bool) {
+       var buffer *Buffer
+
+       switch x := actual.(type) {
+       case *Buffer:
+               buffer = x
+       case BufferProvider:
+               buffer = x.Buffer()
+       default:
+               return nil, false
+       }
+
+       return buffer, true
+}
+
+func (m *sayMatcher) Match(actual interface{}) (success bool, err error) {
+       buffer, ok := m.buffer(actual)
+       if !ok {
+               return false, fmt.Errorf("Say must be passed a *gbytes.Buffer or BufferProvider.  Got:\n%s", format.Object(actual, 1))
+       }
+
+       didSay, sayings := buffer.didSay(m.re)
+       m.receivedSayings = sayings
+
+       return didSay, nil
+}
+
+func (m *sayMatcher) FailureMessage(actual interface{}) (message string) {
+       return fmt.Sprintf(
+               "Got stuck at:\n%s\nWaiting for:\n%s",
+               format.IndentString(string(m.receivedSayings), 1),
+               format.IndentString(m.re.String(), 1),
+       )
+}
+
+func (m *sayMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+       return fmt.Sprintf(
+               "Saw:\n%s\nWhich matches the unexpected:\n%s",
+               format.IndentString(string(m.receivedSayings), 1),
+               format.IndentString(m.re.String(), 1),
+       )
+}
+
+func (m *sayMatcher) MatchMayChangeInTheFuture(actual interface{}) bool {
+       switch x := actual.(type) {
+       case *Buffer:
+               return !x.Closed()
+       case BufferProvider:
+               return !x.Buffer().Closed()
+       default:
+               return true
+       }
+}
diff --git a/vendor/github.com/onsi/gomega/gexec/build.go b/vendor/github.com/onsi/gomega/gexec/build.go
new file mode 100644 (file)
index 0000000..3e9bf9f
--- /dev/null
@@ -0,0 +1,78 @@
+package gexec
+
+import (
+       "errors"
+       "fmt"
+       "io/ioutil"
+       "os"
+       "os/exec"
+       "path"
+       "path/filepath"
+       "runtime"
+)
+
+var tmpDir string
+
+/*
+Build uses go build to compile the package at packagePath.  The resulting binary is saved off in a temporary directory.
+A path pointing to this binary is returned.
+
+Build uses the $GOPATH set in your environment.  It passes the variadic args on to `go build`.
+*/
+func Build(packagePath string, args ...string) (compiledPath string, err error) {
+       return BuildIn(os.Getenv("GOPATH"), packagePath, args...)
+}
+
+/*
+BuildIn is identical to Build but allows you to specify a custom $GOPATH (the first argument).
+*/
+func BuildIn(gopath string, packagePath string, args ...string) (compiledPath string, err error) {
+       tmpDir, err := temporaryDirectory()
+       if err != nil {
+               return "", err
+       }
+
+       if len(gopath) == 0 {
+               return "", errors.New("$GOPATH not provided when building " + packagePath)
+       }
+
+       executable := filepath.Join(tmpDir, path.Base(packagePath))
+       if runtime.GOOS == "windows" {
+               executable = executable + ".exe"
+       }
+
+       cmdArgs := append([]string{"build"}, args...)
+       cmdArgs = append(cmdArgs, "-o", executable, packagePath)
+
+       build := exec.Command("go", cmdArgs...)
+       build.Env = append([]string{"GOPATH=" + gopath}, os.Environ()...)
+
+       output, err := build.CombinedOutput()
+       if err != nil {
+               return "", fmt.Errorf("Failed to build %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output))
+       }
+
+       return executable, nil
+}
+
+/*
+You should call CleanupBuildArtifacts before your test ends to clean up any temporary artifacts generated by
+gexec. In Ginkgo this is typically done in an AfterSuite callback.
+*/
+func CleanupBuildArtifacts() {
+       if tmpDir != "" {
+               os.RemoveAll(tmpDir)
+       }
+}
+
+func temporaryDirectory() (string, error) {
+       var err error
+       if tmpDir == "" {
+               tmpDir, err = ioutil.TempDir("", "gexec_artifacts")
+               if err != nil {
+                       return "", err
+               }
+       }
+
+       return ioutil.TempDir(tmpDir, "g")
+}
diff --git a/vendor/github.com/onsi/gomega/gexec/exit_matcher.go b/vendor/github.com/onsi/gomega/gexec/exit_matcher.go
new file mode 100644 (file)
index 0000000..e6f4329
--- /dev/null
@@ -0,0 +1,88 @@
+package gexec
+
+import (
+       "fmt"
+
+       "github.com/onsi/gomega/format"
+)
+
+/*
+The Exit matcher operates on a session:
+
+       Ω(session).Should(Exit(<optional status code>))
+
+Exit passes if the session has already exited.
+
+If no status code is provided, then Exit will succeed if the session has exited regardless of exit code.
+Otherwise, Exit will only succeed if the process has exited with the provided status code.
+
+Note that the process must have already exited.  To wait for a process to exit, use Eventually:
+
+       Eventually(session, 3).Should(Exit(0))
+*/
+func Exit(optionalExitCode ...int) *exitMatcher {
+       exitCode := -1
+       if len(optionalExitCode) > 0 {
+               exitCode = optionalExitCode[0]
+       }
+
+       return &exitMatcher{
+               exitCode: exitCode,
+       }
+}
+
+type exitMatcher struct {
+       exitCode       int
+       didExit        bool
+       actualExitCode int
+}
+
+type Exiter interface {
+       ExitCode() int
+}
+
+func (m *exitMatcher) Match(actual interface{}) (success bool, err error) {
+       exiter, ok := actual.(Exiter)
+       if !ok {
+               return false, fmt.Errorf("Exit must be passed a gexec.Exiter (Missing method ExitCode() int) Got:\n%s", format.Object(actual, 1))
+       }
+
+       m.actualExitCode = exiter.ExitCode()
+
+       if m.actualExitCode == -1 {
+               return false, nil
+       }
+
+       if m.exitCode == -1 {
+               return true, nil
+       }
+       return m.exitCode == m.actualExitCode, nil
+}
+
+func (m *exitMatcher) FailureMessage(actual interface{}) (message string) {
+       if m.actualExitCode == -1 {
+               return "Expected process to exit.  It did not."
+       } else {
+               return format.Message(m.actualExitCode, "to match exit code:", m.exitCode)
+       }
+}
+
+func (m *exitMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+       if m.actualExitCode == -1 {
+               return "you really shouldn't be able to see this!"
+       } else {
+               if m.exitCode == -1 {
+                       return "Expected process not to exit.  It did."
+               } else {
+                       return format.Message(m.actualExitCode, "not to match exit code:", m.exitCode)
+               }
+       }
+}
+
+func (m *exitMatcher) MatchMayChangeInTheFuture(actual interface{}) bool {
+       session, ok := actual.(*Session)
+       if ok {
+               return session.ExitCode() == -1
+       }
+       return true
+}
diff --git a/vendor/github.com/onsi/gomega/gexec/prefixed_writer.go b/vendor/github.com/onsi/gomega/gexec/prefixed_writer.go
new file mode 100644 (file)
index 0000000..05e695a
--- /dev/null
@@ -0,0 +1,53 @@
+package gexec
+
+import (
+       "io"
+       "sync"
+)
+
+/*
+PrefixedWriter wraps an io.Writer, emiting the passed in prefix at the beginning of each new line.
+This can be useful when running multiple gexec.Sessions concurrently - you can prefix the log output of each
+session by passing in a PrefixedWriter:
+
+gexec.Start(cmd, NewPrefixedWriter("[my-cmd] ", GinkgoWriter), NewPrefixedWriter("[my-cmd] ", GinkgoWriter))
+*/
+type PrefixedWriter struct {
+       prefix        []byte
+       writer        io.Writer
+       lock          *sync.Mutex
+       atStartOfLine bool
+}
+
+func NewPrefixedWriter(prefix string, writer io.Writer) *PrefixedWriter {
+       return &PrefixedWriter{
+               prefix:        []byte(prefix),
+               writer:        writer,
+               lock:          &sync.Mutex{},
+               atStartOfLine: true,
+       }
+}
+
+func (w *PrefixedWriter) Write(b []byte) (int, error) {
+       w.lock.Lock()
+       defer w.lock.Unlock()
+
+       toWrite := []byte{}
+
+       for _, c := range b {
+               if w.atStartOfLine {
+                       toWrite = append(toWrite, w.prefix...)
+               }
+
+               toWrite = append(toWrite, c)
+
+               w.atStartOfLine = c == '\n'
+       }
+
+       _, err := w.writer.Write(toWrite)
+       if err != nil {
+               return 0, err
+       }
+
+       return len(b), nil
+}
diff --git a/vendor/github.com/onsi/gomega/gexec/session.go b/vendor/github.com/onsi/gomega/gexec/session.go
new file mode 100644 (file)
index 0000000..46e7122
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+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)
+}