Testing
June 1, 2026 · View on GitHub
This document describes the testing strategy for the project, the different test categories, and when to use each.
Tests are organized in four categories, each with a distinct purpose and scope.
| Category | Location | Build Tag | Scope |
|---|---|---|---|
| Unit | tool/**/*_test.go, pkg/**/*_test.go | none | Single function or component in isolation. |
| Integration | test/integration/ | integration | Instrumented binary against a local or in-process dependency. |
| E2E | test/e2e/ | e2e | Multiple processes (e.g. client + server). |
| LatestLibBuild | test/latestlibbuild/ | latestlibbuild | Compile instrumented app against @latest of each instrumented library. |
Unit Tests
Important
When to write a unit test. Any change to a single function, hook, or internal component. If the behavior can be validated without building an instrumented binary, it belongs here.
Unit tests live next to the source code they exercise and require no build tags.
There are two main areas:
- Tool tests (
tool/). Cover the compile-time instrumentation pipeline: AST rewriting, import resolution, trampoline generation, package loading, and setup logic. Golden-file tests intool/internal/instrument/snapshot expected output and can be updated withmake test-unit/update-golden. - Package tests (
pkg/). Cover the runtime instrumentation hooks and semantic convention helpers. Each hook package has tests that verify span creation, context propagation, error recording, and the enable/disable mechanism viaOTEL_GO_ENABLED_INSTRUMENTATIONS/OTEL_GO_DISABLED_INSTRUMENTATIONS.
Golden-test helper packages
A golden testcase directory under tool/internal/instrument/testdata/golden/<name>/ may contain a helpers/ subdirectory with one or more Go packages. The test harness automatically discovers each subdirectory, compiles it into a .a archive, and registers it in the importcfg so the instrumented source can import it at compile time.
Use this convention when a testcase exercises call rules that reference wrapper functions from an external (non-stdlib) package via the imports: field. To add a new helper:
- Create
helpers/<pkgname>/<pkgname>.gowith the wrapper code (package name must match the directory name). - Reference the full import path in
rules.ymlunderimports:, the path is<root-module>/tool/internal/instrument/testdata/golden/<testname>/helpers/<pkgname>. - Create a placeholder for the golden file and run
make test-unit/update-goldento regenerate the.goldensnapshot.
Integration Tests
Important
When to write an integration test.
- Tool hook changes. Any change to the tool's code injection or the
HookContextinterface must be covered bybasic_test.go. It exercisespkg/instrumentation/basic/and validates the foundational hook machinery that all other instrumentations rely on. - Instrumentation package changes. Every package in
pkg/instrumentation/must have a corresponding integration test. If you add or modify a hook, there should be an integration test that builds an instrumented binary and asserts on the exported spans for that component.
Integration tests build real binaries with the otelc tool and run them against in-process dependencies (e.g. httptest.Server, in-process gRPC server, miniredis, testdb driver).
All test apps are built once in TestMain before any test runs (via otelc go build). Each test follows the same pattern:
- Start an in-memory OTLP collector.
- Run the pre-built instrumented binary against a local dependency.
- Assert on the exported spans and their semantic conventions.
E2E Tests
Important
When to write an E2E test. When the scenario involves multiple instrumented processes or services. Typical cases include context propagation across services, multi-service interactions or complex scenarios.
E2E tests spin up multiple processes (e.g. an instrumented client and an instrumented server) and verify they produce a coherent trace with spans from every participant sharing the same trace ID.
Test Applications
Minimal applications in test/apps/ serve as instrumentation targets. Each is a standalone Go module that the test infrastructure builds with otelc go build.
Shared helpers in test/testutil/ provide the OTLP collector, build/run wrappers, readiness probes, and semantic convention assertion functions used by both integration and E2E tests.
Running Tests
Note
Integration and e2e tests require make build (and make build-demo for gRPC/HTTP demos) before running.
# All tests
make test
# Unit tests
make test-unit # all unit tests
make test-unit/tool # tool only
make test-unit/pkg # pkg only
make test-unit/update-golden # update golden files
# Integration tests (requires: make build)
make test-integration
# E2E tests (requires: make build build-demo)
make test-e2e
# LatestLibBuild tests (requires: make build; mutates test/apps/*/go.mod — run git restore test/apps afterwards)
make test-latestlibbuild
# Coverage
make test-unit/coverage
make test-integration/coverage
make test-e2e/coverage
All test commands use -shuffle=on and -count=1 to avoid ordering issues and caching.
CI runs each category in a separate workflow across Linux (amd64/arm64), macOS (arm64), and Windows (amd64). See .github/workflows/test-*.yaml for details.
LatestLibBuild Tests
Important
What this test does. For each app under test/apps/, the test discovers the app's direct dependencies whose module paths match an instrumentation rule target:, bumps those dependencies to @latest, and verifies that otelc go build still succeeds. It is a compile-only check — no binary is executed and no spans are asserted. It runs on every pull request and blocks merge if it fails.
| Category | Location | Build Tag | Scope |
|---|---|---|---|
| LatestLibBuild | test/latestlibbuild/ | latestlibbuild | Compile instrumented app against @latest of each library. |
What a failure means
A failure means that the latest release of an upstream library introduced a compile-time API break that is incompatible with the current instrumentation hook. The remediation is:
- Cap the existing rule's version range in the relevant
pkg/instrumentation/.../*.yamlfile (e.g. change the version field fromv1.2.3tov1.2.3,v4.5.6). - Open a new rule entry covering
v4.5.6,and implement the updated hook.
Writing New Tests
- Pick the right category. Use the decision tree below.
- Follow existing patterns. Table-driven tests for units,
TestFixturefor integration/E2E. - Use semantic convention helpers.
testutil.RequireHTTPClientSemconv,RequireGRPCServerSemconv, etc. - Add a test app if needed. If the existing apps in
test/apps/don't cover your case, add a new minimal module there.