internal/lsp/regtest: add a workspace symbols benchmark

It's pretty easy to add an LSP benchmark using the regtests, provided we
run the benchmark ourselves from inside the runner. Do this for
workspace symbols to start, though we should add several of these.

Also fix some error messages when setting options.

Change-Id: Iab134018edec8837e90a0a926ec2e73addf95bb3
Reviewed-on: https://go-review.googlesource.com/c/tools/+/250798
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/fake/editor.go b/internal/lsp/fake/editor.go
index a5dd479..e97c49b 100644
--- a/internal/lsp/fake/editor.go
+++ b/internal/lsp/fake/editor.go
@@ -67,7 +67,7 @@
 
 	// SymbolMatcher is the config associated with the "symbolMatcher" gopls
 	// config option.
-	SymbolMatcher *string
+	SymbolMatcher, SymbolStyle *string
 
 	// LimitWorkspaceScope is true if the user does not want to expand their
 	// workspace scope to the entire module.
@@ -185,6 +185,10 @@
 		config["symbolMatcher"] = *e.Config.SymbolMatcher
 	}
 
+	if e.Config.SymbolStyle != nil {
+		config["symbolStyle"] = *e.Config.SymbolStyle
+	}
+
 	return config
 }
 
diff --git a/internal/lsp/regtest/bench_test.go b/internal/lsp/regtest/bench_test.go
new file mode 100644
index 0000000..d91c424
--- /dev/null
+++ b/internal/lsp/regtest/bench_test.go
@@ -0,0 +1,71 @@
+// 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 regtest
+
+import (
+	"flag"
+	"fmt"
+	"testing"
+
+	"golang.org/x/tools/internal/lsp/fake"
+	"golang.org/x/tools/internal/lsp/protocol"
+)
+
+var symbolBench = struct {
+	workdir, query, matcher, style string
+	printResults                   bool
+}{}
+
+func init() {
+	flag.StringVar(&symbolBench.workdir, "symbol_workdir", "", "if set, run symbol benchmark in this directory")
+	flag.StringVar(&symbolBench.query, "symbol_query", "test", "symbol query to use in benchmark")
+	flag.StringVar(&symbolBench.matcher, "symbol_matcher", "", "symbol matcher to use in benchmark")
+	flag.StringVar(&symbolBench.style, "symbol_style", "", "symbol style to use in benchmark")
+	flag.BoolVar(&symbolBench.printResults, "symbol_print_results", false, "symbol style to use in benchmark")
+}
+
+func TestBenchmarkSymbols(t *testing.T) {
+	if symbolBench.workdir == "" {
+		t.Skip("-symbol_workdir not configured")
+	}
+	opts := stressTestOptions(symbolBench.workdir)
+	conf := fake.EditorConfig{}
+	if symbolBench.matcher != "" {
+		conf.SymbolMatcher = &symbolBench.matcher
+	}
+	if symbolBench.style != "" {
+		conf.SymbolStyle = &symbolBench.style
+	}
+	opts = append(opts, WithEditorConfig(conf))
+	withOptions(opts...).run(t, "", func(t *testing.T, env *Env) {
+		// We can't Await in this test, since we have disabled hooks. Instead, run
+		// one symbol request to completion to ensure all necessary cache entries
+		// are populated.
+		results, err := env.Editor.Server.Symbol(env.Ctx, &protocol.WorkspaceSymbolParams{
+			Query: symbolBench.query,
+		})
+		if err != nil {
+			t.Fatal(err)
+		}
+		if symbolBench.printResults {
+			fmt.Println("Results:")
+			for i := 0; i < len(results); i++ {
+				fmt.Printf("\t%d. %s\n", i, results[i].Name)
+			}
+		}
+		b := testing.Benchmark(func(b *testing.B) {
+			for i := 0; i < b.N; i++ {
+				if _, err := env.Editor.Server.Symbol(env.Ctx, &protocol.WorkspaceSymbolParams{
+					Query: symbolBench.query,
+				}); err != nil {
+					t.Fatal(err)
+				}
+			}
+		})
+		fmt.Println("Benchmark stats:")
+		fmt.Println(b.String())
+		fmt.Println(b.MemString())
+	})
+}
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index c6c3973..06d4ea0 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -47,7 +47,6 @@
 	"golang.org/x/tools/internal/lsp/analysis/simplifyslice"
 	"golang.org/x/tools/internal/lsp/analysis/undeclaredname"
 	"golang.org/x/tools/internal/lsp/analysis/unusedparams"
-	"golang.org/x/tools/internal/lsp/debug/tag"
 	"golang.org/x/tools/internal/lsp/diff"
 	"golang.org/x/tools/internal/lsp/diff/myers"
 	"golang.org/x/tools/internal/lsp/protocol"
@@ -494,8 +493,10 @@
 			o.SymbolStyle = FullyQualifiedSymbols
 		case "dynamic":
 			o.SymbolStyle = DynamicSymbols
-		default:
+		case "package":
 			o.SymbolStyle = PackageQualifiedSymbols
+		default:
+			result.errorf("Unsupported symbol style %q", style)
 		}
 
 	case "hoverKind":
@@ -515,7 +516,7 @@
 		case "Structured":
 			o.HoverKind = Structured
 		default:
-			result.errorf("Unsupported hover kind", tag.HoverKind.Of(hoverKind))
+			result.errorf("Unsupported hover kind %q", hoverKind)
 		}
 
 	case "linkTarget":