analysistest

package
v0.18.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 12, 2024 License: BSD-3-Clause Imports: 21 Imported by: 4

Documentation

Overview

Package analysistest provides utilities for testing analyzers.

Index

Constants

This section is empty.

Variables

View Source
var TestData = func() string {
	testdata, err := filepath.Abs("testdata")
	if err != nil {
		log.Fatal(err)
	}
	return testdata
}

TestData returns the effective filename of the program's "testdata" directory. This function may be overridden by projects using an alternative build system (such as Blaze) that does not run a test in its package directory.

Functions

func WriteFiles

func WriteFiles(filemap map[string]string) (dir string, cleanup func(), err error)

WriteFiles is a helper function that creates a temporary directory and populates it with a GOPATH-style project using filemap (which maps file names to contents). On success it returns the name of the directory and a cleanup function to delete it.

Types

type Result

type Result = checker.TestAnalyzerResult

A Result holds the result of applying an analyzer to a package.

func Run

func Run(t Testing, dir string, a *analysis.Analyzer, patterns ...string) []*Result

Run applies an analysis to the packages denoted by the "go list" patterns.

It loads the packages from the specified directory using golang.org/x/tools/go/packages, runs the analysis on them, and checks that each analysis emits the expected diagnostics and facts specified by the contents of '// want ...' comments in the package's source files. It treats a comment of the form "//...// want..." or "/*...// want... */" as if it starts at 'want'.

If the directory contains a go.mod file, Run treats it as the root of the Go module in which to work. Otherwise, Run treats it as the root of a GOPATH-style tree, with package contained in the src subdirectory.

An expectation of a Diagnostic is specified by a string literal containing a regular expression that must match the diagnostic message. For example:

fmt.Printf("%s", 1) // want `cannot provide int 1 to %s`

An expectation of a Fact associated with an object is specified by 'name:"pattern"', where name is the name of the object, which must be declared on the same line as the comment, and pattern is a regular expression that must match the string representation of the fact, fmt.Sprint(fact). For example:

func panicf(format string, args interface{}) { // want panicf:"printfWrapper"

Package facts are specified by the name "package" and appear on line 1 of the first source file of the package.

A single 'want' comment may contain a mixture of diagnostic and fact expectations, including multiple facts about the same object:

// want "diag" "diag2" x:"fact1" x:"fact2" y:"fact3"

Unexpected diagnostics and facts, and unmatched expectations, are reported as errors to the Testing.

Run reports an error to the Testing if loading or analysis failed. Run also returns a Result for each package for which analysis was attempted, even if unsuccessful. It is safe for a test to ignore all the results, but a test may use it to perform additional checks.

func RunWithSuggestedFixes

func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns ...string) []*Result

RunWithSuggestedFixes behaves like Run, but additionally verifies suggested fixes. It uses golden files placed alongside the source code under analysis: suggested fixes for code in example.go will be compared against example.go.golden.

Golden files can be formatted in one of two ways: as plain Go source code, or as txtar archives. In the first case, all suggested fixes will be applied to the original source, which will then be compared against the golden file. In the second case, suggested fixes will be grouped by their messages, and each set of fixes will be applied and tested separately. Each section in the archive corresponds to a single message.

A golden file using txtar may look like this:

-- turn into single negation --
package pkg

func fn(b1, b2 bool) {
	if !b1 { // want `negating a boolean twice`
		println()
	}
}

-- remove double negation --
package pkg

func fn(b1, b2 bool) {
	if b1 { // want `negating a boolean twice`
		println()
	}
}

Conflicts

A single analysis pass may offer two or more suggested fixes that (1) conflict but are nonetheless logically composable, (e.g. because both update the import declaration), or (2) are fundamentally incompatible (e.g. alternative fixes to the same statement).

It is up to the driver to decide how to apply such fixes. A sophisticated driver could attempt to resolve conflicts of the first kind, but this test driver simply reports the fact of the conflict with the expectation that the user will split their tests into nonconflicting parts.

Conflicts of the second kind can be avoided by giving the alternative fixes different names (SuggestedFix.Message) and using a multi-section .txtar file with a named section for each alternative fix.

Analyzers that compute fixes from a textual diff of the before/after file contents (instead of directly from syntax tree positions) may produce fixes that, although logically non-conflicting, nonetheless conflict due to the particulars of the diff algorithm. In such cases it may suffice to introduce sufficient separation of the statements in the test input so that the computed diffs do not overlap. If that fails, break the test into smaller parts.

type Testing

type Testing interface {
	Errorf(format string, args ...interface{})
}

Testing is an abstraction of a *testing.T.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL