92 lines
4.3 KiB
Markdown
92 lines
4.3 KiB
Markdown
# Testing Policy
|
||
|
||
## DNS Resolution Tests
|
||
|
||
All tests that involve DNS resolution — in every package, including
|
||
consumers of the resolver such as the watcher — **MUST** use live
|
||
queries against real DNS servers. No mocking, faking, or stubbing of
|
||
DNS at any layer is permitted.
|
||
|
||
### Rationale
|
||
|
||
The resolver performs iterative resolution from root nameservers through
|
||
the full delegation chain. Mocked responses cannot faithfully represent
|
||
the variety of real-world DNS behavior (truncation, referrals, glue
|
||
records, DNSSEC, varied response times, EDNS, etc.). Testing against
|
||
real servers ensures the resolver works correctly in production.
|
||
Robustness comes from handling real-world DNS behavior with tolerant
|
||
assertions and sensible timeouts, not from mocks.
|
||
|
||
### Constraints
|
||
|
||
- Tests hit real DNS infrastructure and require network access
|
||
- Test duration depends on network conditions; timeout tuning keeps
|
||
the suite within the 60-second target
|
||
- Query timeout is calibrated to 3× maximum antipodal RTT (~300ms)
|
||
plus processing margin
|
||
- Root server fan-out is limited to reduce parallel query load
|
||
- Flaky failures from transient network issues are acceptable and
|
||
should be investigated as potential resolver bugs, not papered over
|
||
with mocks or skip flags
|
||
- Watcher change-detection tests seed a synthetic *previous state*
|
||
and compare it against fresh live lookups; the DNS side is never
|
||
faked
|
||
- Live query concurrency is bounded per package (`liveGate` in
|
||
`internal/resolver`, `liveWatcherGate` in `internal/watcher`) so
|
||
parallel tests do not burst at the root servers
|
||
- Those gates are package-scoped and therefore per test binary, so
|
||
`script/test` also passes `-p 1`: with both live-DNS packages
|
||
running at once the gates sum instead of holding, and the resolver's
|
||
per-attempt deadlines start expiring
|
||
|
||
### Transport failures: loopback nameservers, not mocks
|
||
|
||
The resolver classifies a nameserver that stays silent as
|
||
`StatusTimeout` and one that answers SERVFAIL as `StatusError`. The
|
||
public network cannot be made to produce either on demand — a
|
||
black-holed address is only black-holed on some networks, and build
|
||
environments that transparently intercept UDP/53 answer it locally —
|
||
so a test built on a chosen remote address asserts on the network it
|
||
happens to run on rather than on the resolver.
|
||
|
||
`internal/resolver/transport_test.go` binds a real nameserver on
|
||
`127.0.0.1` instead and points the query at it.
|
||
`nameserverAddr` dials an address that already carries a port as
|
||
written, so no production behaviour is bypassed to arrange this.
|
||
|
||
**This is permitted, and it is not a mock.** The rule above bans
|
||
substituting `DNSClient` or any other DNS abstraction, which lets the
|
||
code under test skip DNS and hands it a manufactured verdict. A
|
||
loopback nameserver does the opposite: the resolver dials a real
|
||
socket, writes a real query with the real `miekg/dns` client, and
|
||
applies its real deadline and its real classification logic to what
|
||
comes back. Choosing which nameserver a live query is sent to is not
|
||
faking DNS — the resolver is aimed at a nameserver of the caller's
|
||
choosing in production too.
|
||
|
||
The distinction to hold on to: **substituting the client is banned;
|
||
choosing the server is not.** A test that reaches for a fake
|
||
`DNSClient` to force a classification is still forbidden, no matter
|
||
how awkward the alternative looks.
|
||
|
||
Such a test must stay cheap. The resolver asks for eight record
|
||
types and retries each once, so a nameserver silent on every type
|
||
costs sixteen query timeouts. `TestQueryNameserverIP_Timeout` is
|
||
silent on `A` alone and answers the rest, which is all the resolver
|
||
needs to classify the response and keeps the test to two.
|
||
|
||
### What NOT to do
|
||
|
||
- **Do not mock `DNSClient`**, the watcher's `DNSResolver` interface,
|
||
or any other DNS abstraction — in any package, for any reason
|
||
- **Do not add `-short` flags** to skip slow tests
|
||
- **Do not increase `-timeout`** to hide hanging queries
|
||
- **Do not remove `-count=1` from `script/test`** — Go's test cache
|
||
replays a previous run's output without querying anything, so a
|
||
cached pass is not evidence that live resolution works
|
||
- **Do not remove `-p 1` from `script/test`** — running the live-DNS
|
||
packages in parallel oversubscribes live DNS past what their gates
|
||
bound, which surfaces as unrelated resolver tests failing on
|
||
expired deadlines
|
||
- **Do not modify linter configuration** to suppress findings
|