Open-Source Wikis

/

CoreDNS

/

How to contribute

/

Testing

coredns/coredns

Testing

CoreDNS has three test surfaces: unit tests next to each plugin, integration tests under test/, and fuzz harnesses (*/fuzz.go). All of them run in CI.

Quick reference

go test ./...                              # everything
go test -race ./plugin/cache/...           # one plugin with race detector
go test -run TestServeDNS ./test           # one integration test
go test -count=1 ./...                     # disable test result cache
go test -tags=fuzz ./plugin/forward        # run fuzz-only test files

go test -race is what CI uses. Race-test failures often surface in cache, forward, and kubernetes because of their goroutine-heavy designs.

Unit tests

Live next to the code they cover:

plugin/cache/
├── cache.go
├── cache_test.go
├── handler.go
├── handler_test.go (none — covered by cache_test.go)
├── prefetch_test.go
├── setup_test.go
├── ...

Plugins typically expose a parse (or similarly-named) helper that takes a Caddyfile string and returns the configured plugin instance. Tests build a caddy.Controller from a string and assert on the resulting object:

c := caddy.NewTestController("dns", `cache 30 { prefetch 5 }`)
ca, err := cacheParse(c)

Other patterns:

  • dnstest.NewRecorder(w) from plugin/pkg/dnstest captures the response a plugin writes.
  • plugin/pkg/nonwriter is a buffering writer for tests that just want to inspect the produced message.
  • Most plugins ship a tiny log_test.go that initialises the plugin's logger so *_test.go files can call log.D.Set() without duplication.

Integration tests

test/ contains 51 *_test.go files that spin up a real CoreDNS in-process via test.Server (test/server.go). They cover Corefile parsing edge cases and end-to-end behaviours across plugin combinations.

Examples:

File What it tests
test/auto_test.go auto plugin watching a temp directory
test/etcd_test.go etcd backend (requires a local etcd)
test/forward_test.go, test/proxy_test.go, test/proxy_health_test.go Forwarder behaviours
test/grpc_test.go, test/quic_test.go, test/https_test.go, test/https3_test.go, test/tls_test.go Encrypted transports
test/multisocket_test.go SO_REUSEPORT listener parallelism
test/reload_test.go Caddy graceful reload
test/view_test.go View filtering
test/wildcard_test.go, test/server_reverse_test.go Zone match edge cases
test/presubmit_test.go Repository-wide checks (no proxy references, every plugin has a README, etc.)

The presubmit_test.go file is interesting: it asserts on the shape of the repo, not on runtime behaviour. It will fail if you forget to add a README.md for a new plugin, or if you accidentally introduce the deprecated proxy directive in tests.

test/server.go provides test.Server (a wrapper around caddy.Start) and test.TempFile (creates a temp file with given contents). Pretty much every integration test starts with one of these.

Fuzz tests

Five plugins ship fuzz harnesses:

File Targets
plugin/forward/fuzz.go Forward parser
plugin/cache/fuzz.go Cache key/insert
plugin/file/fuzz.go Master-file parser
plugin/kubernetes/object/... (where applicable) object parsing
test/fuzz_corefile.go Corefile parser

Fuzz harnesses are wired into OSS-Fuzz via .github/workflows/cifuzz.yml. The cifuzz workflow runs each harness for a short time on every PR.

Mocking and helpers

Package What it provides
plugin/pkg/dnstest NewRecorder, NewServer, Multiplexer
plugin/pkg/nonwriter A dns.ResponseWriter that just stores
plugin/test Case struct for table-driven DNS exchange tests, helpers for dns.RR slices
plugin/file/example_org.go A pre-built example.org zone for backend tests

A typical table-driven test:

cases := []test.Case{
    {Qname: "a.example.org.", Qtype: dns.TypeA,
     Answer: []dns.RR{test.A("a.example.org. IN A 127.0.0.1")}},
}
for _, tc := range cases {
    m := tc.Msg()
    rec := dnstest.NewRecorder(&test.ResponseWriter{})
    h.ServeDNS(ctx, rec, m)
    test.SortAndCheck(t, rec.Msg, tc)
}

What CI runs

The Go test workflow (.github/workflows/go.test.yml) does, in order:

  1. Set up Go (matrix on a few minor versions).
  2. make gen and git diff --exit-code to ensure committed generated files are current.
  3. go test -race ./....
  4. Optionally upload coverage.

Failures usually fall into two buckets:

  • Generated file drift — solved by make gen and committing the result.
  • Race conditions — usually surface in forward or cache. The race detector is unforgiving.

The cifuzz.yml workflow runs fuzz harnesses against the diff. The make.doc.yml workflow runs make -f Makefile.doc and asserts no diff. The golangci-lint.yml workflow runs the configured linters.

Local performance loop

CoreDNS doesn't ship a benchmark suite per se, but most plugins have Benchmark* tests:

go test -bench=. -benchmem -run='^$' ./plugin/cache/...
go test -bench=. -benchmem -run='^$' ./plugin/forward/...

benchstat from golang.org/x/perf/cmd/benchstat is used to compare before/after runs. Results are usually reported in PR descriptions when claiming a performance improvement.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Testing – CoreDNS wiki | Factory