internal/lsp: add "run file benchmarks" code lens
This CL adds a code lens to run all benchmarks in a file. Additionally,
it updates the test command handler to better support both tests and
benchmarks.
Updates golang/go#36787
Change-Id: I6e90460f7d97607f96c263be0754537764bd0052
Reviewed-on: https://go-review.googlesource.com/c/tools/+/246017
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/internal/lsp/command.go b/internal/lsp/command.go
index 96bdd6e..537f94d 100644
--- a/internal/lsp/command.go
+++ b/internal/lsp/command.go
@@ -9,7 +9,6 @@
"fmt"
"io"
"path"
- "strings"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/debug/tag"
@@ -97,9 +96,8 @@
switch command {
case source.CommandTest:
var uri protocol.DocumentURI
- var flag string
- var funcName string
- if err := source.UnmarshalArgs(params.Arguments, &uri, &flag, &funcName); err != nil {
+ var tests, benchmarks []string
+ if err := source.UnmarshalArgs(params.Arguments, &uri, &tests, &benchmarks); err != nil {
return nil, err
}
snapshot, _, ok, release, err := s.beginFileRequest(ctx, uri, source.UnknownKind)
@@ -107,7 +105,7 @@
if !ok {
return nil, err
}
- go s.runTest(ctx, snapshot, []string{flag, funcName}, params.WorkDoneToken)
+ go s.runTests(ctx, snapshot, uri, params.WorkDoneToken, tests, benchmarks)
case source.CommandGenerate:
var uri protocol.DocumentURI
var recursive bool
@@ -193,26 +191,74 @@
return snapshot.RunGoCommandDirect(ctx, verb, args)
}
-func (s *Server) runTest(ctx context.Context, snapshot source.Snapshot, args []string, token protocol.ProgressToken) error {
+func (s *Server) runTests(ctx context.Context, snapshot source.Snapshot, uri protocol.DocumentURI, token protocol.ProgressToken, tests, benchmarks []string) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
+ pkgs, err := snapshot.PackagesForFile(ctx, uri.SpanURI())
+ if err != nil {
+ return err
+ }
+ if len(pkgs) == 0 {
+ return fmt.Errorf("package could not be found for file: %s", uri.SpanURI().Filename())
+ }
+ pkgPath := pkgs[0].PkgPath()
+
+ // create output
ew := &eventWriter{ctx: ctx, operation: "test"}
- msg := fmt.Sprintf("running `go test %s`", strings.Join(args, " "))
- wc := s.progress.newWriter(ctx, "test", msg, msg, token, cancel)
+ var title string
+ if len(tests) > 0 && len(benchmarks) > 0 {
+ title = "tests and benchmarks"
+ } else if len(tests) > 0 {
+ title = "tests"
+ } else if len(benchmarks) > 0 {
+ title = "benchmarks"
+ } else {
+ return errors.New("No functions were provided")
+ }
+ msg := fmt.Sprintf("Running %s...", title)
+ wc := s.progress.newWriter(ctx, title, msg, msg, token, cancel)
defer wc.Close()
- messageType := protocol.Info
- message := "test passed"
stderr := io.MultiWriter(ew, wc)
- if err := snapshot.RunGoCommandPiped(ctx, "test", args, ew, stderr); err != nil {
- if errors.Is(err, context.Canceled) {
- return err
+ // run `go test -run Func` on each test
+ var failedTests int
+ for _, funcName := range tests {
+ args := []string{pkgPath, "-run", fmt.Sprintf("^%s$", funcName)}
+ if err := snapshot.RunGoCommandPiped(ctx, "test", args, ew, stderr); err != nil {
+ if errors.Is(err, context.Canceled) {
+ return err
+ }
+ failedTests++
}
- messageType = protocol.Error
- message = "test failed"
}
+
+ // run `go test -run=^$ -bench Func` on each test
+ var failedBenchmarks int
+ for _, funcName := range tests {
+ args := []string{pkgPath, "-run=^$", "-bench", fmt.Sprintf("^%s$", funcName)}
+ if err := snapshot.RunGoCommandPiped(ctx, "test", args, ew, stderr); err != nil {
+ if errors.Is(err, context.Canceled) {
+ return err
+ }
+ failedBenchmarks++
+ }
+ }
+
+ messageType := protocol.Info
+ message := fmt.Sprintf("all %s passed", title)
+ if failedTests > 0 || failedBenchmarks > 0 {
+ messageType = protocol.Error
+ }
+ if failedTests > 0 && failedBenchmarks > 0 {
+ message = fmt.Sprintf("%d / %d tests failed and %d / %d benchmarks failed", failedTests, len(tests), failedBenchmarks, len(benchmarks))
+ } else if failedTests > 0 {
+ message = fmt.Sprintf("%d / %d tests failed", failedTests, len(tests))
+ } else if failedBenchmarks > 0 {
+ message = fmt.Sprintf("%d / %d benchmarks failed", failedBenchmarks, len(benchmarks))
+ }
+
return s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
Type: messageType,
Message: message,
diff --git a/internal/lsp/source/code_lens.go b/internal/lsp/source/code_lens.go
index bc2f2cc..149af3e 100644
--- a/internal/lsp/source/code_lens.go
+++ b/internal/lsp/source/code_lens.go
@@ -57,18 +57,23 @@
if err != nil {
return nil, err
}
+
+ var benchFns []string
for _, d := range pgf.File.Decls {
fn, ok := d.(*ast.FuncDecl)
if !ok {
continue
}
+ if benchmarkRe.MatchString(fn.Name.Name) {
+ benchFns = append(benchFns, fn.Name.Name)
+ }
rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, d.Pos(), d.Pos()).Range()
if err != nil {
return nil, err
}
if matchTestFunc(fn, pkg, testRe, "T") {
- jsonArgs, err := MarshalArgs(fh.URI(), "-run", fn.Name.Name)
+ jsonArgs, err := MarshalArgs(fh.URI(), []string{fn.Name.Name}, nil)
if err != nil {
return nil, err
}
@@ -83,7 +88,7 @@
}
if matchTestFunc(fn, pkg, benchmarkRe, "B") {
- jsonArgs, err := MarshalArgs(fh.URI(), "-bench", fn.Name.Name)
+ jsonArgs, err := MarshalArgs(fh.URI(), nil, []string{fn.Name.Name})
if err != nil {
return nil, err
}
@@ -97,6 +102,23 @@
})
}
}
+ // add a code lens to the top of the file which runs all benchmarks in the file
+ rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
+ if err != nil {
+ return nil, err
+ }
+ args, err := MarshalArgs(fh.URI(), []string{}, benchFns)
+ if err != nil {
+ return nil, err
+ }
+ codeLens = append(codeLens, protocol.CodeLens{
+ Range: rng,
+ Command: protocol.Command{
+ Title: "run file benchmarks",
+ Command: CommandTest.Name,
+ Arguments: args,
+ },
+ })
return codeLens, nil
}
diff --git a/internal/lsp/testdata/lsp/primarymod/codelens/codelens_test.go b/internal/lsp/testdata/lsp/primarymod/codelens/codelens_test.go
index f08c673..f6c6964 100644
--- a/internal/lsp/testdata/lsp/primarymod/codelens/codelens_test.go
+++ b/internal/lsp/testdata/lsp/primarymod/codelens/codelens_test.go
@@ -1,4 +1,4 @@
-package codelens
+package codelens //@codelens("package codelens", "run file benchmarks", "test")
import "testing"
diff --git a/internal/lsp/testdata/lsp/summary.txt.golden b/internal/lsp/testdata/lsp/summary.txt.golden
index 6c7bf5a..a23beb3 100644
--- a/internal/lsp/testdata/lsp/summary.txt.golden
+++ b/internal/lsp/testdata/lsp/summary.txt.golden
@@ -1,6 +1,6 @@
-- summary --
CallHierarchyCount = 1
-CodeLensCount = 4
+CodeLensCount = 5
CompletionsCount = 239
CompletionSnippetCount = 81
UnimportedCompletionsCount = 6