Skip to content

add go test & JUnit OpenTelemetry emitting#2

Open
vito wants to merge 21 commits intomainfrom
gotest
Open

add go test & JUnit OpenTelemetry emitting#2
vito wants to merge 21 commits intomainfrom
gotest

Conversation

@vito
Copy link
Copy Markdown
Contributor

@vito vito commented Apr 7, 2026

Adds two CLIs:

  • ./cmd/otelgotest: a drop-in go test replacement that emits test data to OpenTelemetry
    • usage: otelgotest ./... (same flags as go test, just passes through)
  • ./cmd/oteljunit: loads JUnit XML and re-emits it to OpenTelemetry
    • usage: oteljunit report.xml

And a handful of packages:

  • ./oteltestctx: replaces github.com/dagger/testctx/oteltest and coordinates with otelgotest via a Unix domain socket to propagate otelgotest-created span context down into each test's context.Context
  • ./gotest: reads a go test -json stream and re-emits it to OTel (logic behind otelgotest)
  • ./gotestmain: provides a TestMain wrapper that wires up OTel, mostly redundant but doesn't hurt to have as an option
  • ./junit: logic behind oteljunit

image

vito added 18 commits April 6, 2026 21:32
Adds three packages for converting Go test results to OTel spans:

- gotest: streaming go test -json to OTel span converter
- gotestmain: TestMain helper for in-process instrumentation
- cmd/otelgotest: go test -exec wrapper for zero-config use

Each test becomes a span with subtest nesting, pass/fail/skip status,
log output as span events, and optional OTel log record routing via
SpanStdio. When no OTLP endpoint is configured, tests run with no
overhead.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Adds a junit package that parses JUnit XML via go-junit and emits
OTel spans mirroring the suite/test hierarchy. Each suite becomes a
parent span with child spans per test case, carrying pass/fail/skip
status, test output as span events, and optional log records via
SpanStdio.

The cmd/oteljunit CLI reads JUnit XML from files or stdin and
exports the spans to the configured OTLP endpoint.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Replace the hand-crafted JUnit XML with output from go-junit-report
run against the sample test suite. Adjust tests to match the real
format: empty suite name (falls back to "suite"), generic failure
messages, no per-test system-out, flat subtest names.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Add go:generate directive that pipes gotest/testdata/sample.jsonl
through go-junit-report to produce the JUnit XML fixture. Track
go-junit-report as a tool dependency in go.mod.

The JSON parser produces richer output than piping raw test output:
suite names, classnames, per-test system-out, and failure bodies
are all populated.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Add the sample test source to gotest/testdata/sample/ and a
go:generate directive that runs it with go test -json to produce
sample.jsonl. The JUnit fixture chains from the same source:

  go generate ./gotest/  # sample_test.go -> sample.jsonl
  go generate ./junit/   # sample.jsonl -> sample.xml

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Stop splitting test names on '/' for span names since JUnit can't
express hierarchy anyway. Parse the timestamp attribute from the
testsuite XML element to set proper start/end times on spans
instead of calculating backwards from time.Now().

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Handle package-level JSON events (start/pass/fail) to create a
parent span per package instead of emitting all test spans at the
top level. Top-level tests are parented under their package span,
subtests remain nested under their parent test.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Add a Unix socket sideband so that test binaries using oteltestctx
can adopt span contexts created by otelgotest. When otelgotest
creates a test span from JSON events, it registers the span context
in a SpanContextRegistry. A socket server responds to requests from
the test binary with the traceparent for each test.

This lets in-process instrumented operations (e.g. Dagger calls)
descend from the externally created test span without any
configuration flags.

Also fixes otelgotest package detection to use the full import path
by walking up to go.mod from the working directory.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Move the oteltest middleware into this repo as oteltestctx so it
can share types with gotest and evolve the socket protocol without
a circular dependency.

WithTracing checks OTEL_TEST_SOCKET and adopts the external span
context when available, falling back to local span creation. The
socket is skipped when a custom TracerProvider is provided (e.g.
unit tests with span recorders). Subprocess tests clear the env
var to avoid connecting to the parent's socket.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Apply the same testify error message cleaning used in oteltestctx
to gotest's extractErrorOutput. Strips verbose Error Trace and Test
sections, keeping only the Error and Messages content for the span
status description.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Verify that testify-formatted error messages in test2json output
are cleaned to just the Error/Messages content for span status
descriptions, both standalone and mixed with regular log noise.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Go's testing.decorate adds 4 spaces of indentation to continuation
lines of multi-line error messages. This caused cleanTestifyMessage
to miss the tab-prefixed testify sections since lines started with
spaces instead of tabs. Strip leading spaces before checking for
the tab pattern. Updated integration tests to use the realistic
format with the 4-space prefix.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Replace the go test -exec wrapper with a drop-in go test replacement
that runs go test -json internally. This preserves test caching, which
the -exec approach unconditionally invalidated.

The socket protocol for cross-process span context propagation now
uses package-qualified keys (e.g., "example.com/pkg/TestFoo") so
that a single shared socket works across all packages without
collisions. The oteltestctx package auto-detects its package path
at init time via debug.ReadBuildInfo.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Packages with no test files emit a skip action at the package level.
The span is started but never ended because there is no handler for
the skip case.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Packages with no test files emit a skip action at the package level.
Handle it by ending the span with the skipped status.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Remove AddEvent from gotest and junit — SpanStdio is the sole OTel
log mechanism. Remove WithLogging from oteltestctx — gotest.Run
handles test output logging externally. Previously each output line
generated a span event, a SpanStdio log record from gotest.Run, and
another SpanStdio log record from WithLogging, tripling OTel updates
and causing O(N²) rendering in Dagger's progress view.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
go test -json always forces -test.v=test2json, making
testing.Verbose() return true. While we can't change that, we can
control the reconstructed human-readable output. In non-verbose mode
(the default), per-test output is buffered and only flushed for
failing tests. Package-level output passes through unconditionally.

otelgotest now detects -v in user args and sets WithVerbose
accordingly.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
@vito vito changed the title add go test and JUnit OpenTelemetry emitting add go test & JUnit OpenTelemetry emitting Apr 8, 2026
Keep a leading go -C flag ahead of the injected -json flag so
commands like otelgotest -C ./foo/bar ./... translate to a valid
go test invocation. Add unit coverage for both -C forms and the
existing non--C behavior.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
vito added 2 commits April 8, 2026 20:14
Derive test hierarchy from go test -json run, pause, cont, and
completion events instead of splitting ev.Test on '/'. This keeps
slash-containing leaf names attached to their real parents and
avoids misparenting ambiguous or interleaved parallel subtests.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
When users pass -json, keep printing the raw go test JSON
stream to stdout while still parsing it for spans. This
preserves the requested output mode the same way we already
handle -v.

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant