From 2acd0f4c519f3752d89633cf1fdb1368a0fba555 Mon Sep 17 00:00:00 2001 From: Ian Cottrell Date: Tue, 9 Apr 2019 22:23:02 -0400 Subject: [PATCH] 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 --- .../format/bad_format.gofmt.golden.go | 19 +++++ .../format/good_format.gofmt.golden.go | 9 ++ .../format/newline_format.gofmt.golden.go | 2 + .../noparse_format.gofmt.golden.go | 0 internal/lsp/tests/tests.go | 82 ++++++++++++++++--- 5 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 internal/lsp/testdata/format/bad_format.gofmt.golden.go create mode 100644 internal/lsp/testdata/format/good_format.gofmt.golden.go create mode 100644 internal/lsp/testdata/format/newline_format.gofmt.golden.go create mode 100644 internal/lsp/testdata/noparse_format/noparse_format.gofmt.golden.go 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 00000000..919b2d25 --- /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 00000000..01cb1610 --- /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 00000000..29459ac8 --- /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 00000000..e69de29b diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index 10012c84..d4a89601 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 @@ const ( 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 @@ type Data struct { Symbols Symbols symbolsChildren SymbolsChildren Signatures Signatures + + t testing.TB + fragments map[string]string + dir string } type Tests interface { @@ -91,19 +106,23 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data { 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 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data { } 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 Run(t *testing.T, tests Tests, data *Data) { }) } +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) collectCompletionItems(pos token.Pos, label, detail, kind stri } 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) {