Delve — Testing#
Test metrics#
- Test files: 53 (including 1 fixture-internal test and 1 fuzz test)
- Total Go files: 506
- Ratio (test files / source files): ~12% (53 / 453 source files)
- Test frameworks: Stdlib
testingonly — no testify, gomock, ginkgo, or any assertion library
Test organization#
- Placement: Mixed — most test files use
package xxx_test(external black-box style) for the large integration tests andpackage xxx(white-box) for unit-level tests. The dominant pattern is_testpackage for service-layer tests (package service_test,package dap) and same-package for proc-level tests. - Helper packages:
pkg/proc/test(imported asprotest) — the primary test support library. Contains:BuildFixture(t, name, flags)— compiles a named fixture program from_fixtures/on demand with configurableBuildFlags(inlining, optimization, CGO, PIE, plugin, trimpath, DWZ compression, DWARF strip). Fixtures are cached by(name, flags)pair and compiled concurrently.RunTestsWithFixtures(m)— must be called fromTestMain; enables fixture compilation and cleans up temp binaries after all tests run.AllowRecording(t)/MustHaveRecordingAllowed(t)— opt-in / opt-out mechanism for rr backend testing.DefaultTestBackend(testBackend *string)— resolves thePROCTESTenv var to select the debug backend.MustSupportFunctionCalls(t, backend)— skips tests on platforms that don’t yet support function call injection.FindFixturesDir()— walks up 10 directory levels to find_fixtures/.skipOn(t, reason, conditions...)/skipUnlessOn(t, reason, conditions...)— runtime skip helpers that combineGOOS,GOARCH,testBackend, andbuildModechecks.
service/dap/daptest/— a DAP test client (client.go,resp.go) that wrapsnet.Connand thego-dapcodec to send DAP requests and assert responses in tests of the DAP server.
- Fixtures: 175
.goprograms in_fixtures/, each targeting a specific debugger scenario (variable evaluation, stepping, goroutines, CGO, plugins, eBPF, etc.). Fixtures are compiled at test time — never pre-built binaries — which ensures they are always compiled with the current Go toolchain and debug info.
Test patterns#
Table-driven tests#
- Prevalence: Heavy — 197 occurrences of
t.Run,testCases, ortc.namepatterns in test files. - Style: Anonymous or named struct slices — typically
testcases := []struct{ name string; ... }{ ... }followed byfor _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { ... }) }. Example invariables_test.go:116: a 30+ entry table covering every Go type for variable evaluation. - Example:
pkg/proc/variables_test.go:116—TestVariableEvaluationuses a[]struct{ name, st, value, length, cap, childrenlen }table with one entry per Go type.pkg/proc/evalop/evalop_test.go— table-driven opcode depth-check tests.service/dap/server_test.go:172— subtests parameterized over stop modes (client connected, client disconnected, etc.).
Multi-backend parameterization#
- Mechanism: The
-backendCLI flag (defaulting toPROCTESTenv var or"native") selects which backend (native,lldb,rr) is used whenwithTestProcess/withTestClientsets up a test target. Tests that cannot run under rr callprotest.MustHaveRecordingAllowed(t)to skip; tests that opt in callprotest.AllowRecording(t). - Significance: The same ~400 test functions in
pkg/proc/proc_test.go,variables_test.go, andservice/test/integration2_test.gorun against all three backends in CI. This is the mechanism that validates backend parity. - Example:
proc_test.go:108–130—startTestProcessArgsswitch ontestBackendroutes tonative.Launch,gdbserial.LLDBLaunch, orgdbserial.RecordAndReplay.
Mocking approach#
- Strategy: None — Delve’s tests use no mocking whatsoever. There are no generated mocks, no
gomockcontrollers, and no fake implementations ofproc.Processorproc.ProcessInternal. Tests exercise the real process backend against real compiled binaries. The only “fake” is the DAP test client (daptest/), which is a real client that speaks the real protocol over a pipe. - Rationale: Debugger correctness requires testing against real OS processes, real DWARF debug info, and real CPU state. A mock
ProcessInternalwould be useless for validating ptrace behavior or register reads.
Integration tests#
- Present: Yes — two large integration test files:
service/test/integration2_test.go(3,491 lines) — exercises the full JSON-RPC 2.0 stack: starts a realrpccommon.NewServerover anet.Pipe(), creates anrpc2.NewClient, and drives breakpoints, stepping, variable evaluation, goroutine listing, etc. through the public service API.service/dap/server_test.go(8,655 lines) — exercises the full DAP stack: starts adap.NewServer, usesdaptest.NewClientto send initialize/launch/setBreakpoints/continue/etc. DAP requests, and asserts the JSON responses. This is the largest test file in the project.
- How: In-process, via
net.Pipe()— no Docker, no external services, no testcontainers. The test spins up the server goroutine and client in the same process, connected by a pipe. - Separation: Not separated into a distinct package or build tag — they live alongside unit tests but are organized by layer (service/ vs pkg/).
Platform-specific tests#
- Files:
proc_linux_test.go,proc_darwin_test.go,proc_darwin_amd64_test.go,proc_amd64_test.go,proc_unix_test.go,proc_general_test.go,debugger_unix_test.go,sameuser_linux_test.go— mirroring the filename-based conditional compilation used in production code. - Build tags: Only 3 test files use
//go:buildtags:proc_unix_test.go(linux || darwin),ebpf/helpers_test.go(linux && amd64 && cgo && go1.16),debugger_unix_test.go(!windows). All other platform restrictions use runtimeskipOn()calls, keeping the tests in a single build. - skipOn pattern:
skipOn(t, "broken", "linux", "386", "pie")— takes a reason string and 1-N conditions that are AND-matched againstGOOS,GOARCH,testBackend, andbuildMode. This is more expressive than build tags for conditions that combine OS, arch, and backend.
Fuzz testing#
- Present:
pkg/proc/variables_fuzz_test.go—FuzzEvalExpressionfuzzes the variable loader and expression evaluator. Requires a one-time setup step (-fuzzevalexpressionsetup) to compile a fixture binary and capture a core dump, then runsgo test -fuzz FuzzEvalExpressionreplaying mutations against the frozen core dump. This is a high-value fuzz target: expression evaluation is a complex parser/interpreter operating on unsafe memory.
eBPF tests#
- Special handling: eBPF tests (
TestTraceEBPF*) require elevated Linux capabilities (CAP_BPF,CAP_PERFMON,CAP_SYS_RESOURCE) and must be run withsudo. The CLAUDE.md documents running these under Docker with--privilegedfor CI environments that cannot grant kernel capabilities to the test runner.
Test quality observations#
What’s done well:
- Fixture compile-on-demand system (
protest.BuildFixture) is an elegant solution to the hard problem of testing a debugger: tests need real compiled binaries with specific debug info characteristics (inlining on/off, optimization on/off, PIE, plugins). Fixtures are built at test time, cached by key, cleaned up automatically, and compile flags are first-class parameters. - Backend-agnostic test design: Writing a single test function that runs identically against
native,lldb, andrrbackends enforces a strong behavioral contract on all backends and catches regressions immediately. TheAllowRecording/MustHaveRecordingAlloweddouble-opt-in pattern is pragmatic about the subset of tests that are compatible with record/replay. - No mocking discipline: The decision to never mock
ProcessInternalmeans tests validate real system behavior. This trades test speed for correctness — appropriate for a debugger where correctness is paramount. - Table-driven variable tests:
TestVariableEvaluationandTestEvalExpressionexhaustively cover Go type system edge cases in tabular form, making it easy to add a new type assertion without writing a new function. - DAP server test coverage:
server_test.goat 8,655 lines is extremely comprehensive — it tests every DAP request type, edge cases in attach/launch/stop/disconnect flows, and output event formatting. This is the most thorough protocol-level test coverage in the project. - Fuzz corpus for expression evaluator: Having a fuzz test (
FuzzEvalExpression) for the most complex and security-relevant subsystem (parsing and executing DWARF expressions against process memory) is best practice for a tool that handles untrusted binaries.
- Fixture compile-on-demand system (
What could improve:
- No mocking means slow tests: Every test that exercises variable evaluation starts a real OS process. Tests in
proc_test.go(6,171 lines) are real process integration tests, not unit tests. Fast iteration is expensive. There is no unit test layer for the expression evaluator that doesn’t require a live process. - Test file size:
server_test.goat 8,655 lines andproc_test.goat 6,171 lines are enormous. Navigation and review are challenging. Sub-package or file splitting would improve discoverability without changing test quality. - Limited CI file visibility: Only a CI config for Windows ARM64 and a release workflow are visible in
.github/workflows/. The main Linux/macOS CI matrix is not in the repository (possibly in a separate CI system or omitted from the analyzed snapshot), which makes it hard to observe the full test matrix.
- No mocking means slow tests: Every test that exercises variable evaluation starts a real OS process. Tests in
Patterns worth emulating:
- The
protestfixture system: Any project that needs to test behavior against compiled artifacts (compilers, linkers, analyzers, debuggers) should adopt this pattern — compile-on-demand with flag parameterization, lifecycle management inTestMain, cleanup via deferred path registration. - Multi-backend test parameterization via flags: Running the same test suite against multiple backends with a flag (not build tags) is more flexible and catches divergence earlier than maintaining separate test suites.
- The
skipOn(reason, conditions...)helper: More ergonomic than build tags for multi-dimensional skip conditions. The reason string is invaluable for understanding why a test is skipped, especially when conditions reference issue URLs. - Protocol-level testing with an in-process client:
daptest.NewClientovernet.Pipe()tests the full serialization/deserialization path and response ordering without network overhead or external process management. This pattern generalizes to any protocol server test.
- The