"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",
--- /dev/null
+/*
+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
+ }
+}
--- /dev/null
+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
+ }
+}
--- /dev/null
+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")
+}
--- /dev/null
+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
+}
--- /dev/null
+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
+}
--- /dev/null
+/*
+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)
+}