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:
parent
5658f888b3
commit
490d13020f
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
find ./internal/lsp/ -name *.golden -delete
|
||||
go test ./internal/lsp/ ./internal/lsp/cmd -golden
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
@ -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("")
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
-- gofmt --
|
||||
package format //@format("package")
|
||||
|
||||
import (
|
||||
|
@ -7,3 +8,6 @@ import (
|
|||
func goodbye() {
|
||||
log.Printf("byeeeee")
|
||||
}
|
||||
|
||||
-- gofmt-d --
|
||||
|
|
@ -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 _() {}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
package format //@format("package")
|
||||
func _() {}
|
|
@ -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")
|
||||
|
|
@ -1 +0,0 @@
|
|||
package format //@format("package")
|
|
@ -0,0 +1,6 @@
|
|||
-- Random-hover --
|
||||
func Random() int
|
||||
-- Random2-hover --
|
||||
func Random2(y int) int
|
||||
-- err-hover --
|
||||
var err error
|
|
@ -1 +0,0 @@
|
|||
var err error
|
|
@ -1 +0,0 @@
|
|||
type a.A string
|
|
@ -1 +0,0 @@
|
|||
func a.Stuff()
|
|
@ -0,0 +1,6 @@
|
|||
-- PosSum-hover --
|
||||
func (*Pos).Sum() int
|
||||
-- PosX-hover --
|
||||
field x int
|
||||
-- RandomParamY-hover --
|
||||
var y int
|
|
@ -1 +0,0 @@
|
|||
field x int
|
|
@ -1 +0,0 @@
|
|||
func (*Pos).Sum() int
|
|
@ -1 +0,0 @@
|
|||
func Random() int
|
|
@ -1 +0,0 @@
|
|||
var y int
|
|
@ -1 +0,0 @@
|
|||
func Random2(y int) int
|
|
@ -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()
|
|
@ -1 +0,0 @@
|
|||
type S2 struct{F1 string; F2 int; *a.A}
|
|
@ -1 +0,0 @@
|
|||
field F1 string
|
|
@ -1 +0,0 @@
|
|||
field F2 int
|
|
@ -1 +0,0 @@
|
|||
type S1 struct{F1 int; S2; a.A}
|
|
@ -1 +0,0 @@
|
|||
field F1 int
|
|
@ -1 +0,0 @@
|
|||
field S2 S2
|
|
@ -0,0 +1,4 @@
|
|||
-- S1-hover --
|
||||
type S1 struct{F1 int; S2; a.A}
|
||||
-- S1F1-hover --
|
||||
field F1 int
|
|
@ -0,0 +1,2 @@
|
|||
-- myUnclosedIf-hover --
|
||||
var myUnclosedIf string
|
|
@ -1 +0,0 @@
|
|||
var myUnclosedIf string
|
|
@ -0,0 +1,4 @@
|
|||
-- gofmt --
|
||||
|
||||
-- gofmt-d --
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue