skip to content

A Go tool's -tags=integration suite was green for weeks without running. How do you prove that?

level: seniorimportance: should knowfreq 34%

answer

  1. green can mean nothing ran
  2. ask which files the build used
  3. the no-test-files line is the tell
  4. TestGoFiles versus IgnoredGoFiles

basics

~20 s

Ask the go command which files it actually built: go list -f '{{.TestGoFiles}}' shows the tagged file missing, and go test prints a no-test-files line instead of running anything. The usual causes are a misspelled build tag or a pipeline that never passes -tags.

solid answer

~50 s

The tell is in the output: `go test ./...` reports a `[no test files]` line for that package rather than `ok`, because an excluded suite produces no failure and no test names — a green pipeline means nothing there. I confirm with `go list -f '{{.ImportPath}} {{.TestGoFiles}} {{.IgnoredGoFiles}}' ./...`: if the file appears under IgnoredGoFiles rather than TestGoFiles, a build constraint dropped it. Then I check three causes in order — the job never passes `-tags=integration`; the tag in the file is misspelled so nothing can satisfy it; or the `//go:build` line lost its blank line or sits below the package clause. Finally I make absence loud: `go test -tags=integration -list '.*' ./...` prints the test functions the tagged build can see, and the job asserts that list is non-empty instead of trusting the exit status.

code

text · 8 lines
text
$ go test ./migrate/
?   	example.com/tool/migrate	[no test files]

$ go list -f '{{.TestGoFiles}} {{.IgnoredGoFiles}}' ./migrate/
[] [migrate_integration_test.go]

$ go test -tags=integration ./migrate/
ok  	example.com/tool/migrate	9.204s

go deeper

for a junior

Learn to read the two go test package lines apart: an ok line means tests ran, and a no-test-files marker means the go command found none to compile.

for a middle

Be able to reach for go list, name the field that shows which files the build used, and explain why an excluded file cannot report a skip.

for a senior

Walk the diagnosis end to end — the output tell, the go list confirmation, the three likely causes — and say how you make the pipeline fail when the suite does not run.

for a principal

Own the invariant that silence is not success: decide what evidence a scheduled job must publish before its green result is allowed to count for anything.

## Why this failure is invisible by construction Build constraints exclude files while the go command is assembling the package, before compilation. An excluded test file produces no test binary entry, so it cannot fail, cannot skip, and cannot print its own name. Every symptom of "the suite never ran" is therefore an *absence*, and absence is the one thing a pass/fail pipeline is bad at noticing. `go test` exits 0 for a package with no test files, which is correct behaviour — most packages legitimately have none — and it is why weeks can pass. ## Step 1: read the package line, not the exit code `go test` prints one line per package, and two of them look similar at a glance: ``` ok example.com/tool/migrate 0.412s ? example.com/tool/migrate [no test files] ``` The second says the go command found no test files in this build configuration. If the package is supposed to have an integration suite, that line is the whole diagnosis: nothing was compiled, so nothing ran. ## Step 2: ask the go command what it built `go list` reports the resolved file lists for the current build configuration: ``` $ go list -f '{{.TestGoFiles}} {{.IgnoredGoFiles}}' ./migrate/ [] [migrate_integration_test.go] ``` An empty `TestGoFiles` with the file sitting in `IgnoredGoFiles` proves a constraint excluded it rather than, say, someone deleting the file. Re-run the same command with `-tags=integration` and the file should move into `TestGoFiles`. If it does *not* move, the problem is in the file, not in the pipeline command — a typo in the tag name, or a misplaced constraint line. ## Step 3: the three causes, in likelihood order **The command never passed the tag.** Someone edited the job, split a step, or copied a template. This is the most common cause and the easiest to confirm: run the exact command the job runs, locally. **The tag name is misspelled in the file.** `//go:build integraton` is satisfied only by `-tags=integraton`. Tag names are arbitrary identifiers, so nothing validates them — no warning, no error, exit 0. Comparing the tag spelling in every constrained file against the spelling in the pipeline command is worth doing once and then automating. **The constraint is malformed or misplaced.** A `//go:build` line below the package clause, or one without a blank line after it, silently stops being a constraint. That direction of the bug makes the tests run *always*, which is loud; the direction that hides is a stray character in the expression that no configuration satisfies. `go vet`'s build-tag check catches misplaced and malformed lines, and `go test` runs that check by default. ## Step 4: prove the suite is alive, not just that it compiles `go test -tags=integration -list '.*' ./...` compiles the tagged configuration and prints the names of the test functions it found without running them — a fast, dependency-free way to assert that the suite exists in that configuration. A pipeline step that fails when this list is empty converts the silent failure into a loud one. A cheaper cousin is `go build -tags=integration ./...` or `go vet -tags=integration ./...` on every push: it type-checks the excluded files in seconds and catches the rot that silence hides. ## Step 5: fix the class, not the instance Once the cause is found, the durable fixes are structural rather than textual: - One documented tag name per repository, and one pipeline step per tag, so the two spellings live next to each other. - The tagged job asserts that it ran a known-non-zero number of tests. - Anything behind a tag has a scheduled build whose failure has an owner, because an excluded file is not type-checked by any other job. The underlying lesson generalises past this incident: with opt-in test selection, green is evidence about what ran, and says nothing about what did not. The pipeline has to be told what it is supposed to run before its silence can mean anything.

  • What in go test's per-package output separates a passing package from one with no tests?
    A package that ran tests prints an ok line with the import path and a duration. A package the go command found no test files in prints a question-mark line with the import path and a no-test-files marker. Both leave the exit status at 0, which is exactly why the second slides past a pipeline unnoticed.
  • How would you stop this from recurring?
    Make absence loud. Have the tagged job assert that the suite it expects actually exists — compare go test -tags=integration -list '.*' ./... against a known non-zero count, or fail when a package that should have tests reports none. Opt-in selection gives you no other signal, because an excluded file cannot report anything about itself.
  • A tagged test file has a compile error. When do you find out?
    Only when something builds with the tag. Excluded files are not parsed or type-checked, so the default run stays green while the tagged suite has not compiled for weeks. Running go vet -tags=integration ./... or go build -tags=integration ./... on every push catches it in seconds without paying for the suite.
  • Does passing an unknown tag value make the command fail?
    No. Tag names are arbitrary identifiers and nothing validates them, so a misspelled -tags value satisfies no constraint, leaves the file excluded, and exits 0. That is the single most common way a tagged suite disappears, and it is why the spelling in the file and the spelling in the pipeline command belong in the same place.

saying these in an interview costs you the question

  • Treats a green go test ./... as proof the suite ran
  • Reads a no-test-files line as equivalent to ok
  • Assumes an unknown -tags value produces an error
  • Looks for a skip line that an excluded file never prints
  • Fixes the one typo without making absence detectable