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