Skip to content

Commit b389785

Browse files
committed
gopls: fix tests that depend on log sequencing
As noted recently in gopls/internal/lsp/protocol/context.go, log messages are delivered asynchronously to other LSP events. This means that any test relying on sequencing of log messages with respect to other operations is fundamentally incorrect. Searching for such tests turned up several known flakes. Fixes golang/go#61521 Fixes golang/go#51549 Fixes golang/go#51772 Change-Id: I4cb5031140e34e1c816b221a90d5ffa4055ad76c Reviewed-on: https://go-review.googlesource.com/c/tools/+/513097 Run-TryBot: Robert Findley <[email protected]> Reviewed-by: Alan Donovan <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 2ffc4dc commit b389785

File tree

4 files changed

+30
-52
lines changed

4 files changed

+30
-52
lines changed

gopls/internal/lsp/regtest/expectation.go

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -212,20 +212,6 @@ func ReadAllDiagnostics(into *map[string]*protocol.PublishDiagnosticsParams) Exp
212212
}
213213
}
214214

215-
// ReadLogs is an expectation that may be used with AfterChange or OnceMet
216-
// conditions to copy logs into the provided slice.
217-
func ReadLogs(into *[]*protocol.LogMessageParams) Expectation {
218-
check := func(s State) Verdict {
219-
*into = make([]*protocol.LogMessageParams, len(s.logs))
220-
copy(*into, s.logs)
221-
return Met
222-
}
223-
return Expectation{
224-
Check: check,
225-
Description: "read logs",
226-
}
227-
}
228-
229215
// NoOutstandingWork asserts that there is no work initiated using the LSP
230216
// $/progress API that has not completed.
231217
func NoOutstandingWork() Expectation {
@@ -521,6 +507,10 @@ func NoErrorLogs() Expectation {
521507
// The count argument specifies the expected number of matching logs. If
522508
// atLeast is set, this is a lower bound, otherwise there must be exactly count
523509
// matching logs.
510+
//
511+
// Logs are asynchronous to other LSP messages, so this expectation should not
512+
// be used with combinators such as OnceMet or AfterChange that assert on
513+
// ordering with respect to other operations.
524514
func LogMatching(typ protocol.MessageType, re string, count int, atLeast bool) Expectation {
525515
rec, err := regexp.Compile(re)
526516
if err != nil {
@@ -537,6 +527,11 @@ func LogMatching(typ protocol.MessageType, re string, count int, atLeast bool) E
537527
if found == count || (found >= count && atLeast) {
538528
return Met
539529
}
530+
// If we require an exact count, and have received more than expected, the
531+
// expectation can never be met.
532+
if found > count && !atLeast {
533+
return Unmeetable
534+
}
540535
return Unmet
541536
}
542537
desc := fmt.Sprintf("log message matching %q expected %v times", re, count)

gopls/internal/regtest/diagnostics/diagnostics_test.go

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"context"
99
"fmt"
1010
"os/exec"
11-
"strings"
1211
"testing"
1312

1413
"golang.org/x/tools/gopls/internal/bug"
@@ -433,7 +432,11 @@ func TestResolveDiagnosticWithDownload(t *testing.T) {
433432
func TestMissingDependency(t *testing.T) {
434433
Run(t, testPackageWithRequire, func(t *testing.T, env *Env) {
435434
env.OpenFile("print.go")
436-
env.Await(LogMatching(protocol.Error, "initial workspace load failed", 1, false))
435+
env.Await(
436+
// Log messages are asynchronous to other events on the LSP stream, so we
437+
// can't use OnceMet or AfterChange here.
438+
LogMatching(protocol.Error, "initial workspace load failed", 1, false),
439+
)
437440
})
438441
}
439442

@@ -1303,42 +1306,18 @@ func _() {
13031306
)
13041307
env.OpenFile("a/a_exclude.go")
13051308

1306-
countLoads := func(logs []*protocol.LogMessageParams) int {
1307-
count := 0
1308-
for _, log := range logs {
1309-
if strings.Contains(log.Message, `go/packages.Load`) {
1310-
count++
1311-
}
1312-
}
1313-
return count
1314-
}
1315-
1316-
var before []*protocol.LogMessageParams
1317-
env.AfterChange(
1318-
Diagnostics(env.AtRegexp("a/a_exclude.go", "package (a)")),
1319-
ReadLogs(&before),
1320-
)
1309+
loadOnce := LogMatching(protocol.Info, "query=.*file=.*a_exclude.go", 1, false)
1310+
env.Await(loadOnce) // can't use OnceMet or AfterChange as logs are async
13211311

13221312
// Check that orphaned files are not reloaded, by making a change in
13231313
// a.go file and confirming that the workspace diagnosis did not reload
13241314
// a_exclude.go.
13251315
//
1326-
// Currently, typing in a_exclude.go does result in a reload, because we
1327-
// aren't precise about which changes could result in an unloadable file
1328-
// becoming loadable.
1329-
loads0 := countLoads(before)
1330-
if loads0 == 0 {
1331-
t.Fatal("got 0 Load log messages, want at least 1", loads0)
1332-
}
1316+
// This is racy (but fails open) because logs are asynchronous to other LSP
1317+
// operations. There's a chance gopls _did_ log, and we just haven't seen
1318+
// it yet.
13331319
env.RegexpReplace("a/a.go", "package a", "package a // arbitrary comment")
1334-
var after []*protocol.LogMessageParams
1335-
env.AfterChange(
1336-
Diagnostics(env.AtRegexp("a/a_exclude.go", "package (a)")),
1337-
ReadLogs(&after),
1338-
)
1339-
if loads := countLoads(after); loads != loads0 {
1340-
t.Errorf("got %d Load log messages after change to orphaned file, want %d", loads, loads0)
1341-
}
1320+
env.AfterChange(loadOnce)
13421321
})
13431322
}
13441323

@@ -1853,8 +1832,10 @@ func main() {}
18531832
`
18541833
Run(t, files, func(t *testing.T, env *Env) {
18551834
env.OpenFile("go.mod")
1856-
env.AfterChange(
1835+
env.Await(
18571836
// Check that we have only loaded "<dir>/..." once.
1837+
// Log messages are asynchronous to other events on the LSP stream, so we
1838+
// can't use OnceMet or AfterChange here.
18581839
LogMatching(protocol.Info, `.*query=.*\.\.\..*`, 1, false),
18591840
)
18601841
})

gopls/internal/regtest/watch/watch_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,9 @@ package a
383383
).Run(t, pkg, func(t *testing.T, env *Env) {
384384
env.OpenFile("a/a.go")
385385
env.OpenFile("a/a_unneeded.go")
386-
env.AfterChange(
386+
env.Await(
387+
// Log messages are asynchronous to other events on the LSP stream, so we
388+
// can't use OnceMet or AfterChange here.
387389
LogMatching(protocol.Info, "a_unneeded.go", 1, false),
388390
)
389391

@@ -395,7 +397,7 @@ package a
395397
Diagnostics(env.AtRegexp("a/a.go", "fmt")),
396398
)
397399
env.SaveBuffer("a/a.go")
398-
env.AfterChange(
400+
env.Await(
399401
// There should only be one log message containing
400402
// a_unneeded.go, from the initial workspace load, which we
401403
// check for earlier. If there are more, there's a bug.
@@ -411,7 +413,7 @@ package a
411413
).Run(t, pkg, func(t *testing.T, env *Env) {
412414
env.OpenFile("a/a.go")
413415
env.OpenFile("a/a_unneeded.go")
414-
env.AfterChange(
416+
env.Await(
415417
LogMatching(protocol.Info, "a_unneeded.go", 1, false),
416418
)
417419

@@ -423,7 +425,7 @@ package a
423425
Diagnostics(env.AtRegexp("a/a.go", "fmt")),
424426
)
425427
env.SaveBuffer("a/a.go")
426-
env.AfterChange(
428+
env.Await(
427429
// There should only be one log message containing
428430
// a_unneeded.go, from the initial workspace load, which we
429431
// check for earlier. If there are more, there's a bug.

gopls/internal/regtest/workspace/workspace_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ func TestReloadOnlyOnce(t *testing.T) {
177177
replace random.org => %s
178178
`, env.ReadWorkspaceFile("pkg/go.mod"), dir)
179179
env.WriteWorkspaceFile("pkg/go.mod", goModWithReplace)
180-
env.AfterChange(
180+
env.Await(
181181
LogMatching(protocol.Info, `packages\.Load #\d+\n`, 2, false),
182182
)
183183
})

0 commit comments

Comments
 (0)