Documentation
¶
Overview ¶
Package testlayout checks that test files follow the repository's test-suite layout conventions.
A test file is one of two kinds, and which one is not a matter of opinion: it is decided by the package the file declares. A white-box file declares the package under test and can reach its unexported identifiers; a black-box file declares the external <pkg>_test package and sees only what a consumer sees. Settings say which kinds a repository allows and what a file of each kind may be named, and the rules follow from that.
Rules ¶
Each diagnostic names the rule that produced it as the first word of its message, so a consumer can exclude one rule without silencing the linter:
- test-package: a test file of a kind the settings do not allow.
- test-file-name: a test file whose name no pattern for its kind allows.
- spec-less-test-file: a file named for the source it exercises carrying no specs, which means the specs for that source are somewhere else.
- specs-in-helper-file: a file named for a supporting role carrying specs, which puts specs where no reader is looking for them.
- ginkgo-adapter-file: RunSpecs called from a file not reserved for it.
- ginkgo-adapter-missing: a package registering Ginkgo specs where nothing the directory's test binary compiles calls RunSpecs, so none of those specs run.
- ginkgo-adapter-duplicate: a second call to RunSpecs in one test binary, which Ginkgo rejects at run time.
- suite-file-contents: anything a suite file declares beyond its reason for existing.
Names, and what <source> means ¶
A pattern is either a shell glob matched against the file name or a <source> pattern, never both. The token <source> stands for the base name of a source file in the same directory, and what brackets it is matched literally, which is what lets "<source>_test.go" mean "named for the source it exercises" rather than naming any file in particular. A source split under a GOOS or GOARCH file name satisfies it, so platform_test.go is named for platform_linux.go and is not read as naming nothing.
A file matching a helper pattern is required to carry no specs, rather than merely permitted to carry none. The two lists therefore divide the allowed names rather than overlapping: a name says which of the two a file is, and the file has to be it. Helper patterns are consulted first, so a helpers.go beside helpers_test.go does not turn the test file into one owing specs.
A file carries specs when it calls a Ginkgo container or leaf builder, or declares a function — not a method — named for a go test entry point: Test, Benchmark, Fuzz, or Example followed by nothing or by a rune that is not lower case, which is the rule go test itself applies. Calls are matched by name, so a dot-imported Describe and a ginkgo.Describe count alike, and no import is resolved to find out.
The Ginkgo adapter ¶
The adapter is the go test entry point that calls RunSpecs and hands the package's registered specs to the test binary. It is one per test binary, not one per package: a directory's internal and external test packages compile into the same binary, and Ginkgo fails a run where two files call RunSpecs.
A suite file — one named by adapter-patterns — is held to what it is for. It may declare imports, a go test entry point — func TestXxx(t *testing.T) or func TestMain(m *testing.M) — and the suite-level hooks registered as var _ = BeforeSuite(...): BeforeSuite and AfterSuite, their Synchronized forms, and the ReportBeforeSuite and ReportAfterSuite that bracket a run with a report. Nothing else: a helper here is out of the place a reader looks for it, and a spec here registers against the very suite the file exists to start. The rule holds whether or not the file turns out to carry the adapter, because the name is what sends a reader there.
ginkgo-adapter-missing judges the directory rather than the pass. The directory is read from disk, because a pass holds one test package and the answer can turn on a file in the other; a test file no build compiles answers for nothing, since it is not in the binary either. Only the pass holding the file a diagnostic lands on reports it, which is what keeps one problem from being reported twice. ginkgo-adapter-file judges one file on its own. Both apply only where Ginkgo is used: a package of plain go test functions has no adapter to place.
Settings ¶
Settings carries a Category for each kind, and the names reserved for the adapter. Absent keys take the defaults, which allow black-box files only:
whitebox: allowed: false blackbox: allowed: true patterns: ['<source>_test.go', suite_test.go] helper-patterns: [helpers_test.go, fakes_test.go] adapter-patterns: [suite_test.go]
An absent pattern list takes its default; an explicitly empty one allows nothing, which is how a repository says it wants no helper files at all. A configuration no file could satisfy — a malformed glob, a pattern holding a path separator or a glob around <source>, a pattern naming the source twice, an allowed category with no name available to it, an empty adapter-patterns, neither kind allowed — fails the run rather than silently matching nothing.
What this linter does not decide ¶
Which directories the conventions apply to is golangci-lint's to answer, not this linter's. A tree of integration suites that keeps its own layout, or a directory of build tooling in package main, is excluded through linters.exclusions.paths like anything else, so the settings here carry no path list of their own. Note also that run.tests, which a plugin cannot read, decides whether this linter sees any test files at all.
Entry points ¶
New is the golangci-lint plugin constructor and is what github.com/zcayou/tools/golangci registers. NewAnalyzer builds the underlying analysis.Analyzer directly and is the entry point for tests and for any other go/analysis driver. Both fail on settings no file could satisfy.
Load mode ¶
Every rule is syntactic, so the linter runs under register.LoadModeSyntax and never forces type checking on a consumer.
Suppression ¶
golangci-lint applies the nolint directive centrally, matched on the linter name, so a "nolint:testlayout" comment suppresses a finding. This linter does not interpret such directives itself. A diagnostic is positioned on the package clause, which is the line such a directive has to sit on, except for ginkgo-adapter-file, which sits on the RunSpecs call, and suite-file-contents, which sits on the declaration it is about.
The directive is spelled out rather than written here, because golangci-lint reads one out of any comment line — trimming leading slashes and spaces first — so writing it would make this paragraph suppress findings on whatever follows it.
Index ¶
Constants ¶
const Name = "testlayout"
Name is what golangci-lint knows this linter by: the key under linters.settings.custom, the entry in linters.enable, and the token a //nolint:testlayout directive must carry.
Variables ¶
This section is empty.
Functions ¶
func DefaultAdapterPatterns ¶
func DefaultAdapterPatterns() []string
DefaultAdapterPatterns are the names a file holding the Ginkgo adapter may have when settings do not name their own.
func DefaultHelperPatterns ¶
func DefaultHelperPatterns() []string
DefaultHelperPatterns are the names a test file carrying no specs may have when a category does not name its own.
func DefaultPatterns ¶
func DefaultPatterns() []string
DefaultPatterns are the names a spec-bearing test file may have when a category does not name its own: one named for a source file beside it, and the suite file, which is named for the suite rather than for any source.
func New ¶
func New(settings any) (register.LinterPlugin, error)
New builds the plugin from the raw settings golangci-lint decoded out of the configuration file. It satisfies register.NewPlugin.
Types ¶
type Category ¶
type Category struct {
// Allowed reports whether a test file of this kind may exist at all.
Allowed *bool `json:"allowed"`
// Patterns are the names a spec-bearing file of this kind may have. Absent
// means [DefaultPatterns]; an explicit empty list allows none, which leaves
// HelperPatterns as the only names this kind of file may have.
Patterns []string `json:"patterns"`
// HelperPatterns are the names a file of this kind carrying no specs may have.
// A file matching one is required to carry no specs, so a name saying
// "helpers" cannot quietly hold the specs for a source file. Absent means
// [DefaultHelperPatterns]; an explicit empty list allows no such file.
HelperPatterns []string `json:"helper-patterns"`
}
Category is one kind of test file: whether files of that kind may exist, and what they may be named.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin adapts the analyzer to golangci-lint's module plugin contract.
func (*Plugin) BuildAnalyzers ¶
BuildAnalyzers returns the single analyzer this linter runs.
func (*Plugin) GetLoadMode ¶
GetLoadMode reports that the rules are syntactic, sparing consumers the cost of type checking for this linter.
type Rule ¶
type Rule string
Rule names the convention a diagnostic came from.
golangci-lint discards analysis.Diagnostic.Category, so the rule is also the first word of every message; that is what makes it addressable by a linters.exclusions text rule.
const ( // RuleTestPackage fires on a test file of a kind the settings do not allow: // a white-box file where only black-box files may be, or the reverse. RuleTestPackage Rule = "test-package" // RuleTestFileName fires on a test file whose name no pattern for its kind // allows — most often a spec file with no source file to be named after. RuleTestFileName Rule = "test-file-name" // RuleSpecLessTestFile fires on a file named for the source it exercises // that carries no specs, so the specs for that source are somewhere else. RuleSpecLessTestFile Rule = "spec-less-test-file" // RuleSpecsInHelperFile fires on a file named for its supporting role // that carries specs anyway, which puts specs where a reader is not looking. RuleSpecsInHelperFile Rule = "specs-in-helper-file" // RuleGinkgoAdapterFile fires where RunSpecs is called from a file // the settings do not reserve for the adapter. RuleGinkgoAdapterFile Rule = "ginkgo-adapter-file" // RuleGinkgoAdapterMissing fires on a package registering Ginkgo specs where // no file the directory's test binary compiles calls RunSpecs, so none // of those specs run. RuleGinkgoAdapterMissing Rule = "ginkgo-adapter-missing" // RuleGinkgoAdapterDuplicate fires on a second call to RunSpecs in one test // binary, which Ginkgo rejects at run time. RuleGinkgoAdapterDuplicate Rule = "ginkgo-adapter-duplicate" // RuleSuiteFileContents fires on anything a suite file declares beyond the go // test entry point and the suite-level hooks. RuleSuiteFileContents Rule = "suite-file-contents" )
type Settings ¶
type Settings struct {
// Whitebox configures test files that declare the package under test and so
// can reach its unexported identifiers. Absent means not allowed.
Whitebox *Category `json:"whitebox"`
// Blackbox configures test files that declare the external <pkg>_test package
// and so see only what a consumer sees. Absent means allowed.
Blackbox *Category `json:"blackbox"`
// AdapterPatterns are the names a file holding the Ginkgo adapter — the go
// test entry point calling RunSpecs — may have. Absent means
// [DefaultAdapterPatterns].
AdapterPatterns []string `json:"adapter-patterns"`
}
Settings is the decoded linters.settings.custom.testlayout.settings block.
Decoding rejects unknown fields, so a misspelled key fails the run rather than being silently ignored. The zero value is the default configuration: black-box test files only, each named for the source it exercises.