podman
1/*
2Package gbytes provides a buffer that supports incrementally detecting input.
3
4You 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.
5
6Subsequent matches against the buffer will only operate against data that appears *after* the read cursor.
7
8The 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
9access the entire buffer's contents with Contents().
10
11*/
12package gbytes13
14import (15"errors"16"fmt"17"io"18"regexp"19"sync"20"time"21)
22
23/*
24gbytes.Buffer implements an io.Writer and can be used with the gbytes.Say matcher.
25
26You 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!
27*/
28type Buffer struct {29contents []byte30readCursor uint6431lock *sync.Mutex32detectCloser chan interface{}33closed bool34}
35
36/*
37NewBuffer returns a new gbytes.Buffer
38*/
39func NewBuffer() *Buffer {40return &Buffer{41lock: &sync.Mutex{},42}43}
44
45/*
46BufferWithBytes returns a new gbytes.Buffer seeded with the passed in bytes
47*/
48func BufferWithBytes(bytes []byte) *Buffer {49return &Buffer{50lock: &sync.Mutex{},51contents: bytes,52}53}
54
55/*
56BufferReader returns a new gbytes.Buffer that wraps a reader. The reader's contents are read into
57the Buffer via io.Copy
58*/
59func BufferReader(reader io.Reader) *Buffer {60b := &Buffer{61lock: &sync.Mutex{},62}63
64go func() {65io.Copy(b, reader)66b.Close()67}()68
69return b70}
71
72/*
73Write implements the io.Writer interface
74*/
75func (b *Buffer) Write(p []byte) (n int, err error) {76b.lock.Lock()77defer b.lock.Unlock()78
79if b.closed {80return 0, errors.New("attempt to write to closed buffer")81}82
83b.contents = append(b.contents, p...)84return len(p), nil85}
86
87/*
88Read implements the io.Reader interface. It advances the
89cursor as it reads.
90*/
91func (b *Buffer) Read(d []byte) (int, error) {92b.lock.Lock()93defer b.lock.Unlock()94
95if uint64(len(b.contents)) <= b.readCursor {96return 0, io.EOF97}98
99n := copy(d, b.contents[b.readCursor:])100b.readCursor += uint64(n)101
102return n, nil103}
104
105/*
106Clear clears out the buffer's contents
107*/
108func (b *Buffer) Clear() error {109b.lock.Lock()110defer b.lock.Unlock()111
112if b.closed {113return errors.New("attempt to clear closed buffer")114}115
116b.contents = []byte{}117b.readCursor = 0118return nil119}
120
121/*
122Close signifies that the buffer will no longer be written to
123*/
124func (b *Buffer) Close() error {125b.lock.Lock()126defer b.lock.Unlock()127
128b.closed = true129
130return nil131}
132
133/*
134Closed returns true if the buffer has been closed
135*/
136func (b *Buffer) Closed() bool {137b.lock.Lock()138defer b.lock.Unlock()139
140return b.closed141}
142
143/*
144Contents returns all data ever written to the buffer.
145*/
146func (b *Buffer) Contents() []byte {147b.lock.Lock()148defer b.lock.Unlock()149
150contents := make([]byte, len(b.contents))151copy(contents, b.contents)152return contents153}
154
155/*
156Detect takes a regular expression and returns a channel.
157
158The channel will receive true the first time data matching the regular expression is written to the buffer.
159The channel is subsequently closed and the buffer's read-cursor is fast-forwarded to just after the matching region.
160
161You 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
162be branch and handle different outputs written to the buffer.
163
164For 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.
165
166You could do something like:
167
168select {
169case <-buffer.Detect("You are not logged in"):
170//log in
171case <-buffer.Detect("Success"):
172//carry on
173case <-time.After(time.Second):
174//welp
175}
176buffer.CancelDetects()
177
178You 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.
179
180Finally, you can pass detect a format string followed by variadic arguments. This will construct the regexp using fmt.Sprintf.
181*/
182func (b *Buffer) Detect(desired string, args ...interface{}) chan bool {183formattedRegexp := desired184if len(args) > 0 {185formattedRegexp = fmt.Sprintf(desired, args...)186}187re := regexp.MustCompile(formattedRegexp)188
189b.lock.Lock()190defer b.lock.Unlock()191
192if b.detectCloser == nil {193b.detectCloser = make(chan interface{})194}195
196closer := b.detectCloser197response := make(chan bool)198go func() {199ticker := time.NewTicker(10 * time.Millisecond)200defer ticker.Stop()201defer close(response)202for {203select {204case <-ticker.C:205b.lock.Lock()206data, cursor := b.contents[b.readCursor:], b.readCursor207loc := re.FindIndex(data)208b.lock.Unlock()209
210if loc != nil {211response <- true212b.lock.Lock()213newCursorPosition := cursor + uint64(loc[1])214if newCursorPosition >= b.readCursor {215b.readCursor = newCursorPosition216}217b.lock.Unlock()218return219}220case <-closer:221return222}223}224}()225
226return response227}
228
229/*
230CancelDetects cancels any pending detects and cleans up their goroutines. You should always call this when you're done with a set of Detect channels.
231*/
232func (b *Buffer) CancelDetects() {233b.lock.Lock()234defer b.lock.Unlock()235
236close(b.detectCloser)237b.detectCloser = nil238}
239
240func (b *Buffer) didSay(re *regexp.Regexp) (bool, []byte) {241b.lock.Lock()242defer b.lock.Unlock()243
244unreadBytes := b.contents[b.readCursor:]245copyOfUnreadBytes := make([]byte, len(unreadBytes))246copy(copyOfUnreadBytes, unreadBytes)247
248loc := re.FindIndex(unreadBytes)249
250if loc != nil {251b.readCursor += uint64(loc[1])252return true, copyOfUnreadBytes253}254return false, copyOfUnreadBytes255}
256