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>
91 lines
3.6 KiB
Markdown
91 lines
3.6 KiB
Markdown
# 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.Command`s it; exercises the true `os.Exit` path |
|
|
|
|
## Running the tests
|
|
|
|
```sh
|
|
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`.
|