From gomega
Provides gbytes.Buffer (io.Writer/Reader/Closer) and the Say regexp matcher for ordered assertions on streaming output. Use with Eventually for polling process stdout/stderr or API streams.
How this skill is triggered — by the user, by Claude, or both
Slash command
/gomega:gbytesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
`gbytes` provides `*gbytes.Buffer`, an in-memory `io.Writer` (and `io.Reader`/`io.Closer`) that captures everything written to it, plus the `Say` matcher for making **ordered** assertions against streaming data as it arrives. Pairs naturally with `gomega:async` (poll with `Eventually`) and `gomega:gexec` (a session's `.Out`/`.Err` are `*gbytes.Buffer`s, and the session itself is a `BufferProvid...
gbytes provides *gbytes.Buffer, an in-memory io.Writer (and io.Reader/io.Closer) that captures everything written to it, plus the Say matcher for making ordered assertions against streaming data as it arrives. Pairs naturally with gomega:async (poll with Eventually) and gomega:gexec (a session's .Out/.Err are *gbytes.Buffers, and the session itself is a BufferProvider). Docs: https://onsi.github.io/gomega/#gbytes-testing-streaming-buffers.
import (
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
)
gbytes.NewBuffer() returns an empty *gbytes.Buffer. Hand it to anything that wants an io.Writer and it accumulates the bytes:
buffer := gbytes.NewBuffer()
go client.AttachToDataStream(buffer) // client writes to buffer concurrently
gbytes.BufferWithBytes(b []byte) — seed a buffer with already-captured bytes.buffer.Contents() []byte — all bytes ever written, regardless of cursor position. Use this for whole-buffer assertions (Expect(buffer.Contents()).To(ContainSubstring(...))).buffer.Close() / buffer.Closed() — mark a buffer done. Writes to a closed buffer error. A closed buffer tells Eventually to give up (see below).Say matchergbytes.Say(pattern, args...) takes a regular expression (optionally fmt.Sprintf-formatted with args) and matches against the buffer's unread portion:
Expect(buffer).To(gbytes.Say(`hello \w+`))
Say is cursor-based, not whole-buffer. Each buffer carries an opaque read cursor you cannot access. When Say matches, it fast-forwards the cursor to just past the match. The next Say only sees bytes after that point. This is what makes ordered assertions work — and the gotcha below.
Eventually + SayStreaming output arrives over time, so poll the buffer with Eventually (gomega:async). Each successful Say advances the cursor, so successive Says assert on successive output in order:
Eventually(buffer).Should(gbytes.Say(`Attached as client \d+`))
client.ReticulateSplines()
Eventually(buffer).Should(gbytes.Say(`reticulating splines`))
client.EncabulateRetros(7)
Eventually(buffer).Should(gbytes.Say(`encabulating 7 retros`))
Because the cursor only moves forward, this counts repeats correctly — these two assertions pass only if reticulating splines appears twice:
client.ReticulateSplines()
Eventually(buffer).Should(gbytes.Say(`reticulating splines`))
client.ReticulateSplines()
Eventually(buffer).Should(gbytes.Say(`reticulating splines`))
And consequently this (counterintuitively) passes — the first Say consumed the match:
Eventually(buffer).Should(gbytes.Say(`reticulating splines`))
Consistently(buffer).ShouldNot(gbytes.Say(`reticulating splines`))
Say works on a *gbytes.Buffer or any BufferProvider (anything with a Buffer() *gbytes.Buffer method, e.g. a gexec.Session), so Eventually(session).Should(gbytes.Say(...)) works directly.
When the test must react to whichever output arrives, use buffer.Detect(regexp, args...), which returns a channel that fires once on match (and fast-forwards the cursor). Always CancelDetects() to clean up the spawned goroutines:
client.Authorize()
select {
case <-buffer.Detect("You are not logged in"):
client.Login()
case <-buffer.Detect("Success"):
// carry on
case <-time.After(time.Second):
Fail("timed out waiting for output")
}
buffer.CancelDetects()
io.Reader/io.Writer/io.CloserThese interfaces are expected to block, so calling Read/Write/Close directly in a test risks hanging forever. Wrap them with timeouts; the wrappers return gbytes.ErrTimeout if the operation doesn't complete in time:
p := make([]byte, 5)
_, err := gbytes.TimeoutReader(reader, time.Second).Read(p)
Expect(err).NotTo(HaveOccurred())
gbytes.TimeoutReader, gbytes.TimeoutWriter, and gbytes.TimeoutCloser each wrap the matching interface. To use Say against an io.Reader, wrap it with gbytes.BufferReader(reader) — it launches an io.Copy goroutine into a fresh buffer (closed when the copy completes). Because the copy is async you must use Eventually:
Eventually(gbytes.BufferReader(reader)).Should(gbytes.Say("abcde"))
(, ), ., [, +, ?, \d, etc. are regex syntax. To match a literal splines (v2) write gbytes.Say(splines (v2)). The pattern is compiled with regexp.MustCompile, so a bad pattern panics.Say already consumed. If you need to assert on the whole buffer, use buffer.Contents() with a string matcher (gomega:matchers) instead of Say.Say with Eventually for live streams; use plain Expect only on an already-complete buffer. A bare Expect(buffer).To(Say(...)) checks the buffer once, right now — it will flake if the data hasn't arrived yet.Eventually early. Once Close() is called, a pending Say can never succeed on new data, so the matcher signals Eventually/Consistently to stop polling and fail immediately rather than wait out the timeout.npx claudepluginhub onsi/gomega --plugin gomegaBuilds Go binaries, runs them as subprocesses, sends signals, and asserts on exit codes and stdout/stderr with Gomega matchers.
Makes Ginkgo specs interruptible with SpecContext cancellation, timeouts, and graceful cleanup. For hanging specs, polling, streams, goroutines, or async testing with Gomega.
Provides guidance on testify for Go testing: assert vs require, core assertions, mocks, and suites. Activates when code imports github.com/stretchr/testify.