gopls: refactor the cmd tests
This allows them to be run from the gopls module as well to test
the code with the hooks installed.
Change-Id: I3079a04ffe3bd221ccc2523e746cbed384e05e2f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/196321
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/gopls/test/gopls_test.go b/gopls/test/gopls_test.go
new file mode 100644
index 0000000..5245579
--- /dev/null
+++ b/gopls/test/gopls_test.go
@@ -0,0 +1,38 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gopls_test
+
+import (
+ "context"
+ "os"
+ "testing"
+
+ "golang.org/x/tools/go/packages/packagestest"
+ "golang.org/x/tools/gopls/internal/hooks"
+ cmdtest "golang.org/x/tools/internal/lsp/cmd/test"
+ "golang.org/x/tools/internal/lsp/tests"
+ "golang.org/x/tools/internal/testenv"
+)
+
+func TestMain(m *testing.M) {
+ testenv.ExitIfSmallMachine()
+ os.Exit(m.Run())
+}
+
+func TestCommandLine(t *testing.T) {
+ packagestest.TestAll(t, testCommandLine)
+}
+
+func testCommandLine(t *testing.T, exporter packagestest.Exporter) {
+ const testdata = "../../internal/lsp/testdata"
+ if stat, err := os.Stat(testdata); err != nil || !stat.IsDir() {
+ t.Skip("testdata directory not present")
+ }
+ ctx := context.Background()
+ hooks.Install(ctx)
+ data := tests.Load(t, exporter, testdata)
+ defer data.Exported.Cleanup()
+ tests.Run(t, cmdtest.NewRunner(exporter, data, tests.Context(t)), data)
+}
diff --git a/internal/lsp/cmd/cmd_test.go b/internal/lsp/cmd/cmd_test.go
index 4dad9db..1440459 100644
--- a/internal/lsp/cmd/cmd_test.go
+++ b/internal/lsp/cmd/cmd_test.go
@@ -5,18 +5,19 @@
package cmd_test
import (
- "bytes"
- "context"
- "io/ioutil"
+ "fmt"
"os"
"path/filepath"
- "strconv"
- "strings"
+ "regexp"
+ "runtime"
"testing"
"golang.org/x/tools/go/packages/packagestest"
+ "golang.org/x/tools/internal/lsp/cmd"
+ cmdtest "golang.org/x/tools/internal/lsp/cmd/test"
"golang.org/x/tools/internal/lsp/tests"
"golang.org/x/tools/internal/testenv"
+ "golang.org/x/tools/internal/tool"
)
func TestMain(m *testing.M) {
@@ -24,12 +25,6 @@
os.Exit(m.Run())
}
-type runner struct {
- exporter packagestest.Exporter
- data *tests.Data
- ctx context.Context
-}
-
func TestCommandLine(t *testing.T) {
packagestest.TestAll(t, testCommandLine)
}
@@ -37,138 +32,32 @@
func testCommandLine(t *testing.T, exporter packagestest.Exporter) {
data := tests.Load(t, exporter, "../testdata")
defer data.Exported.Cleanup()
+ tests.Run(t, cmdtest.NewRunner(exporter, data, tests.Context(t)), data)
+}
- r := &runner{
- exporter: exporter,
- data: data,
- ctx: tests.Context(t),
+func TestDefinitionHelpExample(t *testing.T) {
+ // TODO: https://golang.org/issue/32794.
+ t.Skip()
+ if runtime.GOOS == "android" {
+ t.Skip("not all source files are available on android")
}
- tests.Run(t, r, data)
-}
-
-func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) {
- //TODO: add command line completions tests when it works
-}
-
-func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) {
- //TODO: add command line folding range tests when it works
-}
-
-func (r *runner) Highlight(t *testing.T, data tests.Highlights) {
- //TODO: add command line highlight tests when it works
-}
-
-func (r *runner) Reference(t *testing.T, data tests.References) {
- //TODO: add command line references tests when it works
-}
-
-func (r *runner) PrepareRename(t *testing.T, data tests.PrepareRenames) {
- //TODO: add command line prepare rename tests when it works
-}
-
-func (r *runner) Symbol(t *testing.T, data tests.Symbols) {
- //TODO: add command line symbol tests when it works
-}
-
-func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) {
- //TODO: add command line signature tests when it works
-}
-
-func (r *runner) Link(t *testing.T, data tests.Links) {
- //TODO: add command line link tests when it works
-}
-
-func (r *runner) Import(t *testing.T, data tests.Imports) {
- //TODO: add command line imports tests when it works
-}
-
-func (r *runner) SuggestedFix(t *testing.T, data tests.SuggestedFixes) {
- //TODO: add suggested fix tests when it works
-}
-
-func captureStdOut(t testing.TB, f func()) string {
- r, out, err := os.Pipe()
+ dir, err := os.Getwd()
if err != nil {
- t.Fatal(err)
+ t.Errorf("could not get wd: %v", err)
+ return
}
- old := os.Stdout
- defer func() {
- os.Stdout = old
- out.Close()
- r.Close()
- }()
- os.Stdout = out
- f()
- out.Close()
- data, err := ioutil.ReadAll(r)
- if err != nil {
- t.Fatal(err)
- }
- return string(data)
-}
-
-// normalizePaths replaces all paths present in s with just the fragment portion
-// this is used to make golden files not depend on the temporary paths of the files
-func normalizePaths(data *tests.Data, s string) string {
- type entry struct {
- path string
- index int
- fragment string
- }
- match := make([]entry, 0, len(data.Exported.Modules))
- // collect the initial state of all the matchers
- for _, m := range data.Exported.Modules {
- for fragment := range m.Files {
- filename := data.Exported.File(m.Name, fragment)
- index := strings.Index(s, filename)
- if index >= 0 {
- match = append(match, entry{filename, index, fragment})
- }
- if slash := filepath.ToSlash(filename); slash != filename {
- index := strings.Index(s, slash)
- if index >= 0 {
- match = append(match, entry{slash, index, fragment})
- }
- }
- quoted := strconv.Quote(filename)
- if escaped := quoted[1 : len(quoted)-1]; escaped != filename {
- index := strings.Index(s, escaped)
- if index >= 0 {
- match = append(match, entry{escaped, index, fragment})
- }
- }
- }
- }
- // result should be the same or shorter than the input
- buf := bytes.NewBuffer(make([]byte, 0, len(s)))
- last := 0
- for {
- // find the nearest path match to the start of the buffer
- next := -1
- nearest := len(s)
- for i, c := range match {
- if c.index >= 0 && nearest > c.index {
- nearest = c.index
- next = i
- }
- }
- // if there are no matches, we copy the rest of the string and are done
- if next < 0 {
- buf.WriteString(s[last:])
- return buf.String()
- }
- // we have a match
- n := &match[next]
- // copy up to the start of the match
- buf.WriteString(s[last:n.index])
- // skip over the filename
- last = n.index + len(n.path)
- // add in the fragment instead
- buf.WriteString(n.fragment)
- // see what the next match for this path is
- n.index = strings.Index(s[last:], n.path)
- if n.index >= 0 {
- n.index += last
+ thisFile := filepath.Join(dir, "definition.go")
+ baseArgs := []string{"query", "definition"}
+ expect := regexp.MustCompile(`(?s)^[\w/\\:_-]+flag[/\\]flag.go:\d+:\d+-\d+: defined here as FlagSet struct {.*}$`)
+ for _, query := range []string{
+ fmt.Sprintf("%v:%v:%v", thisFile, cmd.ExampleLine, cmd.ExampleColumn),
+ fmt.Sprintf("%v:#%v", thisFile, cmd.ExampleOffset)} {
+ args := append(baseArgs, query)
+ got := cmdtest.CaptureStdOut(t, func() {
+ _ = tool.Run(tests.Context(t), cmd.New("gopls-test", "", nil), args)
+ })
+ if !expect.MatchString(got) {
+ t.Errorf("test with %v\nexpected:\n%s\ngot:\n%s", args, expect, got)
}
}
}
diff --git a/internal/lsp/cmd/check_test.go b/internal/lsp/cmd/test/check.go
similarity index 96%
rename from internal/lsp/cmd/check_test.go
rename to internal/lsp/cmd/test/check.go
index 5752b87..78ea9d5 100644
--- a/internal/lsp/cmd/check_test.go
+++ b/internal/lsp/cmd/test/check.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package cmd_test
+package cmdtest
import (
"fmt"
@@ -24,7 +24,7 @@
fname := uri.Filename()
args := []string{"-remote=internal", "check", fname}
app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env)
- out := captureStdOut(t, func() {
+ out := CaptureStdOut(t, func() {
_ = tool.Run(r.ctx, app, args)
})
// parse got into a collection of reports
diff --git a/internal/lsp/cmd/test/cmdtest.go b/internal/lsp/cmd/test/cmdtest.go
new file mode 100644
index 0000000..3352323
--- /dev/null
+++ b/internal/lsp/cmd/test/cmdtest.go
@@ -0,0 +1,161 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package cmdtest contains the test suite for the command line behavior of gopls.
+package cmdtest
+
+import (
+ "bytes"
+ "context"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "testing"
+
+ "golang.org/x/tools/go/packages/packagestest"
+ "golang.org/x/tools/internal/lsp/tests"
+)
+
+type runner struct {
+ exporter packagestest.Exporter
+ data *tests.Data
+ ctx context.Context
+}
+
+func NewRunner(exporter packagestest.Exporter, data *tests.Data, ctx context.Context) tests.Tests {
+ return &runner{
+ exporter: exporter,
+ data: data,
+ ctx: ctx,
+ }
+}
+
+func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) {
+ //TODO: add command line completions tests when it works
+}
+
+func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) {
+ //TODO: add command line folding range tests when it works
+}
+
+func (r *runner) Highlight(t *testing.T, data tests.Highlights) {
+ //TODO: add command line highlight tests when it works
+}
+
+func (r *runner) Reference(t *testing.T, data tests.References) {
+ //TODO: add command line references tests when it works
+}
+
+func (r *runner) PrepareRename(t *testing.T, data tests.PrepareRenames) {
+ //TODO: add command line prepare rename tests when it works
+}
+
+func (r *runner) Symbol(t *testing.T, data tests.Symbols) {
+ //TODO: add command line symbol tests when it works
+}
+
+func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) {
+ //TODO: add command line signature tests when it works
+}
+
+func (r *runner) Link(t *testing.T, data tests.Links) {
+ //TODO: add command line link tests when it works
+}
+
+func (r *runner) Import(t *testing.T, data tests.Imports) {
+ //TODO: add command line imports tests when it works
+}
+
+func (r *runner) SuggestedFix(t *testing.T, data tests.SuggestedFixes) {
+ //TODO: add suggested fix tests when it works
+}
+
+func CaptureStdOut(t testing.TB, f func()) string {
+ r, out, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ old := os.Stdout
+ defer func() {
+ os.Stdout = old
+ out.Close()
+ r.Close()
+ }()
+ os.Stdout = out
+ f()
+ out.Close()
+ data, err := ioutil.ReadAll(r)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return string(data)
+}
+
+// normalizePaths replaces all paths present in s with just the fragment portion
+// this is used to make golden files not depend on the temporary paths of the files
+func normalizePaths(data *tests.Data, s string) string {
+ type entry struct {
+ path string
+ index int
+ fragment string
+ }
+ match := make([]entry, 0, len(data.Exported.Modules))
+ // collect the initial state of all the matchers
+ for _, m := range data.Exported.Modules {
+ for fragment := range m.Files {
+ filename := data.Exported.File(m.Name, fragment)
+ index := strings.Index(s, filename)
+ if index >= 0 {
+ match = append(match, entry{filename, index, fragment})
+ }
+ if slash := filepath.ToSlash(filename); slash != filename {
+ index := strings.Index(s, slash)
+ if index >= 0 {
+ match = append(match, entry{slash, index, fragment})
+ }
+ }
+ quoted := strconv.Quote(filename)
+ if escaped := quoted[1 : len(quoted)-1]; escaped != filename {
+ index := strings.Index(s, escaped)
+ if index >= 0 {
+ match = append(match, entry{escaped, index, fragment})
+ }
+ }
+ }
+ }
+ // result should be the same or shorter than the input
+ buf := bytes.NewBuffer(make([]byte, 0, len(s)))
+ last := 0
+ for {
+ // find the nearest path match to the start of the buffer
+ next := -1
+ nearest := len(s)
+ for i, c := range match {
+ if c.index >= 0 && nearest > c.index {
+ nearest = c.index
+ next = i
+ }
+ }
+ // if there are no matches, we copy the rest of the string and are done
+ if next < 0 {
+ buf.WriteString(s[last:])
+ return buf.String()
+ }
+ // we have a match
+ n := &match[next]
+ // copy up to the start of the match
+ buf.WriteString(s[last:n.index])
+ // skip over the filename
+ last = n.index + len(n.path)
+ // add in the fragment instead
+ buf.WriteString(n.fragment)
+ // see what the next match for this path is
+ n.index = strings.Index(s[last:], n.path)
+ if n.index >= 0 {
+ n.index += last
+ }
+ }
+}
diff --git a/internal/lsp/cmd/definition_test.go b/internal/lsp/cmd/test/definition.go
similarity index 63%
rename from internal/lsp/cmd/definition_test.go
rename to internal/lsp/cmd/test/definition.go
index 5737fc0..158d943 100644
--- a/internal/lsp/cmd/definition_test.go
+++ b/internal/lsp/cmd/test/definition.go
@@ -2,13 +2,10 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package cmd_test
+package cmdtest
import (
"fmt"
- "os"
- "path/filepath"
- "regexp"
"runtime"
"strings"
"testing"
@@ -36,33 +33,6 @@
jsonGoDef,
}
-func TestDefinitionHelpExample(t *testing.T) {
- // TODO: https://golang.org/issue/32794.
- t.Skip()
- if runtime.GOOS == "android" {
- t.Skip("not all source files are available on android")
- }
- dir, err := os.Getwd()
- if err != nil {
- t.Errorf("could not get wd: %v", err)
- return
- }
- thisFile := filepath.Join(dir, "definition.go")
- baseArgs := []string{"query", "definition"}
- expect := regexp.MustCompile(`(?s)^[\w/\\:_-]+flag[/\\]flag.go:\d+:\d+-\d+: defined here as FlagSet struct {.*}$`)
- for _, query := range []string{
- fmt.Sprintf("%v:%v:%v", thisFile, cmd.ExampleLine, cmd.ExampleColumn),
- fmt.Sprintf("%v:#%v", thisFile, cmd.ExampleOffset)} {
- args := append(baseArgs, query)
- got := captureStdOut(t, func() {
- _ = tool.Run(tests.Context(t), cmd.New("gopls-test", "", nil), args)
- })
- if !expect.MatchString(got) {
- t.Errorf("test with %v\nexpected:\n%s\ngot:\n%s", args, expect, got)
- }
- }
-}
-
func (r *runner) Definition(t *testing.T, data tests.Definitions) {
// TODO: https://golang.org/issue/32794.
t.Skip()
@@ -82,7 +52,7 @@
args = append(args, "definition")
uri := d.Src.URI()
args = append(args, fmt.Sprint(d.Src))
- got := captureStdOut(t, func() {
+ got := CaptureStdOut(t, func() {
app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env)
_ = tool.Run(r.ctx, app, args)
})
diff --git a/internal/lsp/cmd/format_test.go b/internal/lsp/cmd/test/format.go
similarity index 96%
rename from internal/lsp/cmd/format_test.go
rename to internal/lsp/cmd/test/format.go
index 8782461..11e3419 100644
--- a/internal/lsp/cmd/format_test.go
+++ b/internal/lsp/cmd/test/format.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package cmd_test
+package cmdtest
import (
"os/exec"
@@ -38,7 +38,7 @@
continue
}
app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Config.Env)
- got := captureStdOut(t, func() {
+ got := CaptureStdOut(t, func() {
_ = tool.Run(r.ctx, app, append([]string{"-remote=internal", "format"}, args...))
})
got = normalizePaths(r.data, got)
diff --git a/internal/lsp/cmd/rename_test.go b/internal/lsp/cmd/test/rename.go
similarity index 96%
rename from internal/lsp/cmd/rename_test.go
rename to internal/lsp/cmd/test/rename.go
index 73d3296..b7fd272 100644
--- a/internal/lsp/cmd/rename_test.go
+++ b/internal/lsp/cmd/test/rename.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package cmd_test
+package cmdtest
import (
"fmt"
@@ -40,7 +40,7 @@
}
args = append(args, loc, tag)
var err error
- got := captureStdOut(t, func() {
+ got := CaptureStdOut(t, func() {
err = tool.Run(r.ctx, app, args)
})
if err != nil {