skip to content

When is a _linux_test.go filename better than skipping the test with a runtime.GOOS check?

level: middleimportance: nice to knowfreq 27%

answer

  1. one hides the file, one reports a skip
  2. compile-time versus run-time exclusion
  3. does it even compile elsewhere?
  4. the suffix needs something before it

basics

~20 s

Use the _linux suffix when the file only compiles on Linux — the name alone keeps it out of every other build. Use a runtime.GOOS check with t.Skip when the file compiles everywhere and only the behaviour is Linux-specific.

solid answer

~40 s

A file named `migrate_linux_test.go` carries an implicit build constraint from its name, so on any other GOOS the go command leaves it out of the package before compiling. It costs nothing elsewhere and it may reference Linux-only APIs freely. A runtime check — `if runtime.GOOS != "linux" { t.Skip(...) }` — keeps the file in every build, so it must still compile on macOS and Windows, but it reports an explicit SKIP and stays type-checked everywhere. My rule is: if the test *cannot compile* elsewhere, use the filename suffix; if it merely *should not run* elsewhere, skip at run time so the omission is visible in the output. One trap worth knowing: the suffix rule needs a prefix, so `linux_test.go` on its own is not platform-constrained at all and is built everywhere.

code

go · 11 lines
go
// migrate_linux_test.go: excluded from non-Linux builds by its name alone.
func TestMigrationLockFilePermissions(t *testing.T) {
	// free to use Linux-only APIs
}

// migrate_test.go: compiled everywhere, so it must build on Windows too.
func TestMigrationPathLayout(t *testing.T) {
	if runtime.GOOS != "linux" {
		t.Skip("path layout check is Linux-only")
	}
}

go deeper

for a junior

Recall that a _linux or _windows piece in a file name keeps that file out of other platforms' builds, and that t.Skip with a runtime.GOOS check is the alternative.

for a middle

Explain the compile-time versus run-time difference: what still has to compile in each case, and how the two appear differently in go test output.

for a senior

Judge which one a real suite needs — the visibility of a skip against the freedom to call platform-only APIs — and verify on each target platform which files actually made it into the build.

for a principal

Decide how many platforms the team commits to keeping green, and what it costs to own code paths that only one machine in the fleet ever compiles.

## Two ways to keep a test off a platform Suppose a schema-migration tool has a check that only means anything on Linux — say it verifies the permissions of a lock file under a path that only exists there. There are two mechanisms, and they differ in *when* the exclusion happens. **Compile-time, by filename.** If the file is called `migrate_linux_test.go`, the go command derives a build constraint from the name itself and drops the file from the package on every other GOOS. It is never parsed or type-checked there. The file is therefore free to import platform-specific packages and call platform-specific APIs. **Run time, by skipping.** If the file is called `migrate_test.go` and the test opens with `if runtime.GOOS != "linux" { t.Skip("lock-file layout is Linux-only") }`, the file is compiled on every platform and the test simply declines to run. Everything it references must exist on every platform you build for. ## How the filename rule reads The go command looks at the file name with the extension removed and, if present, a trailing `_test` stripped first. What is left is checked for a trailing `_GOOS`, `_GOARCH`, or `_GOOS_GOARCH` component. So: - `migrate_linux_test.go` — a test file, built only on Linux. The `_test` and `_linux` rules compose; neither cancels the other. - `migrate_linux_amd64_test.go` — a test file built only for linux/amd64. - `migrate_windows.go` — non-test source, built only on Windows. - `linux_test.go` — **not** constrained. After `_test.go` is stripped the remaining name has no underscore at all, so nothing is read as a GOOS suffix and the file is built everywhere. This one surprises people, and it is exactly the sort of thing that makes a "Linux-only" test fail on a Mac laptop. An explicit `//go:build linux` line does the same job as the suffix and is the option to reach for when the file name is already meaningful or when the condition is more than one platform. ## The visibility difference This is the real decision criterion, more than syntax. A file excluded by its name produces *nothing*: no test name, no skip line, and possibly a `[no test files]` line for the whole package. Someone reading a Windows CI log has no way to tell that a Linux-only suite exists. A run-time skip produces `--- SKIP: TestMigrationLockFilePermissions` with your message under `-v` — a reader can see the test exists, see why it did not run, and notice if the reason has gone stale. So the ordering is: prefer the run-time skip for its legibility, and fall back to the filename suffix when compilation forces your hand. ## When compilation forces your hand A compiled-everywhere file is constrained in what it can mention. Platform-specific constants in `syscall`, packages that only build on Unix, or a helper you wrote in a `_linux.go` file are all unavailable to a file that Windows must compile. Once the test needs any of those, `runtime.GOOS` plus `t.Skip` stops being an option: the build breaks on Windows before the skip ever executes. That is the signal to rename the file or add an explicit constraint. A middle path is common in real repositories: keep the *test* compiled everywhere and push only the platform-specific *helper* behind a suffix, with a portable stub in a `_windows.go` file. Then the test body is portable, the skip is visible, and only a small amount of code is platform-gated. ## Cross-checking what got built Whatever you choose, `go list -f '{{.TestGoFiles}} {{.IgnoredGoFiles}}' ./...` on the target platform answers the only question that matters: is the file in the build here or not? Running it on each GOOS you support turns a guess about constraints into a fact, and it costs a second.

  • Does the _test suffix cancel the _linux suffix in migrate_linux_test.go?
    No, they compose. The go command strips the _test part before looking for a GOOS or GOARCH suffix, so the file is both a test file and Linux-only. The same composition gives you migrate_linux_amd64_test.go, a test file built only for that GOOS and GOARCH pair.
  • What does the test output look like on Windows for each approach?
    With the filename suffix, nothing at all — the file is not in the package, no test name is printed, and the package may report that it has no test files. With the runtime.GOOS skip you get a SKIP line and your message under -v, so a reader can see the test exists and was deliberately passed over.
  • Why does the runtime.GOOS approach constrain what the file may import?
    Because it is compiled on every platform, so everything it references must exist on every platform. A Unix-only package or a constant that only exists on Linux breaks the Windows build even though the test would have skipped there. At that point you need the filename suffix or an explicit build constraint.

saying these in an interview costs you the question

  • Thinks a runtime.GOOS check stops the file from compiling
  • Believes linux_test.go alone is platform-constrained
  • Assumes the _test suffix cancels the _linux suffix
  • Expects a suffix-excluded test to show up as skipped
  • Puts Linux-only imports in a file every platform compiles