-
Notifications
You must be signed in to change notification settings - Fork 752
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
GoTestResolver: Limit scope of indexing to the package level instead of entire workspace/module #2504
Comments
Thanks for the FR @mnoah1 There is a separate feature request #1739 and I think that matches what you described as an alternative - except that the FR and the [cl/351172](https://go-review.git.corp.google.com/c/vscode-go/+/351172 proposed by @firelizzard18 discusses only on Often times, users work on multiple packages. I am worried that kicking the resolver every time as users navigate/open/close files in different packages can be more complex than it looks. |
Hi @hyangah - I think in the case of a large repo, the exclude option would not be ideal since for most users that work on only a few projects, they'll end up needing to set most of the repo to be excluded which will become tedious to maintain. If we could just avoid walking the entire workspace when looking for more packages to add, it should be relatively close to the current behavior with new packages updating in the list based on users opening files, and staying there if the user leaves to go to another package. So effectively it would build up a list of the user's current working packages. |
Hi @hyangah - any thoughts on this? I have a solution on my fork that seems to work as needed, should I try submitting it as a PR? |
@hyangah - Just wanted to bump this. |
I would like this feature as well. In monorepos you sometimes/often have different projects in different languages. In my case I have C++, Node.js and Python projects in my monorepo. Currently the go test explorer is completely useless for me and I have it disabled using
I can help work on a setting to enable this, but would like to hear from you guys if this is something that would be accepted in a PR. |
I have a working version of this in a fork, I can add what I have so far into a PR as well. It adds a setting, |
@mnoah1 does that mean that you need to open up a I think a better approach would be to simply have a list of excluded directories that the If you would have a project layout like so:
The "go.testExplorer.walk.exclude": [
"my_cpp_project",
"my_nodejs_project",
"my_python_project",
] And |
I see, I think that's a slightly different use case, more like this issue? #1739 The intent with this one is for very large repos, where the current walk logic works fine, but indexes too many packages that aren't relevant at that point in time for the user. So this could limit it to the user's current open packages. |
Added a PR for the limiting indexing to current package. I think it could coexist with an additional setting for directories to exclude entirely from the walk as @greenstatic is suggesting. |
Change https://go.dev/cl/478175 mentions this issue: |
@greenstatic My impression is that the Go team do not have the bandwidth to work on this themselves and would likely accept a PR. As the original developer of the test explorer adapter, I am happy for others to contribute.
That is odd. The resolver implementation does not recursively scan folders so it should not bog down the UI unless the host (VSCode) is doing something I don't expect.
Maybe it's the find packages step that's taking so long for you. Scanning for files and symbols should only happen lazily when you expand those items in the explorer. It's possible enabling package nesting will improve performance, since that might make the package discovery lazy instead of it recursing down all the folder structures. Another possibility would be adding a setting to disable support for non-module workspaces. That should automatically exclude any folder that does not contain a
The test explorer driver is implemented 100% in typescript (except for symbol resolution); gopls settings aren't respected because it doesn't use gopls (except for symbols).
@mnoah1 IMO a more useful setting would be |
@mnoah1 As I said above, the test explorer should not be trying to index the entire workspace. Before implementing your proposal, I'd rather see if my proposal (ignore non-modules) would work for you and/or determine why performance is so bad in your case. Because it's definitely supposed to be lazy. FWIW the setting that nests packages is @greenstatic I responded in detail on #1739 to your |
@firelizzard18 Is it expected to be lazy in loading the list of packages, or just the tests within those packages? It looks like the current behavior is as follows, and while it's lazy to load the test cases, it does attempt to find all packages right away:
So the proposed setting on #2709 could turn off steps 1-3 and keep step 4.
|
@mnoah1 It's been a while so I forgot the details. Originally it would lazily list folders and only scan for files once you expanded those folders. However that lead to it including folders that were not packages, hence the check for a *_test.go file. I think the following would improve your case:
Looking at the code I agree that the go.mod setting wouldn't make a huge difference, since it still scans. Part of the reason I'm not enthusiastic about #2709 is that it modifies the file-did-open behavior as well as effectively disabling the resolver. If all you were doing was disabling the resolver, I'd suggest renaming the setting and then give it my 👍. However the fact that the file-did-open handler would also be triggering the resolver erodes separation of concerns and would make the code harder to follow and maintain. Perhaps that could be addressed via adding the resolve logic in an additional event handler registered by the resolver itself, but I'd rather think more carefully about the overall problem. Disabling the resolver feels like a band-aid and not a true solution. |
I think we could address the separation of concerns issue by moving the conditional logic into the resolver, creating a I also looked into both of the suggestions below, but I think they have some drawbacks specific to very large monorepos:
So I think, to make this experience good for users in large organizations that use this extension, there would ideally be some kind of inference between the user's current work and the tests that appear in the explorer. |
@firelizzard18 @suzmue @hyangah - Just wanted to see if there are any thoughts on the above? |
The solution you implement #2709 is not one I find appealing. It essentially boils down to "index a package if and when I open a file in that package." I believe that it works but it feels inelegant and more of a stop-gap than a true solution. Ultimately I'm not in a position to dis/approve your MR so my opinion only matters in so much as it influences the maintainers. I'm not very satisfied with the test discovery in general. I've mentioned before that I'd like to move test discovery into gopls, though I can't find those comments now. I think a lot of issues with the test explorer could be resolved by doing that. gopls is already indexing the workspace for Go files so I expect it would be relatively simple and quick to produce a list of packages with test files. |
I opened golang/go#59445 to discuss moving test discovery into gopls. |
@mnoah1 I am working on rebuilding the test explorer. For context, please familiarize yourself with gopls'
A couple notes: the implementations are not actually recursive. It's meaningful to talk about these arguments as recursive, but the implementation is already scanning over the entire set of modules/packages loaded by I'm rebuilding the test explorer around these two functions. The basic implementation is to scan the workspace for modules (recursively) and list those, then when the user expands a module it will scan that module for packages (recursively). At that point, Given this new approach, how do you want the test explorer to work in your scenario? Here's what I have so far. |
@firelizzard18 thanks for reaching out, I'll take a look. |
Sorry for the delay, I've taken a look at this and have some ideas. Since I see you already have a spot to set the discovery mode, a couple of options that I think could make sense:
I think this depends heavily on the repo/project structure, in a monorepo setup where there is only one go mod file, this could still be tens of thousands of packages, so may need to be up to the user.
This brings up another point, in how gopls will handle the command to get packages (especially its integration with GOPACKAGESDRIVER). Will it only return packages that are already known to gopls, or is it similar to the existing flow used for code intelligence where it would trigger a GOPACKAGESDRIVER request? As an example, if we have this scenario:
Currently if the user opens sample_abc_test.go, gopls will request package info for sample_abc_test.go and become aware of packages abc, foo, bar. But it won't know about packages def, aaa, bbb unless a file in package def (or something else with those packages in their dependency graph) is opened. So there may need to be some kind of reload mechanism here if gopls has loaded more packages since the initial population of test explorer, maybe triggered on browsing a _test.go file that does not yet have a test item. |
The statements below reflect how I have been told to implement these commands. They may not be precisely true but they reflect how gopls's internal APIs behave and are expected to be used and thus are true for all intents and purposes relevant to this issue in so far as I understand gopls.
The current (not merged) implementation of the
gopls is aware of the metadata of imported packages, but I don't think this is relevant. If they do not belong to the same module as abc, I'm almost certain the module they do belong to will not be loaded, thus
Your statement is not necessarily correct with respect to this issue (to the two gopls commands I mentioned). From the perspective of those commands, any packages that are in the same module as def (including but not limited to abc, aaa, and bbb) will be loaded and any packages that are in a different module will not be loaded. |
I added a qualification in bold, which is how I would understand the situation to work when using packagesdriver (and the behavior that we see with other gopls features like type checking and autocomplete). Is that not correct? The packages driver responds with the relevant packages when requested based on go file name, but until a package has been part of at least one response, gopls won't have it stored or be able to return it, regardless of which module it's a part of. |
The current iteration of those commands boils down to: func (s *Server) Modules(...) (...) {
for _, view := range s.session.Views() {
snapshot := view.Snapshot()
defer snapshot.Release()
for _, mod := range view.Modules() {
if want(mod) {
add(mod)
}
}
}
}
func (s *Server) Packages(...) (...) {
for _, view := range s.session.Views() {
snapshot := view.Snapshot()
defer snapshot.Release()
for _, pkg := range view.WorkspacePackages() {
if want(pkg) {
add(pkg, snapshot.Tests(pkg))
}
}
}
} So these commands scan whatever views and packages have already been loaded. Each view is tied to a go.mod or go.work (or equivalent), though there can be multiple views for a go.mod/go.work for different build tag sets. I don't have a clue how it works with GOPACKAGEDRIVER/a non- I'll poke around and see what I can see next time I have gopls setup for debugging. But, and this is purely conjecture, I think it's something like packages loaded from source vs packages loaded from the build cache. As in, all you need for most language features (e.g. autocompletion) is type metadata included in the compiled package. But when you want to open a file and jump to a definition, you need to know source information about the declaration you're jumping to. |
From golang/go#57987, which is about refactoring gopls (so it may not reflect the final state of that effort). Based on that snippet, I think packages within a view are loaded as "syntax packages" and external packages they depend on are loaded as "export packages". At least in the context of the default packages driver, I believe all packages within the module/view are loaded as syntax packages, whereas any dependencies are loaded as export packages. And test discovery does not scan export packages. |
This should be addressed by #3523 |
Is your feature request related to a problem? Please describe.
When working in a large monorepo, the test explorer indexes indefinitely as it attempts to discover every test in the monorepo. Even after the tests in the currently open package are displayed, it continues to spin and slowly populate other packages which may not be relevant to the user's current work.
Describe the solution you'd like
Adjust the
resolve
method inGoTestResolver
. Add a new configuration setting that optionally skips the step of finding all packages in the workspace, so tests will only get added if part of the user's active package.Describe alternatives you've considered
Potentially more complex logic that lets a user configure the projects that are relevant to them to be included in indexing.
Additional context
Below is the block of code in
goTest/resolve
that could go behind a conditional. I can contribute this change if you're good with it.The text was updated successfully, but these errors were encountered: