-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
x/tools/gopls: integrate test discovery #59445
Comments
@gopherbot, please add label FeatureRequest |
See also golang/vscode-go#2719. We should do this. The actual analysis is not that hard: there are examples of recognizing test functions in the tests analyzer and subtests in the loopclosure analyzer. We already have a |
@findleyr from an information perspective that would be sufficient if the code lens messages include some additional information (listed below). Otherwise we'd still need document symbols or some other mechanism for extracting that. However it seems like that might be problematic from a usability perspective. A user may wish to use the test explorer exclusively and disable the test code lens - if the user disables test code lenses does that tell gopls to stop sending those messages? Off the top of my head I think these are the components we need:
With respect to (1) and (2), testing for a A big motivation for moving (1) into gopls is to address performance issues on large projects (see golang/vscode-go#2504). The current implementation recursively walks the filesystem, which does not perform well on large projects (and likely performs badly if the project is on a remote filesystem). I realize that gopls will also have to walk the filesystem, but it must already be doing that (to supply type information) and I theorize that using the build metadata gopls collects will perform much better than the current JS implementation. |
I am more than willing to work on this myself but I don't know when I'll have the time. |
@firelizzard18 thanks for the details. It sounds like it may be cleanest to have a custom command, as code lenses in general require type information and we may be able to implement this query without fully type-checking. This command may also have a life-cycle that is different from code lenses, typically. If we do choose to implement this with type information, we can extract test information as part of our package post-processing, and serialize them in our file cache, so that they will be quickly available at startup. This aspect of the change would be tricker for an external contributor. I have slated this for [email protected], as this keeps cropping up. |
@findleyr I think we can separate the major areas of improvement into three groups:
I think (1) and (2) are orthogonal and could be implemented separately, if that is more convenient. Whether (3) is dependent on (2) depends on the implementation. (3) could be implemented as an improvement to the normal test discovery; alternatively it could be implemented as a second pass. Static subtest discovery (3) is the part I find most technically interesting. To be clear I know it is impossible to statically discover all sub tests (the halting problem) so IMO the target should be "statically discover subtests in cases where it is feasible to do so" as a companion to dynamic subtest discovery (that is, analyzing test output). I have functionally zero knowledge of how gopls works internally, so maybe I should wait until the team has implemented some form of static subtest discovery and then I can provide contributions building on that. |
Can this improvement be done in away that it considers (or at least leaves the option open for) an option to infer relevant packages from the user's current open workspace state? As we were discussing on golang/vscode-go#2504, to support a large monorepo environment it becomes not just an issue of indexing speed, but also of relevance. When many teams use one monorepo, "Show everything" (the current behavior) becomes too broad and shows thousands of irrelevant packages, and switching to nested mode becomes very deep. On the other hand, "Show only the package of the file I have open" may be too narrow, but is consistent, intuitive, and doesn't require the user to maintain any separate config with relevant paths. Perhaps the solution could be something like limiting the display to packages within "x" levels of the current directory. |
Detecting tests from the syntax alone is possible, but there's a lot of potential for doing better by using typed syntax to infer subtests, at least in the simple cases. It would make most sense architecturally for test inference to be a step executed just after type checking, like references/methodsets/xrefs, to populate an index of tests. This would avoid the potential for requests such as "run all the tests in this module" to entail an expensive operation such as "first type-check all the packages in this module, sequentially". An index-based approach would make this operation parallel, fast, and cached, just like a references query. |
@adonovan I would be happy to work on subtest discovery once the initial implementation is in place. That seems like a rather interesting problem. I’m also interested in contributing to the initial implementation but it sounds like that might be challenging due to indexing/caching/etc. |
Thanks. I don't think the gopls aspects are that tricky: just follow the model used by methodsets and xrefs, which are both analyses that run immediately after type checking and populate a persistent index. The tricky part is the meat of it--inferring names and locations of tests given typed syntax--which is as it should be. |
Change https://go.dev/cl/548675 mentions this issue: |
@adonovan @findleyr How's this for a proof of concept? Issues:
|
Thanks @firelizzard18 for the CL! I will take a look at it tomorrow. |
Note to self
|
@hyangah and I discussed the protocol and agreed that the API below should be sufficient for a starting point. The Packages method below would be a new command (see command.Interface in gopls), and its implementation would require type checking (similar to https://go.dev/cl/548675). // The Packages command returns information about the set of Go packages
// associated with the specified files or directories, plus additional information such
// as the names of any tests within them.
func Packages(PackagesParams) (PackagesResult, error)
type PackagesParams struct {
File []protocol.URI // file or directory (directories are recursively enumerated)
}
type PackagesResult struct {
Packages []Package
}
// Package describes a directory that is a Go package (not an empty parent).
type Package struct {
Path string // Go package path
Dir protocol.URI // package directory
Root protocol.URI // root of view folder (an enclosing directory)
// TestFiles describes each file that defines a test, and the names of those tests.
TestFiles []TestFile // may be empty
}
type TestFile struct {
File protocol.URI // a *_test.go file
Tests []Test
}
// Test represents a tree of test suites and test cases,
// preserving the structure of Go subtests, where possible.
type Test struct {
// Name is the complete name of the test (Test, Benchmark, Example, or Fuzz)
// as it appears in the output of go test.
// The server may attempt to infer names of subtests by static
// analysis; if so, it should aim to simulate the actual computed
// name of the test, including any disambiguating suffix such as "#01".
// Clients should quote this name using "^" + regexp.QuoteMeta(Name) + "$"
// before passing it to go test -test.run=...
Name string // e.g. "TestToplevel/Inner"
// Range enclosing the entire test. This is used to place
// the gutter marker and group tests based on location.
// For subtests whose test names can be determined statically,
// this can be either t.Run or the test data table
// for table-driven setup.
Range protocol.Range
// Nested subtests.
// Note that this is not an exhaustive list.
// For example, if subtests are dynamically generated or
// their names cannot be statically determined, those tests
// may not be included in this list.
Subtests []Test
} |
@adonovan @hyangah I think recursion should be optional. There are scenarios where recursion is helpful, and others where it is not. Especially for large workspaces (#59445 (comment), golang/vscode-go#2504), the user may not want all tests to appear, even if gopls's performance is acceptable. The scenarios I see are:
It might be better to use modules as the roots, instead of workspace folders. That would require some "find all Go modules given these workspace folders" logic, which would be easier with gopls than with JavaScript. But it might also make sense just to use workspace folders, since that will mirror the folder structure. |
Thanks @firelizzard18 Here is a little bit of the background behind the API sketch @adonovan shared. The upcoming zero-config gopls work (#57979) will introduce a significant change in how gopls models a workspace. Basically, gopls won't create a view until users open certain go-related files (go.* files, *.go files, go template files). If a go-related file opens, then gopls attempts to find the appropriate workspace boundary for the file. That can be a module root, or a workspace root, or a gopath directory or the current directory. It solves many scalability and performance issues. A similar model had been used in other editors and we think the experience will be better in a large workspace with multiple modules, or a workspace with go code in nested modules. With this new model, we consider to adjust vscode-go test explorer's UX a bit. Gopls doesn't analyze modules or packages until the corresponding views about them are created. Test explorer needs to honor that new UX too, instead of attempting to eagerly load all modules and packages in the workspace. (Otherwise, either gopls won't return anything, or trigger creating views unnecessarily and cancelling out the benefit of zero config design). We originally attempted to create package trees with multiple view roots. But we are afraid that leaks too much of the gopls's internal data structure. The API in #59445 (comment) instead attempts to simplify the problem by returning a flat package list. If the client wants hierarchical presentation, it can still reconstruct the hierarchy based on the package paths and the package directory paths & root (the view folder). The three cases you listed were also discussed, and we think a client can implement the three cases with the API.
For subtests, we try to preserve the hierarchies found from the source code. However, as you also pointed out, "/" in the test name is not escaped, so reconstructing the hierarchy for dynamically discovered subtests reliably is still difficult. I think for now, vscode-go test explorer can place all the dynamically discovered subtests under the test function. I was originally afraid of the message sizes (and client-side overhead). But from a quick analysis of some large repos, the volume of returned results won't be too bad compared to other LSP messages. I hope we can try with this minimal API, and then if that becomes an issue we can consider to include some filters for optimization. |
@hyangah The API sketch @adonovan shared looks good to me. I only have two very minor reservations:
|
For visibility, here's a WIP CL for integrating this into vscode-go: https://go-review.googlesource.com/c/vscode-go/+/550555. It's the minimum viable change to switch to using gopls for test discovery. I plan to do more, but the full integration will require a lot of refactoring so I thought it would be best to do the basic integration in a small CL. |
Thanks for the investigation and prototype @firelizzard18
Currently all of our team members are busy finishing up planned work for 2023. We are planning to work on testing UX in 2024 H1 (as part of the planning discussion we revived this thread). |
@hyangah I agree that subtests should be displayed nested - I was thinking purely about the backend implementation. vscode-go needs a function for "find the parent of this subtest given the subtest name" for dynamic subtests. So vscode-go could easily handle receiving a flattened list of subtests from gopls; it would just look for a slash in the name and then run it through that function. From a backend implementation POV, I am thinking flattening the
I didn't realize that was an option, though I guess I just assumed I had to use
This has been a continual irritation to me and is definitely something I intend to eliminate. The existing code is supposed to realize that the dynamically discovered test matches the static one, but that obviously isn't working.
I'll ignore it for now; we can always add a new field later if it makes sense. |
Sorry for the delay. As you noticed our team was busy closing gopls v0.15.x bugs and handling regressions related to the latest vscode release. I think we need more time to think about the problems 1 and 2 in your problem description #59445 (comment) (affected by gopls's zero config change, and it's very vscode-specific). However, the problem 3 is concrete enough. I will get back to you early next week. |
@firelizzard18 @adonovan @findleyr I have the updated API sketch in cl/579438
Testify case is a bit more complicated. I hope we can eliminate the need for use of Let's discuss details in cl/579438. |
Change https://go.dev/cl/579438 mentions this issue: |
This new Packages command provides information about go packages known to gopls. The Modules command reports modules known to gopls and found in the directory. For golang/go#59445 Change-Id: Ief0ac4984efb4a0e7f0fee8d8d55dae35eb00375 Reviewed-on: https://go-review.googlesource.com/c/tools/+/579438 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Hyang-Ah Hana Kim <[email protected]> Reviewed-by: Ethan Reesor <[email protected]> Reviewed-by: Robert Findley <[email protected]>
Change https://go.dev/cl/598815 mentions this issue: |
Change https://go.dev/cl/600335 mentions this issue: |
Change https://go.dev/cl/600355 mentions this issue: |
Change https://go.dev/cl/601015 mentions this issue: |
Implements the `gopls.modules` command. Updates golang/go#59445 Change-Id: Ifb39e0ba79be688af81ddc6389570011b4d441cc Reviewed-on: https://go-review.googlesource.com/c/tools/+/598815 Reviewed-by: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]> Auto-Submit: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
Implements the `gopls.packages` command. Updates golang/go#59445 Change-Id: Ia72a971b7aac9baa964c8cf5eee8b332b3125fa4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/600355 Reviewed-by: Cherry Mui <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
I would like to migrate the test discovery services of vscode-go into gopls so they can be implemented in Go and utilize the rich type information provided by the
go/*
packages instead of the current JavaScript implementation which relies on recursive directory walks and analyzing document symbols. Moving test discovery into gopls should improve performance and would bake it far easier to implement improvements such as detecting subtests in cases where that can be done deterministically.CC @hyangah @suzmue
Related: golang/vscode-go#2445, golang/vscode-go#1602
The text was updated successfully, but these errors were encountered: