Why can go test fail with a vet diagnostic before any of your tests run?
answer
- the test binary was never built
- go test does more than compile and run
- a curated always-worth-fixing list
- a wrong Test signature counts as a failure
- one flag turns the step off
basics
~20 sgo test runs a curated subset of go vet over the package before building anything. A diagnostic there aborts the run, so you see a vet message and no PASS or FAIL lines. The -vet=off flag skips the step.
solid answer
~50 s`go test` does more than compile and run: before building the test binary it runs `go vet` over the package with a curated list of checks the Go team considers always worth fixing — among them `printf`, `tests` (malformed Test, Benchmark and Example signatures and names), `atomic`, `bool`, `errorsas`, `ifaceassert`, `nilfunc` and `stringintconv`. If any of them fires, the package fails immediately and you get a vet diagnostic with no test output at all, which is confusing the first time because the message often points at non-test code. The intended response is to fix the code; `go test -vet=off ./...` disables the step, and `-vet=` also accepts a comma-separated list of check names if you want a different set. Note that this is a deliberately small subset: the full `go vet` suite, which includes `copylocks`, `lostcancel` and `unusedresult`, is not run for you.
code
go · 9 linesfunc TestParseConfig(t *testing.T) {
// ...
}
// vet: wrong signature for TestReload
// Without the vet step this compiles and is silently never run.
func TestReload(t *testing.B) {
// ...
}go deeper
Recognise the symptom: go test prints a vet message and no PASS or FAIL lines at all. Know that vet ran first and that the test binary was never built, let alone executed.
Explain that go test runs a curated vet subset before building the tests, name a couple of the checks in it, and know that -vet=off disables the step while the full go vet suite is larger.
This is the question you get pinged about: a contributor's build 'fails with no tests run'. Read the diagnostic, decide whether it is a real defect (it usually is), and explain why the subset is deliberately smaller than the full suite.
Own the position on suppression: switching the step off buys a green build and gives up the only always-on correctness gate the toolchain provides for free. Decide instead where a full vet run belongs and who owns fixing what it turns up.
## The symptom Someone pushes a change, `go test ./...` comes back red, and the output contains no `--- FAIL`, no `PASS`, no test names — just a file, a line number and a sentence about a format verb or a function signature. It looks like the tooling is broken. It is not: the test binary was never built, because `go test` ran a vet pass first and that pass failed. ## What go test actually does Since Go 1.10, `go test` runs `go vet` on the package under test before compiling and running the tests. It does not run the whole shipped analyzer suite. It runs a curated list, chosen because those checks are cheap and have essentially no false positives — if one fires, the code is almost certainly wrong. The set includes, among others: - `printf` — format verbs that do not match their arguments, wrong argument counts, directives in `Println`. - `tests` — malformed test declarations: a `Test` function with the wrong signature, a `Benchmark` taking `*testing.T`, an `Example` whose name refers to a symbol that does not exist or whose `Output:` comment is malformed. - `atomic` — an assignment like `x = atomic.AddInt64(&x, 1)`, which is not atomic at all. - `bool` — suspicious boolean expressions such as a condition repeated on both sides of `&&`. - `errorsas` — a second argument to `errors.As` that is not a pointer to a type implementing `error`. - `ifaceassert`, `nilfunc`, `stringintconv` — impossible interface assertions, comparing a function to nil rather than calling it, and `string(intValue)` conversions that produce a rune rather than digits. A diagnostic from any of them is a build-level failure for that package. Nothing is run. ## Why the `tests` analyzer is in there This one deserves attention because of how it fails without vet. The testing framework discovers tests by name and signature: a function is a test only if it is named `TestXxx` and takes exactly `*testing.T`. Write `func TestReload(t *testing.B)` and it is a perfectly legal function that the test runner simply ignores. Before the vet step existed, the result was a test that silently never ran — green build, zero coverage of that path. The check turns a silent skip into a loud failure, which is the whole point. ## Controlling it - `go test -vet=off ./...` skips the vet step entirely. - `go test -vet=printf,tests ./...` runs exactly the named checks instead of the default list. - Leaving the flag out runs the curated default. `-vet=off` is occasionally the right call — bisecting an unrelated failure, or working inside a tree that is mid-migration — but it is a poor default. The checks in the list were picked precisely because a hit is nearly always a genuine bug, and switching them off removes the only always-on correctness gate the toolchain gives you for nothing. ## Full vet is still bigger The important asymmetry: `go vet ./...` runs the whole shipped suite, and it will report things a green `go test` never mentioned — `copylocks` on a struct with a `sync.Mutex` copied by value, `lostcancel` on a `context.CancelFunc` that is never called, `unusedresult` on a discarded `fmt.Sprintf`. So "the tests pass" does not mean "vet is clean". Reading the output of a full `go vet ./...` line by line finds a different, and often more interesting, class of defect than the test-time subset does. ## And `go build` runs none of it Worth saying explicitly, because it is a common assumption: `go build` does not run vet. A package that compiles has had none of these checks applied. Only `go vet` and the pre-test step inside `go test` run analyzers.
- Which mistakes in the test files themselves does that step catch?The `tests` analyzer covers malformed test declarations: a `Test` function whose parameter is not `*testing.T`, a `Benchmark` taking the wrong type, an `Example` whose name refers to a symbol that does not exist or whose `Output:` comment is malformed. Without it these compile fine and are simply never discovered by the runner, so you get a green build with a path that was never exercised.
- Why might go vet ./... report problems that a passing go test never mentioned?Because `go test` runs only a curated subset. Checks such as `copylocks` (a `sync.Mutex` copied by value), `lostcancel` (a `context.CancelFunc` that is never called on some path) and `unusedresult` (a discarded `fmt.Sprintf`) are in the full suite but not in the test-time list. A green test run is not evidence that vet is clean.
- When is -vet=off a reasonable thing to reach for?Rarely, and temporarily: bisecting a failure that has nothing to do with the diagnostic, or working in a tree mid-migration where you will fix the reports in a separate change. The checks were chosen for near-zero false positives, so a hit almost always means real broken code — turning the step off to get green is how a genuine bug ships.
saying these in an interview costs you the question
- Thinks a vet diagnostic means a test failed
- Says go build runs vet as well
- Believes go test runs the full vet suite
- Reaches for -vet=off as the normal fix
- Assumes only _test.go files are analyzed