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 {