Files
http-latency-prober/docs/usage/step-6-integration-tests.md
Jan Novak a9534ec2c1 test(go): step 6 — integration tests covering all exit codes and output
Extract run(args, stdout, stderr) int from main() for in-process
testability. Fix TLS failure classification (tlsErr now captured from
TLSHandshakeDone hook). Add run_test.go with 14 table-driven in-process
tests and cli_test.go with TestMain + 3 subprocess smoke tests. All
servers use httptest; .invalid TLD for deterministic DNS failures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 01:00:20 +02:00

3.6 KiB

Step 6 — Integration Tests

What this step delivers

A full integration-test suite covering every success and failure mode of the latprobe CLI. Tests are written using the Go standard library only (testing, net/http/httptest, os/exec, encoding/json) — no third-party dependencies.

Two test layers:

Layer File How it runs
In-process go/run_test.go Calls run(args, stdout, stderr) directly; fast, covers the full matrix
Subprocess go/cli_test.go Builds the real binary once, exec.Commands it; exercises the true os.Exit path

Running the tests

cd go

# Run all tests (quiet)
go test ./...

# Run with per-case output
go test -v ./...

# Run only in-process tests
go test -v -run TestRunMatrix .
go test -v -run TestJSON .

# Run only subprocess smoke tests
go test -v -run TestCLI .

Test matrix

In-process (run_test.go)

Test case Exit code Assertion
Success 200 0 stdout contains (200), TCP connect, Total
HTTP 500, no --fail 0 stdout contains (500), Total
HTTP 404, --fail 6 stdout contains 404 ✗
DNS failure (.invalid TLD) 2 stdout contains ✗ dns:
Connection refused (listen-then-close) 3 stdout contains ✗ connect:
Timeout (--timeout 200ms, blocking handler) 4 stdout contains ✗ timeout:
TLS failure (self-signed cert) 5 stdout contains ✗ tls:
Multiple URLs (200 + .invalid) 2 (highest) stdout contains both (200) and ✗ dns:
Sampling -n 3, all success 0 stdout contains 3 samples, min, avg, max
No args 1 stderr contains Usage:
-h 0 stderr contains Usage:
--json success 0 valid JSON, phases.total present, failed == 0
--json DNS failure 2 valid JSON, errors[0].phase == "dns", succeeded == 0
--json -n 3 success 0 succeeded == 3, total.min_ms > 0, max_ms >= min_ms

Subprocess smoke tests (cli_test.go)

TestMain builds the binary with go build -o <tmp>/latprobe . once before any test runs. The binary is deleted on test completion.

Test Checks
TestCLISuccess Local server, exit 0, stdout has (200) and Total
TestCLIDNSFailure .invalid host, real binary exits 2
TestCLIJSONDNSFailure .invalid host, JSON output, errors[0].phase == "dns"

Notes

  • The http: TLS handshake error log line printed during the TLS test is the server-side log of the client correctly rejecting the self-signed cert. It is expected and harmless.
  • httptest.NewServer binds to 127.0.0.1; Go resolves loopback addresses without a DNS query, so the DNS row does not appear in localhost test output. Tests use TCP connect as the success-path phase assertion instead.
  • The test suite requires no network access for any case except DNS failure, which uses the reserved .invalid TLD (RFC 6761 — always NXDOMAIN).

Code changes included in this step

Beyond the tests, two code changes were made:

  1. main.go refactor — extracted run(args []string, stdout, stderr io.Writer) int so the CLI is testable in-process. main() is now a one-liner: os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)). No behaviour change.

  2. probe.go TLS classification fix — Go's httptrace calls TLSHandshakeDone with the error on a failed handshake, so tlsDone was set even on cert rejection. The previous classifier ("tlsStart set, tlsDone zero") never matched. Fixed by capturing tlsErr from the hook and checking it in classifyErr.