Skip to content

Commit f964368

Browse files
committed
gopls/internal/regtest: fix synchronization for TestUseGoplsMod
Jumping to definition in a regtest can indirectly lead to a didOpen call, so the awaits added to TestUseGoplsMod to synchronize metadata were ineffectual. On Android, this can lead to the race described in golang/go#43652. But in any case, all this bookkeeping of notifications is fragile. Avoid it entirely by having the fake editor keep track of notification statistics. In the future, we should use this to clean up many existing regtests. For golang/go#43554 For golang/go#39384 Change-Id: Icd1619bd5cdd2f646d1a0050f5beaf2ab1c27f37 Reviewed-on: https://go-review.googlesource.com/c/tools/+/283512 Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> Trust: Robert Findley <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 8b4aab6 commit f964368

File tree

4 files changed

+60
-11
lines changed

4 files changed

+60
-11
lines changed

gopls/internal/regtest/expectation.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,27 @@ func ShowMessageRequest(title string) SimpleExpectation {
185185
}
186186
}
187187

188+
// DoneWithOpen expects all didOpen notifications currently sent by the editor
189+
// to be completely processed.
190+
func (e *Env) DoneWithOpen() Expectation {
191+
opens := e.Editor.Stats().DidOpen
192+
return CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), opens)
193+
}
194+
195+
// DoneWithChange expects all didChange notifications currently sent by the
196+
// editor to be completely processed.
197+
func (e *Env) DoneWithChange() Expectation {
198+
changes := e.Editor.Stats().DidChange
199+
return CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChange), changes)
200+
}
201+
202+
// DoneWithChangeWatchedFiles expects all didChangeWatchedFiles notifications
203+
// currently sent by the editor to be completely processed.
204+
func (e *Env) DoneWithChangeWatchedFiles() Expectation {
205+
changes := e.Editor.Stats().DidChangeWatchedFiles
206+
return CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), changes)
207+
}
208+
188209
// CompletedWork expects a work item to have been completed >= atLeast times.
189210
//
190211
// Since the Progress API doesn't include any hidden metadata, we must use the

gopls/internal/regtest/reg_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ func TestMain(m *testing.M) {
9898
// Regtest cleanup is broken in go1.12 and earlier, and sometimes flakes on
9999
// Windows due to file locking, but this is OK for our CI.
100100
//
101-
// Fail on non-windows go1.13+.
102-
if testenv.Go1Point() >= 13 && runtime.GOOS != "windows" {
101+
// Fail on go1.13+, except for windows and android which have shutdown problems.
102+
if testenv.Go1Point() >= 13 && runtime.GOOS != "windows" && runtime.GOOS != "android" {
103103
os.Exit(1)
104104
}
105105
}

gopls/internal/regtest/workspace_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ replace a.com => $SANDBOX_WORKDIR/moda/a
449449
// loaded. Validate this by jumping to a definition in b.com and ensuring
450450
// that we go to the module cache.
451451
env.OpenFile("moda/a/a.go")
452-
env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1))
452+
env.Await(env.DoneWithOpen())
453453

454454
// To verify which modules are loaded, we'll jump to the definition of
455455
// b.Hello.
@@ -479,11 +479,12 @@ require (
479479
replace a.com => %s/moda/a
480480
replace b.com => %s/modb
481481
`, workdir, workdir))
482-
env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1))
482+
env.Await(env.DoneWithChangeWatchedFiles())
483483
// Check that go.mod diagnostics picked up the newly active mod file.
484484
// The local version of modb has an extra dependency we need to download.
485485
env.OpenFile("modb/go.mod")
486-
env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 2))
486+
env.Await(env.DoneWithOpen())
487+
487488
var d protocol.PublishDiagnosticsParams
488489
env.Await(
489490
OnceMet(
@@ -501,7 +502,7 @@ replace b.com => %s/modb
501502
// Now, let's modify the gopls.mod *overlay* (not on disk), and verify that
502503
// this change is only picked up once it is saved.
503504
env.OpenFile("gopls.mod")
504-
env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 3))
505+
env.Await(env.DoneWithOpen())
505506
env.SetBufferContent("gopls.mod", fmt.Sprintf(`module gopls-workspace
506507
507508
require (
@@ -514,7 +515,7 @@ replace a.com => %s/moda/a
514515
// Editing the gopls.mod removes modb from the workspace modules, and so
515516
// should clear outstanding diagnostics...
516517
env.Await(OnceMet(
517-
CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChange), 2),
518+
env.DoneWithChange(),
518519
EmptyDiagnostics("modb/go.mod"),
519520
))
520521
// ...but does not yet cause a workspace reload, so we should still jump to modb.

internal/lsp/fake/editor.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ type Editor struct {
4141
buffers map[string]buffer
4242
// Capabilities / Options
4343
serverCapabilities protocol.ServerCapabilities
44+
// Call metrics for the purpose of expectations. This is done in an ad-hoc
45+
// manner for now. Perhaps in the future we should do something more
46+
// systematic.
47+
calls CallCounts
48+
}
49+
50+
type CallCounts struct {
51+
DidOpen, DidChange, DidChangeWatchedFiles int
4452
}
4553

4654
type buffer struct {
@@ -131,6 +139,12 @@ func (e *Editor) Connect(ctx context.Context, conn jsonrpc2.Conn, hooks ClientHo
131139
return e, nil
132140
}
133141

142+
func (e *Editor) Stats() CallCounts {
143+
e.mu.Lock()
144+
defer e.mu.Unlock()
145+
return e.calls
146+
}
147+
134148
// Shutdown issues the 'shutdown' LSP notification.
135149
func (e *Editor) Shutdown(ctx context.Context) error {
136150
if e.Server != nil {
@@ -282,6 +296,7 @@ func (e *Editor) onFileChanges(ctx context.Context, evts []FileEvent) {
282296
return
283297
}
284298
e.mu.Lock()
299+
defer e.mu.Unlock()
285300
var lspevts []protocol.FileEvent
286301
for _, evt := range evts {
287302
// Always send an on-disk change, even for events that seem useless
@@ -302,10 +317,10 @@ func (e *Editor) onFileChanges(ctx context.Context, evts []FileEvent) {
302317
_ = e.setBufferContentLocked(ctx, evt.Path, false, strings.Split(content, "\n"), nil)
303318
}
304319
}
305-
e.mu.Unlock()
306320
e.Server.DidChangeWatchedFiles(ctx, &protocol.DidChangeWatchedFilesParams{
307321
Changes: lspevts,
308322
})
323+
e.calls.DidChangeWatchedFiles++
309324
}
310325

311326
// OpenFile creates a buffer for the given workdir-relative file.
@@ -346,16 +361,17 @@ func (e *Editor) createBuffer(ctx context.Context, path string, dirty bool, cont
346361
dirty: dirty,
347362
}
348363
e.mu.Lock()
364+
defer e.mu.Unlock()
349365
e.buffers[path] = buf
350366
item := textDocumentItem(e.sandbox.Workdir, buf)
351-
e.mu.Unlock()
352367

353368
if e.Server != nil {
354369
if err := e.Server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
355370
TextDocument: item,
356371
}); err != nil {
357372
return errors.Errorf("DidOpen: %w", err)
358373
}
374+
e.calls.DidOpen++
359375
}
360376
return nil
361377
}
@@ -570,6 +586,14 @@ func (e *Editor) SetBufferContent(ctx context.Context, path, content string) err
570586
return e.setBufferContentLocked(ctx, path, true, lines, nil)
571587
}
572588

589+
// HasBuffer reports whether the file name is open in the editor.
590+
func (e *Editor) HasBuffer(name string) bool {
591+
e.mu.Lock()
592+
defer e.mu.Unlock()
593+
_, ok := e.buffers[name]
594+
return ok
595+
}
596+
573597
// BufferText returns the content of the buffer with the given name.
574598
func (e *Editor) BufferText(name string) string {
575599
e.mu.Lock()
@@ -629,6 +653,7 @@ func (e *Editor) setBufferContentLocked(ctx context.Context, path string, dirty
629653
if err := e.Server.DidChange(ctx, params); err != nil {
630654
return errors.Errorf("DidChange: %w", err)
631655
}
656+
e.calls.DidChange++
632657
}
633658
return nil
634659
}
@@ -652,8 +677,10 @@ func (e *Editor) GoToDefinition(ctx context.Context, path string, pos Pos) (stri
652677
}
653678
newPath := e.sandbox.Workdir.URIToPath(resp[0].URI)
654679
newPos := fromProtocolPosition(resp[0].Range.Start)
655-
if err := e.OpenFile(ctx, newPath); err != nil {
656-
return "", Pos{}, errors.Errorf("OpenFile: %w", err)
680+
if !e.HasBuffer(newPath) {
681+
if err := e.OpenFile(ctx, newPath); err != nil {
682+
return "", Pos{}, errors.Errorf("OpenFile: %w", err)
683+
}
657684
}
658685
return newPath, newPos, nil
659686
}

0 commit comments

Comments
 (0)