vendor: add gomega/gbytes and gomega/gexec
This commit is contained in:
parent
af9127b7ea
commit
8c27b64f13
10
Godeps/Godeps.json
generated
10
Godeps/Godeps.json
generated
@ -161,6 +161,16 @@
|
||||
"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",
|
||||
|
229
vendor/github.com/onsi/gomega/gbytes/buffer.go
generated
vendored
Normal file
229
vendor/github.com/onsi/gomega/gbytes/buffer.go
generated
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
105
vendor/github.com/onsi/gomega/gbytes/say_matcher.go
generated
vendored
Normal file
105
vendor/github.com/onsi/gomega/gbytes/say_matcher.go
generated
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
78
vendor/github.com/onsi/gomega/gexec/build.go
generated
vendored
Normal file
78
vendor/github.com/onsi/gomega/gexec/build.go
generated
vendored
Normal file
@ -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")
|
||||
}
|
88
vendor/github.com/onsi/gomega/gexec/exit_matcher.go
generated
vendored
Normal file
88
vendor/github.com/onsi/gomega/gexec/exit_matcher.go
generated
vendored
Normal file
@ -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
|
||||
}
|
53
vendor/github.com/onsi/gomega/gexec/prefixed_writer.go
generated
vendored
Normal file
53
vendor/github.com/onsi/gomega/gexec/prefixed_writer.go
generated
vendored
Normal file
@ -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
|
||||
}
|
214
vendor/github.com/onsi/gomega/gexec/session.go
generated
vendored
Normal file
214
vendor/github.com/onsi/gomega/gexec/session.go
generated
vendored
Normal file
@ -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)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user