internal/lsp: switch golden files to use txtar

I was about to add some more tests and it caused a huge number of golden files,
which was hard to deal with. Now all the golden files are packed into a single
.golden archive in the txtar format.
I also changed the tagging key for hover results to use the marker name rather
than the line and column, as it makes it more stable against test data changes.

Change-Id: Iaa1f54ab55a41d380db67b9f6f928fa7a52d9a5e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/174877
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Ian Cottrell 2019-05-01 19:16:03 -04:00
parent 5658f888b3
commit 490d13020f
35 changed files with 180 additions and 84 deletions

View File

@ -5,9 +5,7 @@
package cmd_test
import (
"bytes"
"context"
"io/ioutil"
"os/exec"
"regexp"
"strings"
@ -33,13 +31,11 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
t.Fatal(err)
}
args := append(mode, filename)
expect := string(r.data.Golden(tag, filename, func(golden string) error {
expect := string(r.data.Golden(tag, filename, func() ([]byte, error) {
cmd := exec.Command("gofmt", args...)
buf := &bytes.Buffer{}
cmd.Stdout = buf
cmd.Run() // ignore error, sometimes we have intentionally ungofmt-able files
contents := r.normalizePaths(fixFileHeader(buf.String()))
return ioutil.WriteFile(golden, []byte(contents), 0666)
contents, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files
contents = []byte(r.normalizePaths(fixFileHeader(string(contents))))
return contents, nil
}))
if expect == "" {
//TODO: our error handling differs, for now just skip unformattable files

View File

@ -9,8 +9,6 @@ import (
"context"
"fmt"
"go/token"
"io/ioutil"
"os"
"os/exec"
"sort"
"strings"
@ -290,16 +288,10 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
if err != nil {
t.Fatal(err)
}
gofmted := string(r.data.Golden("gofmt", filename, func(golden string) error {
gofmted := string(r.data.Golden("gofmt", filename, func() ([]byte, error) {
cmd := exec.Command("gofmt", 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
out, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files
return out, nil
}))
edits, err := r.server.Formatting(context.Background(), &protocol.DocumentFormattingParams{
@ -365,13 +357,13 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
t.Errorf("for %v got %v want %v", d.Src, def, d.Def)
}
if hover != nil {
tag := fmt.Sprintf("hover-%d-%d", d.Def.Start().Line(), d.Def.Start().Column())
filename, err := d.Def.URI().Filename()
tag := fmt.Sprintf("%s-hover", d.Name)
filename, err := d.Src.URI().Filename()
if err != nil {
t.Fatalf("failed for %v: %v", d.Def, err)
}
expectHover := string(r.data.Golden(tag, filename, func(golden string) error {
return ioutil.WriteFile(golden, []byte(hover.Contents.Value), 0666)
expectHover := string(r.data.Golden(tag, filename, func() ([]byte, error) {
return []byte(hover.Contents.Value), nil
}))
if hover.Contents.Value != expectHover {
t.Errorf("for %v got %q want %q", d.Src, hover.Contents.Value, expectHover)

4
internal/lsp/reset_golden.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
find ./internal/lsp/ -name *.golden -delete
go test ./internal/lsp/ ./internal/lsp/cmd -golden

View File

@ -1,3 +1,25 @@
-- gofmt --
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("")
}
-- gofmt-d --
--- format/bad_format.go.orig
+++ format/bad_format.go
@@ -1,16 +1,13 @@
@ -18,3 +40,4 @@
var x int //@diag("x", "LSP", "x declared but not used")
}

View File

@ -1,19 +0,0 @@
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("")
}

View File

@ -1,3 +1,4 @@
-- gofmt --
package format //@format("package")
import (
@ -7,3 +8,6 @@ import (
func goodbye() {
log.Printf("byeeeee")
}
-- gofmt-d --

View File

@ -1,3 +1,8 @@
-- gofmt --
package format //@format("package")
func _() {}
-- gofmt-d --
--- format/newline_format.go.orig
+++ format/newline_format.go
@@ -1,2 +1,2 @@
@ -5,3 +10,4 @@
-func _() {}
\ No newline at end of file
+func _() {}

View File

@ -1,2 +0,0 @@
package format //@format("package")
func _() {}

View File

@ -1,6 +1,11 @@
-- gofmt --
package format //@format("package")
-- gofmt-d --
--- format/one_line.go.orig
+++ format/one_line.go
@@ -1 +1 @@
-package format //@format("package")
\ No newline at end of file
+package format //@format("package")

View File

@ -1 +0,0 @@
package format //@format("package")

View File

@ -0,0 +1,6 @@
-- Random-hover --
func Random() int
-- Random2-hover --
func Random2(y int) int
-- err-hover --
var err error

View File

@ -1 +0,0 @@
var err error

View File

@ -1 +0,0 @@
type a.A string

View File

@ -1 +0,0 @@
func a.Stuff()

View File

@ -0,0 +1,6 @@
-- PosSum-hover --
func (*Pos).Sum() int
-- PosX-hover --
field x int
-- RandomParamY-hover --
var y int

View File

@ -1 +0,0 @@
field x int

View File

@ -1 +0,0 @@
func (*Pos).Sum() int

View File

@ -1 +0,0 @@
func Random() int

View File

@ -1 +0,0 @@
var y int

View File

@ -1 +0,0 @@
func Random2(y int) int

View File

@ -0,0 +1,16 @@
-- A-hover --
type a.A string
-- S1-hover --
type S1 struct{F1 int; S2; a.A}
-- S1F1-hover --
field F1 int
-- S1S2-hover --
field S2 S2
-- S2-hover --
type S2 struct{F1 string; F2 int; *a.A}
-- S2F1-hover --
field F1 string
-- S2F2-hover --
field F2 int
-- Stuff-hover --
func a.Stuff()

View File

@ -1 +0,0 @@
type S2 struct{F1 string; F2 int; *a.A}

View File

@ -1 +0,0 @@
field F1 string

View File

@ -1 +0,0 @@
field F2 int

View File

@ -1 +0,0 @@
type S1 struct{F1 int; S2; a.A}

View File

@ -1 +0,0 @@
field F1 int

View File

@ -1 +0,0 @@
field S2 S2

View File

@ -0,0 +1,4 @@
-- S1-hover --
type S1 struct{F1 int; S2; a.A}
-- S1F1-hover --
field F1 int

View File

@ -0,0 +1,2 @@
-- myUnclosedIf-hover --
var myUnclosedIf string

View File

@ -1 +0,0 @@
var myUnclosedIf string

View File

@ -0,0 +1,4 @@
-- gofmt --
-- gofmt-d --

View File

@ -12,9 +12,9 @@ import (
"go/token"
"io/ioutil"
"os/exec"
"path"
"path/filepath"
"runtime"
"sort"
"strings"
"testing"
@ -22,6 +22,7 @@ import (
"golang.org/x/tools/go/packages/packagestest"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/txtar"
)
// We hardcode the expected number of test cases to ensure that all tests
@ -40,9 +41,9 @@ const (
)
const (
overlayFile = ".overlay"
goldenFile = ".golden"
inFile = ".in"
overlayFileSuffix = ".overlay"
goldenFileSuffix = ".golden"
inFileSuffix = ".in"
testModule = "golang.org/x/tools/internal/lsp"
)
@ -78,6 +79,7 @@ type Data struct {
t testing.TB
fragments map[string]string
dir string
golden map[string]*Golden
}
type Tests interface {
@ -92,6 +94,7 @@ type Tests interface {
}
type Definition struct {
Name string
Src span.Span
IsType bool
Flags string
@ -110,6 +113,12 @@ type Link struct {
Target string
}
type Golden struct {
Filename string
Archive *txtar.Archive
Modified bool
}
func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
t.Helper()
@ -128,19 +137,29 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
t: t,
dir: dir,
fragments: map[string]string{},
golden: map[string]*Golden{},
}
files := packagestest.MustCopyFileTree(dir)
overlays := map[string][]byte{}
for fragment, operation := range files {
if strings.Contains(fragment, goldenFile) {
if trimmed := strings.TrimSuffix(fragment, goldenFileSuffix); trimmed != fragment {
delete(files, fragment)
} else if trimmed := strings.TrimSuffix(fragment, inFile); trimmed != fragment {
goldFile := filepath.Join(dir, fragment)
archive, err := txtar.ParseFile(goldFile)
if err != nil {
t.Fatalf("could not read golden file %v: %v", fragment, err)
}
data.golden[trimmed] = &Golden{
Filename: goldFile,
Archive: archive,
}
} else if trimmed := strings.TrimSuffix(fragment, inFileSuffix); trimmed != fragment {
delete(files, fragment)
files[trimmed] = operation
} else if index := strings.Index(fragment, overlayFile); index >= 0 {
} else if index := strings.Index(fragment, overlayFileSuffix); index >= 0 {
delete(files, fragment)
partial := fragment[:index] + fragment[index+len(overlayFile):]
partial := fragment[:index] + fragment[index+len(overlayFileSuffix):]
contents, err := ioutil.ReadFile(filepath.Join(dir, fragment))
if err != nil {
t.Fatal(err)
@ -200,6 +219,12 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
symbols[i].Children = children
}
}
// run a second pass to collect names for some entries.
if err := data.Exported.Expect(map[string]interface{}{
"godef": data.collectDefinitionNames,
}); err != nil {
t.Fatal(err)
}
return data
}
@ -287,9 +312,23 @@ func Run(t *testing.T, tests Tests, data *Data) {
}
tests.Link(t, data.Links)
})
if *updateGolden {
for _, golden := range data.golden {
if !golden.Modified {
continue
}
sort.Slice(golden.Archive.Files, func(i, j int) bool {
return golden.Archive.Files[i].Name < golden.Archive.Files[j].Name
})
if err := ioutil.WriteFile(golden.Filename, txtar.Format(golden.Archive), 0666); err != nil {
t.Fatal(err)
}
}
}
}
func (data *Data) Golden(tag string, target string, update func(golden string) error) []byte {
func (data *Data) Golden(tag string, target string, update func() ([]byte, error)) []byte {
data.t.Helper()
fragment, found := data.fragments[target]
if !found {
@ -298,24 +337,44 @@ func (data *Data) Golden(tag string, target string, update func(golden string) e
}
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 := data.golden[fragment]
if golden == nil {
if !*updateGolden {
data.t.Fatalf("could not find golden file %v: %v", fragment, tag)
}
golden = &Golden{
Filename: filepath.Join(data.dir, fragment+goldenFileSuffix),
Archive: &txtar.Archive{},
Modified: true,
}
data.golden[fragment] = golden
}
var file *txtar.File
for i := range golden.Archive.Files {
f := &golden.Archive.Files[i]
if f.Name == tag {
file = f
break
}
}
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)
if file == nil {
golden.Archive.Files = append(golden.Archive.Files, txtar.File{
Name: tag,
})
file = &golden.Archive.Files[len(golden.Archive.Files)-1]
}
}
contents, err := ioutil.ReadFile(golden)
contents, err := update()
if err != nil {
data.t.Fatalf("could not read golden file %v: %v", golden, err)
data.t.Fatalf("could not update golden file %v: %v", fragment, err)
}
return contents
file.Data = append(contents, '\n') // add trailing \n for txtar
golden.Modified = true
}
if file == nil {
data.t.Fatalf("could not find golden contents %v: %v", fragment, tag)
}
return file.Data[:len(file.Data)-1] // drop the trailing \n
}
func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg string) {
@ -371,6 +430,12 @@ func (data *Data) collectTypeDefinitions(src, target span.Span) {
}
}
func (data *Data) collectDefinitionNames(src span.Span, name string) {
d := data.Definitions[src]
d.Name = name
data.Definitions[src] = d
}
func (data *Data) collectHighlights(name string, rng span.Span) {
data.Highlights[name] = append(data.Highlights[name], rng)
}