internal/lsp: adding golden file support to the test harness
Also convert the format tests to use it. This means that the build bots no
longer need to run gofmt.
Change-Id: I5cb9d239183b17d81fdb00b38e9099d224c07e6a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/172973
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/testdata/format/bad_format.gofmt.golden.go b/internal/lsp/testdata/format/bad_format.gofmt.golden.go
new file mode 100644
index 0000000..919b2d2
--- /dev/null
+++ b/internal/lsp/testdata/format/bad_format.gofmt.golden.go
@@ -0,0 +1,19 @@
+package format //@format("package")
+
+import (
+ "fmt"
+ "log"
+ "runtime"
+)
+
+func hello() {
+
+ var x int //@diag("x", "LSP", "x declared but not used")
+}
+
+func hi() {
+ runtime.GOROOT()
+ fmt.Printf("")
+
+ log.Printf("")
+}
diff --git a/internal/lsp/testdata/format/good_format.gofmt.golden.go b/internal/lsp/testdata/format/good_format.gofmt.golden.go
new file mode 100644
index 0000000..01cb161
--- /dev/null
+++ b/internal/lsp/testdata/format/good_format.gofmt.golden.go
@@ -0,0 +1,9 @@
+package format //@format("package")
+
+import (
+ "log"
+)
+
+func goodbye() {
+ log.Printf("byeeeee")
+}
diff --git a/internal/lsp/testdata/format/newline_format.gofmt.golden.go b/internal/lsp/testdata/format/newline_format.gofmt.golden.go
new file mode 100644
index 0000000..29459ac
--- /dev/null
+++ b/internal/lsp/testdata/format/newline_format.gofmt.golden.go
@@ -0,0 +1,2 @@
+package format //@format("package")
+func _() {}
diff --git a/internal/lsp/testdata/noparse_format/noparse_format.gofmt.golden.go b/internal/lsp/testdata/noparse_format/noparse_format.gofmt.golden.go
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/internal/lsp/testdata/noparse_format/noparse_format.gofmt.golden.go
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index 10012c8..d4a8960 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -5,13 +5,15 @@
package tests
import (
- "bytes"
"context"
+ "flag"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
+ "os"
"os/exec"
+ "path"
"path/filepath"
"runtime"
"strings"
@@ -36,6 +38,15 @@
ExpectedSignaturesCount = 19
)
+const (
+ overlayFile = ".overlay"
+ goldenFile = ".golden"
+ inFile = ".in"
+ testModule = "golang.org/x/tools/internal/lsp"
+)
+
+var updateGolden = flag.Bool("golden", false, "Update golden files")
+
type Diagnostics map[span.URI][]source.Diagnostic
type CompletionItems map[token.Pos]*source.CompletionItem
type Completions map[span.Span][]token.Pos
@@ -58,6 +69,10 @@
Symbols Symbols
symbolsChildren SymbolsChildren
Signatures Signatures
+
+ t testing.TB
+ fragments map[string]string
+ dir string
}
type Tests interface {
@@ -91,19 +106,23 @@
Symbols: make(Symbols),
symbolsChildren: make(SymbolsChildren),
Signatures: make(Signatures),
+
+ t: t,
+ dir: dir,
+ fragments: map[string]string{},
}
files := packagestest.MustCopyFileTree(dir)
overlays := map[string][]byte{}
for fragment, operation := range files {
- if trimmed := strings.TrimSuffix(fragment, ".in"); trimmed != fragment {
+ if strings.Contains(fragment, goldenFile) {
+ delete(files, fragment)
+ } else if trimmed := strings.TrimSuffix(fragment, inFile); trimmed != fragment {
delete(files, fragment)
files[trimmed] = operation
- }
- const overlay = ".overlay"
- if index := strings.Index(fragment, overlay); index >= 0 {
+ } else if index := strings.Index(fragment, overlayFile); index >= 0 {
delete(files, fragment)
- partial := fragment[:index] + fragment[index+len(overlay):]
+ partial := fragment[:index] + fragment[index+len(overlayFile):]
contents, err := ioutil.ReadFile(filepath.Join(dir, fragment))
if err != nil {
t.Fatal(err)
@@ -113,12 +132,16 @@
}
modules := []packagestest.Module{
{
- Name: "golang.org/x/tools/internal/lsp",
+ Name: testModule,
Files: files,
Overlay: overlays,
},
}
data.Exported = packagestest.Export(t, exporter, modules)
+ for fragment, _ := range files {
+ filename := data.Exported.File(testModule, fragment)
+ data.fragments[filename] = fragment
+ }
// Merge the exported.Config with the view.Config.
data.Config = *data.Exported.Config
@@ -231,6 +254,35 @@
})
}
+func (data *Data) Golden(tag string, target string, update func(golden string) error) []byte {
+ data.t.Helper()
+ fragment, found := data.fragments[target]
+ if !found {
+ if filepath.IsAbs(target) {
+ data.t.Fatalf("invalid golden file fragment %v", target)
+ }
+ fragment = target
+ }
+ dir, file := path.Split(fragment)
+ prefix, suffix := file, ""
+ // we deliberately use the first . not the last
+ if dot := strings.IndexRune(file, '.'); dot >= 0 {
+ prefix = file[:dot]
+ suffix = file[dot:]
+ }
+ golden := path.Join(data.dir, dir, prefix) + "." + tag + goldenFile + suffix
+ if *updateGolden {
+ if err := update(golden); err != nil {
+ data.t.Fatalf("could not update golden file %v: %v", golden, err)
+ }
+ }
+ contents, err := ioutil.ReadFile(golden)
+ if err != nil {
+ data.t.Fatalf("could not read golden file %v: %v", golden, err)
+ }
+ return contents
+}
+
func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg string) {
if _, ok := data.Diagnostics[spn.URI()]; !ok {
data.Diagnostics[spn.URI()] = []source.Diagnostic{}
@@ -266,11 +318,17 @@
}
func (data *Data) collectFormats(pos token.Position) {
- cmd := exec.Command("gofmt", pos.Filename)
- stdout := bytes.NewBuffer(nil)
- cmd.Stdout = stdout
- cmd.Run() // ignore error, sometimes we have intentionally ungofmt-able files
- data.Formats[pos.Filename] = stdout.String()
+ data.Formats[pos.Filename] = string(data.Golden("gofmt", pos.Filename, func(golden string) error {
+ cmd := exec.Command("gofmt", pos.Filename)
+ stdout, err := os.Create(golden)
+ if err != nil {
+ return err
+ }
+ defer stdout.Close()
+ cmd.Stdout = stdout
+ cmd.Run() // ignore error, sometimes we have intentionally ungofmt-able files
+ return nil
+ }))
}
func (data *Data) collectDefinitions(src, target span.Span) {