Path: blob/main/vendor/github.com/onsi/gomega/gbytes/buffer.go
2880 views
/*1Package gbytes provides a buffer that supports incrementally detecting input.23You 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.45Subsequent matches against the buffer will only operate against data that appears *after* the read cursor.67The read cursor is an opaque implementation detail that you cannot access. You should use the Say matcher to sift through the buffer. You can always8access the entire buffer's contents with Contents().9*/10package gbytes1112import (13"errors"14"fmt"15"io"16"regexp"17"sync"18"time"19)2021/*22gbytes.Buffer implements an io.Writer and can be used with the gbytes.Say matcher.2324You 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!25*/26type Buffer struct {27contents []byte28readCursor uint6429lock *sync.Mutex30detectCloser chan any31closed bool32}3334/*35NewBuffer returns a new gbytes.Buffer36*/37func NewBuffer() *Buffer {38return &Buffer{39lock: &sync.Mutex{},40}41}4243/*44BufferWithBytes returns a new gbytes.Buffer seeded with the passed in bytes45*/46func BufferWithBytes(bytes []byte) *Buffer {47return &Buffer{48lock: &sync.Mutex{},49contents: bytes,50}51}5253/*54BufferReader returns a new gbytes.Buffer that wraps a reader. The reader's contents are read into55the Buffer via io.Copy56*/57func BufferReader(reader io.Reader) *Buffer {58b := &Buffer{59lock: &sync.Mutex{},60}6162go func() {63io.Copy(b, reader)64b.Close()65}()6667return b68}6970/*71Write implements the io.Writer interface72*/73func (b *Buffer) Write(p []byte) (n int, err error) {74b.lock.Lock()75defer b.lock.Unlock()7677if b.closed {78return 0, errors.New("attempt to write to closed buffer")79}8081b.contents = append(b.contents, p...)82return len(p), nil83}8485/*86Read implements the io.Reader interface. It advances the87cursor as it reads.88*/89func (b *Buffer) Read(d []byte) (int, error) {90b.lock.Lock()91defer b.lock.Unlock()9293if uint64(len(b.contents)) <= b.readCursor {94return 0, io.EOF95}9697n := copy(d, b.contents[b.readCursor:])98b.readCursor += uint64(n)99100return n, nil101}102103/*104Clear clears out the buffer's contents105*/106func (b *Buffer) Clear() error {107b.lock.Lock()108defer b.lock.Unlock()109110if b.closed {111return errors.New("attempt to clear closed buffer")112}113114b.contents = []byte{}115b.readCursor = 0116return nil117}118119/*120Close signifies that the buffer will no longer be written to121*/122func (b *Buffer) Close() error {123b.lock.Lock()124defer b.lock.Unlock()125126b.closed = true127128return nil129}130131/*132Closed returns true if the buffer has been closed133*/134func (b *Buffer) Closed() bool {135b.lock.Lock()136defer b.lock.Unlock()137138return b.closed139}140141/*142Contents returns all data ever written to the buffer.143*/144func (b *Buffer) Contents() []byte {145b.lock.Lock()146defer b.lock.Unlock()147148contents := make([]byte, len(b.contents))149copy(contents, b.contents)150return contents151}152153/*154Detect takes a regular expression and returns a channel.155156The channel will receive true the first time data matching the regular expression is written to the buffer.157The channel is subsequently closed and the buffer's read-cursor is fast-forwarded to just after the matching region.158159You typically don't need to use Detect and should use the ghttp.Say matcher instead. Detect is useful, however, in cases where your code must160be branch and handle different outputs written to the buffer.161162For 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.163164You could do something like:165166select {167case <-buffer.Detect("You are not logged in"):168169//log in170171case <-buffer.Detect("Success"):172173//carry on174175case <-time.After(time.Second):176177//welp178}179180buffer.CancelDetects()181182You 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.183184Finally, you can pass detect a format string followed by variadic arguments. This will construct the regexp using fmt.Sprintf.185*/186func (b *Buffer) Detect(desired string, args ...any) chan bool {187formattedRegexp := desired188if len(args) > 0 {189formattedRegexp = fmt.Sprintf(desired, args...)190}191re := regexp.MustCompile(formattedRegexp)192193b.lock.Lock()194defer b.lock.Unlock()195196if b.detectCloser == nil {197b.detectCloser = make(chan any)198}199200closer := b.detectCloser201response := make(chan bool)202go func() {203ticker := time.NewTicker(10 * time.Millisecond)204defer ticker.Stop()205defer close(response)206for {207select {208case <-ticker.C:209b.lock.Lock()210data, cursor := b.contents[b.readCursor:], b.readCursor211loc := re.FindIndex(data)212b.lock.Unlock()213214if loc != nil {215response <- true216b.lock.Lock()217newCursorPosition := cursor + uint64(loc[1])218if newCursorPosition >= b.readCursor {219b.readCursor = newCursorPosition220}221b.lock.Unlock()222return223}224case <-closer:225return226}227}228}()229230return response231}232233/*234CancelDetects cancels any pending detects and cleans up their goroutines. You should always call this when you're done with a set of Detect channels.235*/236func (b *Buffer) CancelDetects() {237b.lock.Lock()238defer b.lock.Unlock()239240close(b.detectCloser)241b.detectCloser = nil242}243244func (b *Buffer) didSay(re *regexp.Regexp) (bool, []byte) {245b.lock.Lock()246defer b.lock.Unlock()247248unreadBytes := b.contents[b.readCursor:]249copyOfUnreadBytes := make([]byte, len(unreadBytes))250copy(copyOfUnreadBytes, unreadBytes)251252loc := re.FindIndex(unreadBytes)253254if loc != nil {255b.readCursor += uint64(loc[1])256return true, copyOfUnreadBytes257}258return false, copyOfUnreadBytes259}260261262