Minimal Go-facing provider micro-library for Observer.
If you are new to this surface, start with HOWTO.md before reading the individual snippets and starters.
This aims at the same DX bar as the C, Rust, TypeScript, and Python libraries:
- authors write
Describe(...),Test(...),It(...), andExpect(...) - default identity is derived deterministically from explicit suite path plus test title
- optional
WithID(...)provides a refactor-stable override when needed - observation is bounded and opt-in through
ctx.Observe().* - direct host and embedded
observedispatch are owned by the library
The common path stays human-first. The deterministic boundary stays explicit.
HOWTO.md: detailed user manual covering authoring, determinism, host transport, inventory derivation, and end-to-end workflowobserver.go: public APIobserver_test.go: stdlib-only self-test coverage for the Go SDKgo.mod: installable Go module metadata for the standalone Go libraryexamples/example-smoke/: tiny collection and execution exampleexamples/host-example/: tiny directlistandrunhost exampleexamples/host-embed-example/: own-main styleobservenamespace examplestarter/: runnable project-shaped example with Go build, provider host build, inventory derivation, suite run, and snapshot verificationstarter-embedded/: runnable app-shaped example where the application keepsmain()and routesobserve ...through its own CLIstarter-embedded-failure/: runnable failing companion for the embedded app-shaped pathstarter-failure/: runnable failing companion showing the same provider flow with one intentionally failing exported test
If you want the full end-to-end workflow rather than isolated snippets, start with starter/, then read starter-embedded/, and then compare those with starter-failure/ and starter-embedded-failure/.
package main
import observer "github.com/frogfish/observer/lib/go/observer"
func main() {
tests := observer.MustCollectTests(func() {
observer.Describe("database", func() {
observer.Test("access to the database", func(ctx *observer.TestContext) {
ctx.Stdout("ok\n")
observer.Expect(true).ToBeTruthy()
})
})
})
_ = tests
}When WithID(...) is omitted, Observer derives a deterministic identity from suite path, test title, and duplicate occurrence order.
If a test wants a refactor-stable identity, it opts into WithID(...) explicitly:
observer.Test("access to the database", func(ctx *observer.TestContext) {
observer.Expect(true).ToBeTruthy()
}, observer.WithID("database/access"))If a test wants to emit observational data, it uses the author context directly:
observer.Test("access to the database", func(ctx *observer.TestContext) {
observe := ctx.Observe()
_ = observe.Metric("wall_time_ns", 104233.0)
_ = observe.Vector("request_latency_ns", []float64{1000.0, 1100.0, 980.0})
_ = observe.Tag("resource_path", "fixtures/config.json")
observer.Expect(true).ToBeTruthy()
}, observer.WithID("database/access"))- explicit
WithID(...), when present, must be non-empty - resolved canonical identities must be unique
- resolved targets must be unique
- deterministic sorting is by canonical name, then target
In this first cut, the resolved identity is used for both canonical name and target.
cd lib/go
go test ./...cd lib/go
go run ./examples/example-smokecd lib/go
go run ./examples/host-example -- list
go run ./examples/host-example -- observe --target pkg::smoke --timeout-ms 1000
go run ./examples/host-example -- run --target pkg::fail --timeout-ms 1000The library owns the standard provider host transport for Go too. A direct host can stay nearly trivial:
tests := observer.MustCollectTests(func() {
observer.Describe("pkg", func() {
observer.Test("smoke test", func(ctx *observer.TestContext) {
ctx.Stdout("ok\n")
observer.Expect(true).ToBeTruthy()
}, observer.WithID("pkg::smoke"))
})
})
if err := observer.ObserverHostMain("go", tests); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}For developer-facing usage, prefer observe. run remains available for compatibility with the standardized outer provider contract.
If a project already owns its CLI, the library can also serve an embedded observe namespace:
func main() {
tests := observer.MustCollectTests(func() {
observer.Describe("pkg", func() {
observer.Test("embedded smoke test", func(ctx *observer.TestContext) {
ctx.Stdout("ok\n")
observer.Expect(true).ToBeTruthy()
}, observer.WithID("pkg::embedded-smoke"))
})
})
if len(os.Args) > 1 && os.Args[1] == "observe" {
if err := observer.ObserverHostDispatchEmbedded("go", "observe", tests, os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
return
}
appMain(os.Args)
}Compile and run that path with:
cd lib/go
go run ./examples/host-embed-example -- observe list
go run ./examples/host-embed-example -- observe --target pkg::embedded-smoke --timeout-ms 1000
go run ./examples/host-embed-example