gopls/internal/lsp/cmd/test: delete marker-based tests of gopls cmd

These tests were an implementation of the tests.Test marker-test
machinery, based on invoking the logic of the gopls command,
within the same process. The marker tests exercise server
logic, which is best done by making LSP requests, as the lsp_test
does.

Client logic in the gopls command and its subcommands is
best exercised by fork+execing the command, and exercising
features on the client, such as flags, argument parsing, and
output printing. That's what the new integration_test does.

With this change we are down to one implementation of tests.Tests,
which means we can start to change it to make it usable from
regtests too.

Updates golang/go#54845

Change-Id: Ia00897f0268ed4c293e716e1d34d9c84cfdf3109
Reviewed-on: https://go-review.googlesource.com/c/tools/+/463555
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Alan Donovan <adonovan@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
diff --git a/gopls/internal/lsp/cmd/test/call_hierarchy.go b/gopls/internal/lsp/cmd/test/call_hierarchy.go
deleted file mode 100644
index 5517a51..0000000
--- a/gopls/internal/lsp/cmd/test/call_hierarchy.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2020 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
-
-import (
-	"fmt"
-	"sort"
-	"strings"
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/lsp/protocol"
-	"golang.org/x/tools/gopls/internal/lsp/tests"
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-func (r *runner) CallHierarchy(t *testing.T, spn span.Span, expectedCalls *tests.CallHierarchyResult) {
-	collectCallSpansString := func(callItems []protocol.CallHierarchyItem) string {
-		var callSpans []string
-		for _, call := range callItems {
-			mapper, err := r.data.Mapper(call.URI.SpanURI())
-			if err != nil {
-				t.Fatal(err)
-			}
-			callSpan, err := mapper.LocationSpan(protocol.Location{URI: call.URI, Range: call.Range})
-			if err != nil {
-				t.Fatal(err)
-			}
-			callSpans = append(callSpans, fmt.Sprint(callSpan))
-		}
-		// to make tests deterministic
-		sort.Strings(callSpans)
-		return r.Normalize(strings.Join(callSpans, "\n"))
-	}
-
-	expectIn, expectOut := collectCallSpansString(expectedCalls.IncomingCalls), collectCallSpansString(expectedCalls.OutgoingCalls)
-	expectIdent := r.Normalize(fmt.Sprint(spn))
-
-	uri := spn.URI()
-	filename := uri.Filename()
-	target := filename + fmt.Sprintf(":%v:%v", spn.Start().Line(), spn.Start().Column())
-
-	got, stderr := r.NormalizeGoplsCmd(t, "call_hierarchy", target)
-	if stderr != "" {
-		t.Fatalf("call_hierarchy failed for %s: %s", target, stderr)
-	}
-
-	gotIn, gotIdent, gotOut := cleanCallHierarchyCmdResult(got)
-	if expectIn != gotIn {
-		t.Errorf("incoming calls call_hierarchy failed for %s expected:\n%s\ngot:\n%s", target, expectIn, gotIn)
-	}
-	if expectIdent != gotIdent {
-		t.Errorf("call_hierarchy failed for %s expected:\n%s\ngot:\n%s", target, expectIdent, gotIdent)
-	}
-	if expectOut != gotOut {
-		t.Errorf("outgoing calls call_hierarchy failed for %s expected:\n%s\ngot:\n%s", target, expectOut, gotOut)
-	}
-
-}
-
-// parses function URI and Range from call hierarchy cmd output to
-// incoming, identifier and outgoing calls (returned in that order)
-// ex: "identifier: function d at .../callhierarchy/callhierarchy.go:19:6-7" -> ".../callhierarchy/callhierarchy.go:19:6-7"
-func cleanCallHierarchyCmdResult(output string) (incoming, ident, outgoing string) {
-	var incomingCalls, outgoingCalls []string
-	for _, out := range strings.Split(output, "\n") {
-		if out == "" {
-			continue
-		}
-
-		callLocation := out[strings.LastIndex(out, " ")+1:]
-		if strings.HasPrefix(out, "caller") {
-			incomingCalls = append(incomingCalls, callLocation)
-		} else if strings.HasPrefix(out, "callee") {
-			outgoingCalls = append(outgoingCalls, callLocation)
-		} else {
-			ident = callLocation
-		}
-	}
-	sort.Strings(incomingCalls)
-	sort.Strings(outgoingCalls)
-	incoming, outgoing = strings.Join(incomingCalls, "\n"), strings.Join(outgoingCalls, "\n")
-	return
-}
diff --git a/gopls/internal/lsp/cmd/test/check.go b/gopls/internal/lsp/cmd/test/check.go
deleted file mode 100644
index dcbb2e0..0000000
--- a/gopls/internal/lsp/cmd/test/check.go
+++ /dev/null
@@ -1,63 +0,0 @@
-// 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
-
-import (
-	"io/ioutil"
-	"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/tests"
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-// Diagnostics runs the "gopls check" command on a single file, parses
-// its diagnostics, and compares against the expectations defined by
-// markers in the source file.
-func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []*source.Diagnostic) {
-	out, _ := r.runGoplsCmd(t, "check", uri.Filename())
-
-	content, err := ioutil.ReadFile(uri.Filename())
-	if err != nil {
-		t.Fatal(err)
-	}
-	mapper := protocol.NewMapper(uri, content)
-
-	// Parse command output into a set of diagnostics.
-	var got []*source.Diagnostic
-	for _, line := range strings.Split(out, "\n") {
-		if line == "" {
-			continue // skip blank
-		}
-		parts := strings.SplitN(line, ": ", 2) // "span: message"
-		if len(parts) != 2 {
-			t.Fatalf("output line not of form 'span: message': %q", line)
-		}
-		spn, message := span.Parse(parts[0]), parts[1]
-		rng, err := mapper.SpanRange(spn)
-		if err != nil {
-			t.Fatal(err)
-		}
-		// Set only the fields needed by DiffDiagnostics.
-		got = append(got, &source.Diagnostic{
-			URI:     uri,
-			Range:   rng,
-			Message: message,
-		})
-	}
-
-	// Don't expect fields that we can't populate from the command output.
-	for _, diag := range want {
-		if diag.Source == "no_diagnostics" {
-			continue // see DiffDiagnostics
-		}
-		diag.Source = ""
-		diag.Severity = 0
-	}
-
-	tests.CompareDiagnostics(t, uri, want, got)
-}
diff --git a/gopls/internal/lsp/cmd/test/cmdtest.go b/gopls/internal/lsp/cmd/test/cmdtest.go
index cc0ea86..7f8a13b 100644
--- a/gopls/internal/lsp/cmd/test/cmdtest.go
+++ b/gopls/internal/lsp/cmd/test/cmdtest.go
@@ -1,177 +1,6 @@
-// Copyright 2019 The Go Authors. All rights reserved.
+// Copyright 2023 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"
-	"flag"
-	"fmt"
-	"io"
-	"os"
-	"runtime"
-	"sync"
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/lsp/cache"
-	"golang.org/x/tools/gopls/internal/lsp/cmd"
-	"golang.org/x/tools/gopls/internal/lsp/debug"
-	"golang.org/x/tools/gopls/internal/lsp/lsprpc"
-	"golang.org/x/tools/gopls/internal/lsp/protocol"
-	"golang.org/x/tools/gopls/internal/lsp/source"
-	"golang.org/x/tools/gopls/internal/lsp/tests"
-	"golang.org/x/tools/gopls/internal/span"
-	"golang.org/x/tools/internal/jsonrpc2/servertest"
-	"golang.org/x/tools/internal/tool"
-)
-
-// TestCommandLine runs the marker tests in files beneath testdata/ using
-// implementations of each of the marker operations (e.g. @hover) that
-// 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" {
-		t.Skip("testdata directory not present on android")
-	}
-	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)
-		cmd.CloseTestConnections(ctx)
-	})
-}
-
-func newTestServer(ctx context.Context, options func(*source.Options)) *servertest.TCPServer {
-	ctx = debug.WithInstance(ctx, "", "")
-	cache := cache.New(nil, nil)
-	ss := lsprpc.NewStreamServer(cache, false, options)
-	return servertest.NewTCPServer(ctx, ss, nil)
-}
-
-// 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
-	options     func(*source.Options)
-	normalizers []tests.Normalizer
-	remote      string
-}
-
-func newRunner(data *tests.Data, ctx context.Context, remote string, options func(*source.Options)) *runner {
-	return &runner{
-		data:        data,
-		ctx:         ctx,
-		options:     options,
-		normalizers: tests.CollectNormalizers(data.Exported),
-		remote:      remote,
-	}
-}
-
-// 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 {
-		t.Fatal(err)
-	}
-	oldStdout := os.Stdout
-	rStderr, wStderr, err := os.Pipe()
-	if err != nil {
-		t.Fatal(err)
-	}
-	oldStderr := os.Stderr
-	stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
-	var wg sync.WaitGroup
-	wg.Add(2)
-	go func() {
-		io.Copy(stdout, rStdout)
-		wg.Done()
-	}()
-	go func() {
-		io.Copy(stderr, rStderr)
-		wg.Done()
-	}()
-	os.Stdout, os.Stderr = wStdout, wStderr
-	app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env, r.options)
-	remote := r.remote
-	s := flag.NewFlagSet(app.Name(), flag.ExitOnError)
-	err = tool.Run(tests.Context(t), s,
-		app,
-		append([]string{fmt.Sprintf("-remote=internal@%s", remote)}, args...))
-	if err != nil {
-		fmt.Fprint(os.Stderr, err)
-	}
-	wStdout.Close()
-	wStderr.Close()
-	wg.Wait()
-	os.Stdout, os.Stderr = oldStdout, oldStderr
-	rStdout.Close()
-	rStderr.Close()
-	return stdout.String(), stderr.String()
-}
-
-// NormalizeGoplsCmd runs the gopls command and normalizes its output.
-func (r *runner) NormalizeGoplsCmd(t testing.TB, args ...string) (string, string) {
-	stdout, stderr := r.runGoplsCmd(t, args...)
-	return r.Normalize(stdout), r.Normalize(stderr)
-}
-
-func (r *runner) Normalize(s string) string {
-	return tests.Normalize(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/cmd/test/definition.go b/gopls/internal/lsp/cmd/test/definition.go
deleted file mode 100644
index ca84e80..0000000
--- a/gopls/internal/lsp/cmd/test/definition.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// 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
-
-import (
-	"fmt"
-	"runtime"
-	"strings"
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/lsp/tests"
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-type godefMode int
-
-const (
-	plainGodef = godefMode(1 << iota)
-	jsonGoDef
-)
-
-var godefModes = []godefMode{
-	plainGodef,
-	jsonGoDef,
-}
-
-func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) {
-	if d.IsType || d.OnlyHover {
-		// TODO: support type definition, hover queries
-		return
-	}
-	d.Src = span.New(d.Src.URI(), span.NewPoint(0, 0, d.Src.Start().Offset()), span.Point{})
-	for _, mode := range godefModes {
-		args := []string{"definition", "-markdown"}
-		tag := d.Name + "-definition"
-		if mode&jsonGoDef != 0 {
-			tag += "-json"
-			args = append(args, "-json")
-		}
-		uri := d.Src.URI()
-		args = append(args, fmt.Sprint(d.Src))
-		got, _ := r.NormalizeGoplsCmd(t, args...)
-		if mode&jsonGoDef != 0 && runtime.GOOS == "windows" {
-			got = strings.Replace(got, "file:///", "file://", -1)
-		}
-		expect := strings.TrimSpace(string(r.data.Golden(t, tag, uri.Filename(), func() ([]byte, error) {
-			return []byte(got), nil
-		})))
-		if expect != "" && !strings.HasPrefix(got, expect) {
-			tests.CheckSameMarkdown(t, got, expect)
-		}
-	}
-}
diff --git a/gopls/internal/lsp/cmd/test/folding_range.go b/gopls/internal/lsp/cmd/test/folding_range.go
deleted file mode 100644
index 184c01a..0000000
--- a/gopls/internal/lsp/cmd/test/folding_range.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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
-
-import (
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-func (r *runner) FoldingRanges(t *testing.T, spn span.Span) {
-	goldenTag := "foldingRange-cmd"
-	uri := spn.URI()
-	filename := uri.Filename()
-	got, _ := r.NormalizeGoplsCmd(t, "folding_ranges", filename)
-	expect := string(r.data.Golden(t, goldenTag, filename, func() ([]byte, error) {
-		return []byte(got), nil
-	}))
-
-	if expect != got {
-		t.Errorf("folding_ranges failed failed for %s expected:\n%s\ngot:\n%s", filename, expect, got)
-	}
-}
diff --git a/gopls/internal/lsp/cmd/test/format.go b/gopls/internal/lsp/cmd/test/format.go
deleted file mode 100644
index 368d535..0000000
--- a/gopls/internal/lsp/cmd/test/format.go
+++ /dev/null
@@ -1,87 +0,0 @@
-// 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
-
-import (
-	"bytes"
-	"io/ioutil"
-	"os"
-	"regexp"
-	"strings"
-	"testing"
-
-	exec "golang.org/x/sys/execabs"
-
-	"golang.org/x/tools/gopls/internal/span"
-	"golang.org/x/tools/internal/testenv"
-)
-
-func (r *runner) Format(t *testing.T, spn span.Span) {
-	tag := "gofmt"
-	uri := spn.URI()
-	filename := uri.Filename()
-	expect := string(r.data.Golden(t, tag, filename, func() ([]byte, error) {
-		cmd := exec.Command("gofmt", filename)
-		contents, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files
-		contents = []byte(r.Normalize(fixFileHeader(string(contents))))
-		return contents, nil
-	}))
-	if expect == "" {
-		//TODO: our error handling differs, for now just skip unformattable files
-		t.Skip("Unformattable file")
-	}
-	got, _ := r.NormalizeGoplsCmd(t, "format", filename)
-	if expect != got {
-		t.Errorf("format failed for %s expected:\n%s\ngot:\n%s", filename, expect, got)
-	}
-	// now check we can build a valid unified diff
-	unified, _ := r.NormalizeGoplsCmd(t, "format", "-d", filename)
-	checkUnified(t, filename, expect, unified)
-}
-
-var unifiedHeader = regexp.MustCompile(`^diff -u.*\n(---\s+\S+\.go\.orig)\s+[\d-:. ]+(\n\+\+\+\s+\S+\.go)\s+[\d-:. ]+(\n@@)`)
-
-func fixFileHeader(s string) string {
-	match := unifiedHeader.FindStringSubmatch(s)
-	if match == nil {
-		return s
-	}
-	return strings.Join(append(match[1:], s[len(match[0]):]), "")
-}
-
-func checkUnified(t *testing.T, filename string, expect string, patch string) {
-	testenv.NeedsTool(t, "patch")
-	if strings.Count(patch, "\n+++ ") > 1 {
-		// TODO(golang/go/#34580)
-		t.Skip("multi-file patch tests not supported yet")
-	}
-	applied := ""
-	if patch == "" {
-		applied = expect
-	} else {
-		temp, err := ioutil.TempFile("", "applied")
-		if err != nil {
-			t.Fatal(err)
-		}
-		temp.Close()
-		defer os.Remove(temp.Name())
-		cmd := exec.Command("patch", "-u", "-p0", "-o", temp.Name(), filename)
-		cmd.Stdin = bytes.NewBuffer([]byte(patch))
-		msg, err := cmd.CombinedOutput()
-		if err != nil {
-			t.Errorf("failed applying patch to %s: %v\ngot:\n%s\npatch:\n%s", filename, err, msg, patch)
-			return
-		}
-		out, err := ioutil.ReadFile(temp.Name())
-		if err != nil {
-			t.Errorf("failed reading patched output for %s: %v\n", filename, err)
-			return
-		}
-		applied = string(out)
-	}
-	if expect != applied {
-		t.Errorf("apply unified gave wrong result for %s expected:\n%s\ngot:\n%s\npatch:\n%s", filename, expect, applied, patch)
-	}
-}
diff --git a/gopls/internal/lsp/cmd/test/highlight.go b/gopls/internal/lsp/cmd/test/highlight.go
deleted file mode 100644
index cd51b09..0000000
--- a/gopls/internal/lsp/cmd/test/highlight.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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
-
-import (
-	"testing"
-
-	"fmt"
-
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-func (r *runner) Highlight(t *testing.T, spn span.Span, spans []span.Span) {
-	var expect string
-	for _, l := range spans {
-		expect += fmt.Sprintln(l)
-	}
-	expect = r.Normalize(expect)
-
-	uri := spn.URI()
-	filename := uri.Filename()
-	target := filename + ":" + fmt.Sprint(spn.Start().Line()) + ":" + fmt.Sprint(spn.Start().Column())
-	got, _ := r.NormalizeGoplsCmd(t, "highlight", target)
-	if expect != got {
-		t.Errorf("highlight failed for %s expected:\n%s\ngot:\n%s", target, expect, got)
-	}
-}
diff --git a/gopls/internal/lsp/cmd/test/implementation.go b/gopls/internal/lsp/cmd/test/implementation.go
deleted file mode 100644
index e24584d..0000000
--- a/gopls/internal/lsp/cmd/test/implementation.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// 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
-
-import (
-	"fmt"
-	"sort"
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-func (r *runner) Implementation(t *testing.T, spn span.Span, imps []span.Span) {
-	var itemStrings []string
-	for _, i := range imps {
-		itemStrings = append(itemStrings, fmt.Sprint(i))
-	}
-	sort.Strings(itemStrings)
-	var expect string
-	for _, i := range itemStrings {
-		expect += i + "\n"
-	}
-	expect = r.Normalize(expect)
-
-	uri := spn.URI()
-	filename := uri.Filename()
-	target := filename + fmt.Sprintf(":%v:%v", spn.Start().Line(), spn.Start().Column())
-
-	got, stderr := r.NormalizeGoplsCmd(t, "implementation", target)
-	if stderr != "" {
-		t.Errorf("implementation failed for %s: %s", target, stderr)
-	} else if expect != got {
-		t.Errorf("implementation failed for %s expected:\n%s\ngot:\n%s", target, expect, got)
-	}
-}
diff --git a/gopls/internal/lsp/cmd/test/imports.go b/gopls/internal/lsp/cmd/test/imports.go
deleted file mode 100644
index d26c886..0000000
--- a/gopls/internal/lsp/cmd/test/imports.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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
-
-import (
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/span"
-	"golang.org/x/tools/internal/diff"
-)
-
-func (r *runner) Import(t *testing.T, spn span.Span) {
-	uri := spn.URI()
-	filename := uri.Filename()
-	got, _ := r.NormalizeGoplsCmd(t, "imports", filename)
-	want := string(r.data.Golden(t, "goimports", filename, func() ([]byte, error) {
-		return []byte(got), nil
-	}))
-	if want != got {
-		unified := diff.Unified("want", "got", want, got)
-		t.Errorf("imports failed for %s, expected:\n%s", filename, unified)
-	}
-}
diff --git a/gopls/internal/lsp/cmd/test/links.go b/gopls/internal/lsp/cmd/test/links.go
deleted file mode 100644
index a9616ee..0000000
--- a/gopls/internal/lsp/cmd/test/links.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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
-
-import (
-	"encoding/json"
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/lsp/protocol"
-	"golang.org/x/tools/gopls/internal/lsp/tests"
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
-	m, err := r.data.Mapper(uri)
-	if err != nil {
-		t.Fatal(err)
-	}
-	out, _ := r.NormalizeGoplsCmd(t, "links", "-json", uri.Filename())
-	var got []protocol.DocumentLink
-	err = json.Unmarshal([]byte(out), &got)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if diff := tests.DiffLinks(m, wantLinks, got); diff != "" {
-		t.Error(diff)
-	}
-}
diff --git a/gopls/internal/lsp/cmd/test/prepare_rename.go b/gopls/internal/lsp/cmd/test/prepare_rename.go
deleted file mode 100644
index b247388..0000000
--- a/gopls/internal/lsp/cmd/test/prepare_rename.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// 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
-
-import (
-	"fmt"
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/lsp/cmd"
-	"golang.org/x/tools/gopls/internal/lsp/source"
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.PrepareItem) {
-	m, err := r.data.Mapper(src.URI())
-	if err != nil {
-		t.Errorf("prepare_rename failed: %v", err)
-	}
-
-	var (
-		target         = fmt.Sprintf("%v", src)
-		args           = []string{"prepare_rename", target}
-		stdOut, stdErr = r.NormalizeGoplsCmd(t, args...)
-		expect         string
-	)
-
-	if want.Text == "" {
-		if stdErr != "" && stdErr != cmd.ErrInvalidRenamePosition.Error() {
-			t.Errorf("prepare_rename failed for %s,\nexpected:\n`%v`\ngot:\n`%v`", target, expect, stdErr)
-		}
-		return
-	}
-
-	ws, err := m.RangeSpan(want.Range)
-	if err != nil {
-		t.Errorf("prepare_rename failed: %v", err)
-	}
-
-	expect = r.Normalize(fmt.Sprintln(ws))
-	if expect != stdOut {
-		t.Errorf("prepare_rename failed for %s expected:\n`%s`\ngot:\n`%s`\n", target, expect, stdOut)
-	}
-}
diff --git a/gopls/internal/lsp/cmd/test/references.go b/gopls/internal/lsp/cmd/test/references.go
deleted file mode 100644
index 4b26f98..0000000
--- a/gopls/internal/lsp/cmd/test/references.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// 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
-
-import (
-	"fmt"
-	"sort"
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-func (r *runner) References(t *testing.T, spn span.Span, itemList []span.Span) {
-	for _, includeDeclaration := range []bool{true, false} {
-		t.Run(fmt.Sprintf("refs-declaration-%v", includeDeclaration), func(t *testing.T) {
-			var itemStrings []string
-			for i, s := range itemList {
-				// We don't want the first result if we aren't including the declaration.
-				if i == 0 && !includeDeclaration {
-					continue
-				}
-				itemStrings = append(itemStrings, fmt.Sprint(s))
-			}
-			sort.Strings(itemStrings)
-			var expect string
-			for _, s := range itemStrings {
-				expect += s + "\n"
-			}
-			expect = r.Normalize(expect)
-
-			uri := spn.URI()
-			filename := uri.Filename()
-			target := filename + fmt.Sprintf(":%v:%v", spn.Start().Line(), spn.Start().Column())
-			args := []string{"references"}
-			if includeDeclaration {
-				args = append(args, "-d")
-			}
-			args = append(args, target)
-			got, stderr := r.NormalizeGoplsCmd(t, args...)
-			if stderr != "" {
-				t.Errorf("references failed for %s: %s", target, stderr)
-			} else if expect != got {
-				// TODO(adonovan): print the query, the expectations, and
-				// the actual results clearly. Factor in common with the
-				// other two implementations of runner.References.
-				t.Errorf("references failed for %s expected:\n%s\ngot:\n%s", target, expect, got)
-			}
-		})
-	}
-}
diff --git a/gopls/internal/lsp/cmd/test/rename.go b/gopls/internal/lsp/cmd/test/rename.go
deleted file mode 100644
index a9eb31e..0000000
--- a/gopls/internal/lsp/cmd/test/rename.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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
-
-import (
-	"fmt"
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/lsp/tests/compare"
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-func (r *runner) Rename(t *testing.T, spn span.Span, newText string) {
-	filename := spn.URI().Filename()
-	goldenTag := newText + "-rename"
-	loc := fmt.Sprintf("%v", spn)
-	got, err := r.NormalizeGoplsCmd(t, "rename", loc, newText)
-	got += err
-	want := string(r.data.Golden(t, goldenTag, filename, func() ([]byte, error) {
-		return []byte(got), nil
-	}))
-	if diff := compare.Text(want, got); diff != "" {
-		t.Errorf("rename failed with %v %v (-want +got):\n%s", loc, newText, diff)
-	}
-	// now check we can build a valid unified diff
-	unified, _ := r.NormalizeGoplsCmd(t, "rename", "-d", loc, newText)
-	checkUnified(t, filename, want, unified)
-}
diff --git a/gopls/internal/lsp/cmd/test/semanticdriver.go b/gopls/internal/lsp/cmd/test/semanticdriver.go
deleted file mode 100644
index 069dd64..0000000
--- a/gopls/internal/lsp/cmd/test/semanticdriver.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2020 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
-
-import (
-	"strings"
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-func (r *runner) SemanticTokens(t *testing.T, spn span.Span) {
-	uri := spn.URI()
-	filename := uri.Filename()
-	got, stderr := r.NormalizeGoplsCmd(t, "semtok", filename)
-	if stderr != "" {
-		t.Fatalf("%s: %q", filename, stderr)
-	}
-	want := string(r.data.Golden(t, "semantic", filename, func() ([]byte, error) {
-		return []byte(got), nil
-	}))
-	if want != got {
-		lwant := strings.Split(want, "\n")
-		lgot := strings.Split(got, "\n")
-		t.Errorf("want(%d-%d) != got(%d-%d) for %s", len(want), len(lwant), len(got), len(lgot), r.Normalize(filename))
-		for i := 0; i < len(lwant) && i < len(lgot); i++ {
-			if lwant[i] != lgot[i] {
-				// This is the line number in the golden file.
-				// It is one larger than the line number in the source file.
-				t.Errorf("line %d:\nwant%q\ngot %q\n", i+2, lwant[i], lgot[i])
-			}
-		}
-	}
-}
diff --git a/gopls/internal/lsp/cmd/test/signature.go b/gopls/internal/lsp/cmd/test/signature.go
deleted file mode 100644
index 40669e8..0000000
--- a/gopls/internal/lsp/cmd/test/signature.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// 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
-
-import (
-	"fmt"
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/lsp/protocol"
-	"golang.org/x/tools/gopls/internal/lsp/tests"
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-func (r *runner) SignatureHelp(t *testing.T, spn span.Span, want *protocol.SignatureHelp) {
-	uri := spn.URI()
-	filename := uri.Filename()
-	target := filename + fmt.Sprintf(":%v:%v", spn.Start().Line(), spn.Start().Column())
-	got, _ := r.NormalizeGoplsCmd(t, "signature", target)
-	if want == nil {
-		if got != "" {
-			t.Fatalf("want nil, but got %s", got)
-		}
-		return
-	}
-	goldenTag := want.Signatures[0].Label + "-signature"
-	expect := string(r.data.Golden(t, goldenTag, filename, func() ([]byte, error) {
-		return []byte(got), nil
-	}))
-	if tests.NormalizeAny(expect) != tests.NormalizeAny(got) {
-		t.Errorf("signature failed for %s expected:\n%q\ngot:\n%q'", filename, expect, got)
-	}
-}
diff --git a/gopls/internal/lsp/cmd/test/suggested_fix.go b/gopls/internal/lsp/cmd/test/suggested_fix.go
deleted file mode 100644
index 1e61fe9..0000000
--- a/gopls/internal/lsp/cmd/test/suggested_fix.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// 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
-
-import (
-	"fmt"
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/lsp/tests"
-	"golang.org/x/tools/gopls/internal/lsp/tests/compare"
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-func (r *runner) SuggestedFix(t *testing.T, spn span.Span, suggestedFixes []tests.SuggestedFix, expectedActions int) {
-	uri := spn.URI()
-	filename := uri.Filename()
-	args := []string{"fix", "-a", fmt.Sprintf("%s", spn)}
-	var actionKinds []string
-	for _, sf := range suggestedFixes {
-		if sf.ActionKind == "refactor.rewrite" {
-			t.Skip("refactor.rewrite is not yet supported on the command line")
-		}
-		actionKinds = append(actionKinds, sf.ActionKind)
-	}
-	args = append(args, actionKinds...)
-	got, stderr := r.NormalizeGoplsCmd(t, args...)
-	if stderr == "ExecuteCommand is not yet supported on the command line" {
-		return // don't skip to keep the summary counts correct
-	}
-	want := string(r.data.Golden(t, "suggestedfix_"+tests.SpanName(spn), filename, func() ([]byte, error) {
-		return []byte(got), nil
-	}))
-	if want != got {
-		t.Errorf("suggested fixes failed for %s:\n%s", filename, compare.Text(want, got))
-	}
-}
diff --git a/gopls/internal/lsp/cmd/test/symbols.go b/gopls/internal/lsp/cmd/test/symbols.go
deleted file mode 100644
index aaf3725..0000000
--- a/gopls/internal/lsp/cmd/test/symbols.go
+++ /dev/null
@@ -1,24 +0,0 @@
-// 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
-
-import (
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/lsp/protocol"
-	"golang.org/x/tools/gopls/internal/lsp/tests/compare"
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) {
-	filename := uri.Filename()
-	got, _ := r.NormalizeGoplsCmd(t, "symbols", filename)
-	expect := string(r.data.Golden(t, "symbols", filename, func() ([]byte, error) {
-		return []byte(got), nil
-	}))
-	if diff := compare.Text(expect, got); diff != "" {
-		t.Errorf("symbols differ from expected:\n%s", diff)
-	}
-}
diff --git a/gopls/internal/lsp/cmd/test/workspace_symbol.go b/gopls/internal/lsp/cmd/test/workspace_symbol.go
deleted file mode 100644
index 40c2c65..0000000
--- a/gopls/internal/lsp/cmd/test/workspace_symbol.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2020 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
-
-import (
-	"fmt"
-	"path/filepath"
-	"sort"
-	"strings"
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/lsp/source"
-	"golang.org/x/tools/gopls/internal/lsp/tests"
-	"golang.org/x/tools/gopls/internal/lsp/tests/compare"
-	"golang.org/x/tools/gopls/internal/span"
-)
-
-func (r *runner) WorkspaceSymbols(t *testing.T, uri span.URI, query string, typ tests.WorkspaceSymbolsTestType) {
-	var matcher string
-	switch typ {
-	case tests.WorkspaceSymbolsFuzzy:
-		matcher = "fuzzy"
-	case tests.WorkspaceSymbolsCaseSensitive:
-		matcher = "caseSensitive"
-	case tests.WorkspaceSymbolsDefault:
-		matcher = "caseInsensitive"
-	}
-	r.runWorkspaceSymbols(t, uri, matcher, query)
-}
-
-func (r *runner) runWorkspaceSymbols(t *testing.T, uri span.URI, matcher, query string) {
-	t.Helper()
-
-	out, _ := r.runGoplsCmd(t, "workspace_symbol", "-matcher", matcher, query)
-	var filtered []string
-	dir := filepath.Dir(uri.Filename())
-	for _, line := range strings.Split(out, "\n") {
-		if source.InDir(dir, line) {
-			filtered = append(filtered, filepath.ToSlash(line))
-		}
-	}
-	sort.Strings(filtered)
-	got := r.Normalize(strings.Join(filtered, "\n") + "\n")
-
-	expect := string(r.data.Golden(t, fmt.Sprintf("workspace_symbol-%s-%s", strings.ToLower(string(matcher)), query), uri.Filename(), func() ([]byte, error) {
-		return []byte(got), nil
-	}))
-
-	if expect != got {
-		t.Errorf("workspace_symbol failed for %s:\n%s", query, compare.Text(expect, got))
-	}
-}
diff --git a/gopls/internal/lsp/testdata/folding/a.go.golden b/gopls/internal/lsp/testdata/folding/a.go.golden
index 23befa7..b04ca4d 100644
--- a/gopls/internal/lsp/testdata/folding/a.go.golden
+++ b/gopls/internal/lsp/testdata/folding/a.go.golden
@@ -246,43 +246,6 @@
 is not indented`
 }
 
--- foldingRange-cmd --
-3:9-6:1
-10:22-11:33
-12:10-12:10
-12:20-75:1
-14:10-25:2
-15:12-20:4
-16:12-18:3
-17:16-17:22
-18:11-20:3
-19:16-19:23
-21:13-22:23
-22:15-22:22
-23:10-24:25
-24:15-24:24
-26:24-28:12
-30:24-33:33
-34:12-38:2
-39:16-41:2
-42:21-46:2
-47:17-51:2
-52:8-56:2
-57:15-57:24
-57:32-57:41
-58:10-69:2
-59:18-64:4
-60:11-62:3
-61:16-61:29
-62:11-64:3
-63:16-63:30
-65:11-66:19
-66:15-66:18
-67:10-68:25
-68:15-68:24
-70:32-71:31
-72:9-74:17
-
 -- foldingRange-comment-0 --
 package folding //@fold("package")
 
diff --git a/gopls/internal/lsp/testdata/folding/bad.go.golden b/gopls/internal/lsp/testdata/folding/bad.go.golden
index 6db6982..ab274f7 100644
--- a/gopls/internal/lsp/testdata/folding/bad.go.golden
+++ b/gopls/internal/lsp/testdata/folding/bad.go.golden
@@ -44,16 +44,6 @@
 	return
 }
 
--- foldingRange-cmd --
-3:9-5:1
-7:9-8:9
-11:13-11:13
-11:23-18:1
-12:8-15:2
-14:15-14:21
-15:10-16:24
-16:15-16:22
-
 -- foldingRange-imports-0 --
 package folding //@fold("package")
 
diff --git a/gopls/internal/lsp/tests/tests.go b/gopls/internal/lsp/tests/tests.go
index 58bb6a0..eaa2bb9 100644
--- a/gopls/internal/lsp/tests/tests.go
+++ b/gopls/internal/lsp/tests/tests.go
@@ -143,16 +143,12 @@
 	mappers   map[span.URI]*protocol.Mapper
 }
 
-// The Tests interface abstracts a set of implementations of marker
+// The Tests interface abstracts the LSP-based implementation of the marker
 // test operators (such as @hover) appearing in files beneath ../testdata/.
 //
-// There are two implementations:
-// - *runner in ../cmd/test/check.go, which runs the command-line tool (e.g. "gopls hover")
-// - *runner in ../lsp_test.go, which makes LSP requests (textDocument/hover) to a gopls server.
-//
-// Not all implementations implement all methods.
-//
 // TODO(adonovan): reduce duplication; see https://github.com/golang/go/issues/54845.
+// There is only one implementation (*runner in ../lsp_test.go), so
+// we can abolish the interface now.
 type Tests interface {
 	CallHierarchy(*testing.T, span.Span, *CallHierarchyResult)
 	CodeLens(*testing.T, span.URI, []protocol.CodeLens)
diff --git a/gopls/test/gopls_test.go b/gopls/test/gopls_test.go
deleted file mode 100644
index efa998c..0000000
--- a/gopls/test/gopls_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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 (
-	"os"
-	"testing"
-
-	"golang.org/x/tools/gopls/internal/hooks"
-	cmdtest "golang.org/x/tools/gopls/internal/lsp/cmd/test"
-	"golang.org/x/tools/gopls/internal/lsp/source"
-	"golang.org/x/tools/gopls/internal/lsp/tests"
-	"golang.org/x/tools/internal/bug"
-	"golang.org/x/tools/internal/event"
-	"golang.org/x/tools/internal/testenv"
-)
-
-func TestMain(m *testing.M) {
-	bug.PanicOnBugs = true
-	testenv.ExitIfSmallMachine()
-
-	// Set the global exporter to nil so that we don't log to stderr. This avoids
-	// a lot of misleading noise in test output.
-	//
-	// See also ../internal/lsp/lsp_test.go.
-	event.SetExporter(nil)
-
-	os.Exit(m.Run())
-}
-
-func TestCommandLine(t *testing.T) {
-	cmdtest.TestCommandLine(t, "../internal/lsp/testdata", commandLineOptions)
-}
-
-func commandLineOptions(options *source.Options) {
-	options.Staticcheck = true
-	options.GoDiff = false // workaround for golang/go#57290   TODO(pjw): fix
-	tests.DefaultOptions(options)
-	hooks.Options(options)
-}