gopls/internal/lsp/cmd/test: signpost future cleanups

Also, remove dead code.

Updates golang/go#54845

Change-Id: Ida96b91b878bfb0598dbe59e4d704771f83e0c0a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/462817
Auto-Submit: Alan Donovan <adonovan@google.com>
Run-TryBot: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/gopls/internal/lsp/cmd/test/cmdtest.go b/gopls/internal/lsp/cmd/test/cmdtest.go
index a95875d..cc0ea86 100644
--- a/gopls/internal/lsp/cmd/test/cmdtest.go
+++ b/gopls/internal/lsp/cmd/test/cmdtest.go
@@ -30,7 +30,7 @@
 
 // TestCommandLine runs the marker tests in files beneath testdata/ using
 // implementations of each of the marker operations (e.g. @hover) that
-// fork+exec the gopls command.
+// call the main function of the gopls command within this process.
 func TestCommandLine(t *testing.T, testdata string, options func(*source.Options)) {
 	// On Android, the testdata directory is not copied to the runner.
 	if runtime.GOOS == "android" {
@@ -39,7 +39,7 @@
 	tests.RunTests(t, testdata, false, func(t *testing.T, datum *tests.Data) {
 		ctx := tests.Context(t)
 		ts := newTestServer(ctx, options)
-		tests.Run(t, NewRunner(datum, ctx, ts.Addr, options), datum)
+		tests.Run(t, newRunner(datum, ctx, ts.Addr, options), datum)
 		cmd.CloseTestConnections(ctx)
 	})
 }
@@ -51,7 +51,29 @@
 	return servertest.NewTCPServer(ctx, ss, nil)
 }
 
-// runner implements tests.Tests by fork+execing the gopls command.
+// runner implements tests.Tests by invoking the gopls command.
+//
+// TODO(golang/go#54845): We don't plan to implement all the methods
+// of tests.Test.  Indeed, we'd like to delete the methods that are
+// implemented because the two problems they solve are best addressed
+// in other ways:
+//
+//  1. They provide coverage of the behavior of the server, but this
+//     coverage is almost identical to the coverage provided by
+//     executing the same tests by making LSP RPCs directly (see
+//     lsp_test), and the latter is more efficient. When they do
+//     differ, it is a pain for maintainers.
+//
+//  2. They provide coverage of the client-side code of the
+//     command-line tool, which turns arguments into an LSP request and
+//     prints the results. But this coverage could be more directly and
+//     efficiently achieved by running a small number of tests tailored
+//     to exercise the client-side code, not the server behavior.
+//
+// Once that's done, tests.Tests would have only a single
+// implementation (LSP), and we could refactor the marker tests
+// so that they more closely resemble self-contained regtests,
+// as described in #54845.
 type runner struct {
 	data        *tests.Data
 	ctx         context.Context
@@ -60,7 +82,7 @@
 	remote      string
 }
 
-func NewRunner(data *tests.Data, ctx context.Context, remote string, options func(*source.Options)) *runner {
+func newRunner(data *tests.Data, ctx context.Context, remote string, options func(*source.Options)) *runner {
 	return &runner{
 		data:        data,
 		ctx:         ctx,
@@ -70,60 +92,15 @@
 	}
 }
 
-func (r *runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens) {
-	//TODO: add command line completions tests when it works
-}
-
-func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
-	//TODO: add command line completions tests when it works
-}
-
-func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) {
-	//TODO: add command line completions tests when it works
-}
-
-func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
-	//TODO: add command line completions tests when it works
-}
-
-func (r *runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
-	//TODO: add command line completions tests when it works
-}
-
-func (r *runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
-	//TODO: add command line completions tests when it works
-}
-
-func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
-	//TODO: add command line completions tests when it works
-}
-
-func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
-	//TODO: add command line completions tests when it works
-}
-
-func (r *runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span) {
-	//TODO: function extraction not supported on command line
-}
-
-func (r *runner) MethodExtraction(t *testing.T, start span.Span, end span.Span) {
-	//TODO: function extraction not supported on command line
-}
-
-func (r *runner) AddImport(t *testing.T, uri span.URI, expectedImport string) {
-	//TODO: import addition not supported on command line
-}
-
-func (r *runner) Hover(t *testing.T, spn span.Span, info string) {
-	//TODO: hovering not supported on command line
-}
-
-func (r *runner) InlayHints(t *testing.T, spn span.Span) {
-	// TODO: inlayHints not supported on command line
-}
-
-func (r *runner) SelectionRanges(t *testing.T, spn span.Span) {}
-
+// runGoplsCmd returns the stdout and stderr of a gopls command.
+//
+// It does not fork+exec gopls, but in effect calls its main function,
+// and thus the subcommand's Application.Run method, within this process.
+//
+// Stdout and stderr are temporarily redirected: not concurrency-safe!
+//
+// The "exit code" is printed to stderr but not returned.
+// Invalid flags cause process exit.
 func (r *runner) runGoplsCmd(t testing.TB, args ...string) (string, string) {
 	rStdout, wStdout, err := os.Pipe()
 	if err != nil {
@@ -175,6 +152,26 @@
 	return tests.Normalize(s, r.normalizers)
 }
 
-func (r *runner) NormalizePrefix(s string) string {
-	return tests.NormalizePrefix(s, r.normalizers)
+// Unimplemented methods of tests.Tests (see comment at runner):
+
+func (*runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens) {}
+func (*runner) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
 }
+func (*runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) {
+}
+func (*runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
+}
+func (*runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
+}
+func (*runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
+}
+func (*runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
+}
+func (*runner) RankCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
+}
+func (*runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span) {}
+func (*runner) MethodExtraction(t *testing.T, start span.Span, end span.Span)   {}
+func (*runner) AddImport(t *testing.T, uri span.URI, expectedImport string)     {}
+func (*runner) Hover(t *testing.T, spn span.Span, info string)                  {}
+func (*runner) InlayHints(t *testing.T, spn span.Span)                          {}
+func (*runner) SelectionRanges(t *testing.T, spn span.Span)                     {}
diff --git a/gopls/internal/lsp/completion_test.go b/gopls/internal/lsp/completion_test.go
index 2257846..8211cc5 100644
--- a/gopls/internal/lsp/completion_test.go
+++ b/gopls/internal/lsp/completion_test.go
@@ -5,11 +5,13 @@
 package lsp
 
 import (
+	"fmt"
 	"strings"
 	"testing"
 
 	"golang.org/x/tools/gopls/internal/lsp/protocol"
 	"golang.org/x/tools/gopls/internal/lsp/source"
+	"golang.org/x/tools/gopls/internal/lsp/source/completion"
 	"golang.org/x/tools/gopls/internal/lsp/tests"
 	"golang.org/x/tools/gopls/internal/span"
 )
@@ -111,10 +113,28 @@
 func expected(t *testing.T, test tests.Completion, items tests.CompletionItems) []protocol.CompletionItem {
 	t.Helper()
 
+	toProtocolCompletionItem := func(item *completion.CompletionItem) protocol.CompletionItem {
+		pItem := protocol.CompletionItem{
+			Label:         item.Label,
+			Kind:          item.Kind,
+			Detail:        item.Detail,
+			Documentation: item.Documentation,
+			InsertText:    item.InsertText,
+			TextEdit: &protocol.TextEdit{
+				NewText: item.Snippet(),
+			},
+			// Negate score so best score has lowest sort text like real API.
+			SortText: fmt.Sprint(-item.Score),
+		}
+		if pItem.InsertText == "" {
+			pItem.InsertText = pItem.Label
+		}
+		return pItem
+	}
+
 	var want []protocol.CompletionItem
 	for _, pos := range test.CompletionItems {
-		item := items[pos]
-		want = append(want, tests.ToProtocolCompletionItem(*item))
+		want = append(want, toProtocolCompletionItem(items[pos]))
 	}
 	return want
 }
diff --git a/gopls/internal/lsp/tests/normalizer.go b/gopls/internal/lsp/tests/normalizer.go
index 77d9e66..9c5d7b9 100644
--- a/gopls/internal/lsp/tests/normalizer.go
+++ b/gopls/internal/lsp/tests/normalizer.go
@@ -41,22 +41,6 @@
 	return normalizers
 }
 
-// NormalizePrefix normalizes a single path at the front of the input string.
-func NormalizePrefix(s string, normalizers []Normalizer) string {
-	for _, n := range normalizers {
-		if t := strings.TrimPrefix(s, n.path); t != s {
-			return n.fragment + t
-		}
-		if t := strings.TrimPrefix(s, n.slashed); t != s {
-			return n.fragment + t
-		}
-		if t := strings.TrimPrefix(s, n.escaped); t != s {
-			return n.fragment + t
-		}
-	}
-	return s
-}
-
 // Normalize 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 Normalize(s string, normalizers []Normalizer) string {
diff --git a/gopls/internal/lsp/tests/util.go b/gopls/internal/lsp/tests/util.go
index ec7f80c..cdbd12e 100644
--- a/gopls/internal/lsp/tests/util.go
+++ b/gopls/internal/lsp/tests/util.go
@@ -275,25 +275,6 @@
 	return ""
 }
 
-func ToProtocolCompletionItem(item completion.CompletionItem) protocol.CompletionItem {
-	pItem := protocol.CompletionItem{
-		Label:         item.Label,
-		Kind:          item.Kind,
-		Detail:        item.Detail,
-		Documentation: item.Documentation,
-		InsertText:    item.InsertText,
-		TextEdit: &protocol.TextEdit{
-			NewText: item.Snippet(),
-		},
-		// Negate score so best score has lowest sort text like real API.
-		SortText: fmt.Sprint(-item.Score),
-	}
-	if pItem.InsertText == "" {
-		pItem.InsertText = pItem.Label
-	}
-	return pItem
-}
-
 func FilterBuiltins(src span.Span, items []protocol.CompletionItem) []protocol.CompletionItem {
 	var (
 		got          []protocol.CompletionItem