// 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. func WriteFiles(filemap map[string]string) (dir string, cleanup func(), err error) { … } var TestData … type Testing … // 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 // defining the .golden file as a multi-section txtar file with a // named section for each alternative fix, as shown above. // // 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. // // TODO(adonovan): the behavior of RunWithSuggestedFixes as documented // above is impractical for tests that report multiple diagnostics and // offer multiple alternative fixes for the same diagnostic, and it is // inconsistent with the interpretation of multiple diagnostics // described at Diagnostic.SuggestedFixes. // We need to rethink the analyzer testing API to better support such // cases. In the meantime, users of RunWithSuggestedFixes testing // analyzers that offer alternative fixes are advised to put each fix // in a separate .go file in the testdata. func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns ...string) []*Result { … } // applyDiffsAndCompare applies edits to src and compares the results against // golden after formatting both. fileName is use solely for error reporting. func applyDiffsAndCompare(src, golden []byte, edits []diff.Edit, fileName string) error { … } // 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 Run(t Testing, dir string, a *analysis.Analyzer, patterns ...string) []*Result { … } type Result … // loadPackages uses go/packages to load a specified packages (from source, with // dependencies) from dir, which is the root of a GOPATH-style project tree. // loadPackages returns an error if any package had an error, or the pattern // matched no packages. func loadPackages(a *analysis.Analyzer, dir string, patterns ...string) ([]*packages.Package, error) { … } // check inspects an analysis pass on which the analysis has already // been run, and verifies that all reported diagnostics and facts match // specified by the contents of "// want ..." comments in the package's // source files, which must have been parsed with comments enabled. func check(t Testing, gopath string, pass *analysis.Pass, diagnostics []analysis.Diagnostic, facts map[types.Object][]analysis.Fact) { … } type expectation … func (ex expectation) String() string { … } // parseExpectations parses the content of a "// want ..." comment // and returns the expectations, a mixture of diagnostics ("rx") and // facts (name:"rx"). func parseExpectations(text string) (lineDelta int, expects []expectation, err error) { … } // sanitize removes the GOPATH portion of the filename, // typically a gnarly /tmp directory, and returns the rest. func sanitize(gopath, filename string) string { … }