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>
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 errorlog 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.NewServerbinds to127.0.0.1; Go resolves loopback addresses without a DNS query, so the DNS row does not appear in localhost test output. Tests useTCP connectas the success-path phase assertion instead.- The test suite requires no network access for any case except DNS failure,
which uses the reserved
.invalidTLD (RFC 6761 — always NXDOMAIN).
Code changes included in this step
Beyond the tests, two code changes were made:
-
main.gorefactor — extractedrun(args []string, stdout, stderr io.Writer) intso 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. -
probe.goTLS classification fix — Go'shttptracecallsTLSHandshakeDonewith the error on a failed handshake, sotlsDonewas set even on cert rejection. The previous classifier ("tlsStart set, tlsDone zero") never matched. Fixed by capturingtlsErrfrom the hook and checking it inclassifyErr.