mirror of
https://github.com/golang/go
synced 2024-11-18 17:54:57 -07:00
go/analysis/analysistest: support testing mutually exclusive suggested fixes
Fixes golang/go#38431. Change-Id: Ib2e389a7a657b8ff80995972cd3bcbcb937aeabb Reviewed-on: https://go-review.googlesource.com/c/tools/+/229257 Run-TryBot: Dominik Honnef <dominik@honnef.co> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
parent
655248709e
commit
4697a2867c
@ -2,6 +2,7 @@
|
||||
package analysistest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"go/token"
|
||||
@ -23,6 +24,7 @@ import (
|
||||
"golang.org/x/tools/internal/lsp/diff/myers"
|
||||
"golang.org/x/tools/internal/span"
|
||||
"golang.org/x/tools/internal/testenv"
|
||||
"golang.org/x/tools/txtar"
|
||||
)
|
||||
|
||||
// WriteFiles is a helper function that creates a temporary directory
|
||||
@ -65,10 +67,38 @@ type Testing interface {
|
||||
Errorf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// 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()
|
||||
// }
|
||||
// }
|
||||
func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns ...string) []*Result {
|
||||
r := Run(t, dir, a, patterns...)
|
||||
|
||||
fileEdits := make(map[*token.File][]diff.TextEdit)
|
||||
// file -> message -> edits
|
||||
fileEdits := make(map[*token.File]map[string][]diff.TextEdit)
|
||||
fileContents := make(map[*token.File][]byte)
|
||||
|
||||
// Validate edits, prepare the fileEdits map and read the file contents.
|
||||
@ -101,7 +131,11 @@ func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns
|
||||
if err != nil {
|
||||
t.Errorf("error converting edit to span %s: %v", file.Name(), err)
|
||||
}
|
||||
fileEdits[file] = append(fileEdits[file], diff.TextEdit{
|
||||
|
||||
if _, ok := fileEdits[file]; !ok {
|
||||
fileEdits[file] = make(map[string][]diff.TextEdit)
|
||||
}
|
||||
fileEdits[file][sf.Message] = append(fileEdits[file][sf.Message], diff.TextEdit{
|
||||
Span: spn,
|
||||
NewText: string(edit.NewText),
|
||||
})
|
||||
@ -110,27 +144,77 @@ func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns
|
||||
}
|
||||
}
|
||||
|
||||
for file, edits := range fileEdits {
|
||||
for file, fixes := range fileEdits {
|
||||
// Get the original file contents.
|
||||
orig, ok := fileContents[file]
|
||||
if !ok {
|
||||
t.Errorf("could not find file contents for %s", file.Name())
|
||||
continue
|
||||
}
|
||||
out := diff.ApplyEdits(string(orig), edits)
|
||||
|
||||
// Get the golden file and read the contents.
|
||||
want, err := ioutil.ReadFile(file.Name() + ".golden")
|
||||
ar, err := txtar.ParseFile(file.Name() + ".golden")
|
||||
if err != nil {
|
||||
t.Errorf("error reading %s.golden: %v", file.Name(), err)
|
||||
continue
|
||||
}
|
||||
formatted, err := format.Source([]byte(out))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if string(want) != string(formatted) {
|
||||
d := myers.ComputeEdits("", string(want), string(formatted))
|
||||
t.Errorf("suggested fixes failed for %s:\n%s", file.Name(), diff.ToUnified(file.Name()+".golden", "actual", string(want), d))
|
||||
|
||||
if len(ar.Files) > 0 {
|
||||
// one virtual file per kind of suggested fix
|
||||
|
||||
if len(ar.Comment) != 0 {
|
||||
// we allow either just the comment, or just virtual
|
||||
// files, not both. it is not clear how "both" should
|
||||
// behave.
|
||||
t.Errorf("%s.golden has leading comment; we don't know what to do with it", file.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
for sf, edits := range fixes {
|
||||
found := false
|
||||
for _, vf := range ar.Files {
|
||||
if vf.Name == sf {
|
||||
found = true
|
||||
out := diff.ApplyEdits(string(orig), edits)
|
||||
// the file may contain multiple trailing
|
||||
// newlines if the user places empty lines
|
||||
// between files in the archive. normalize
|
||||
// this to a single newline.
|
||||
want := string(bytes.TrimRight(vf.Data, "\n")) + "\n"
|
||||
formatted, err := format.Source([]byte(out))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if want != string(formatted) {
|
||||
d := myers.ComputeEdits("", want, string(formatted))
|
||||
t.Errorf("suggested fixes failed for %s:\n%s", file.Name(), diff.ToUnified(fmt.Sprintf("%s.golden [%s]", file.Name(), sf), "actual", want, d))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("no section for suggested fix %q in %s.golden", sf, file.Name())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// all suggested fixes are represented by a single file
|
||||
|
||||
var catchallEdits []diff.TextEdit
|
||||
for _, edits := range fixes {
|
||||
catchallEdits = append(catchallEdits, edits...)
|
||||
}
|
||||
|
||||
out := diff.ApplyEdits(string(orig), catchallEdits)
|
||||
want := string(ar.Comment)
|
||||
|
||||
formatted, err := format.Source([]byte(out))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if want != string(formatted) {
|
||||
d := myers.ComputeEdits("", want, string(formatted))
|
||||
t.Errorf("suggested fixes failed for %s:\n%s", file.Name(), diff.ToUnified(file.Name()+".golden", "actual", want, d))
|
||||
}
|
||||
}
|
||||
}
|
||||
return r
|
||||
|
Loading…
Reference in New Issue
Block a user