Skip to content

Commit 23211ff

Browse files
findleyrgopherbot
authored andcommitted
gopls/internal/test/integration: better expectation failures
A particularly tricky-to-diagnose marker test failure finally led me to address several long-standing TODOs: integration test expectations should identify their specific failure reason. Previously, we had been relying on a combination the State.String summary and LSP logs to debug failed expectations, but often it was not obvious from the test failure what condition actually failed. Now, expectations describe their failure, and composite expectations compose their component failures. Change-Id: I2533c8a35b4eb561f505fd3ed95fe55483340773 Reviewed-on: https://go-review.googlesource.com/c/tools/+/651417 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Robert Findley <[email protected]>
1 parent 8b85edc commit 23211ff

File tree

2 files changed

+194
-220
lines changed

2 files changed

+194
-220
lines changed

gopls/internal/test/integration/env.go

Lines changed: 27 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -114,53 +114,16 @@ type workProgress struct {
114114
complete bool // seen 'end'
115115
}
116116

117-
// This method, provided for debugging, accesses mutable fields without a lock,
118-
// so it must not be called concurrent with any State mutation.
119-
func (s State) String() string {
120-
var b strings.Builder
121-
b.WriteString("#### log messages (see RPC logs for full text):\n")
122-
for _, msg := range s.logs {
123-
summary := fmt.Sprintf("%v: %q", msg.Type, msg.Message)
124-
if len(summary) > 60 {
125-
summary = summary[:57] + "..."
126-
}
127-
// Some logs are quite long, and since they should be reproduced in the RPC
128-
// logs on any failure we include here just a short summary.
129-
fmt.Fprint(&b, "\t"+summary+"\n")
130-
}
131-
b.WriteString("\n")
132-
b.WriteString("#### diagnostics:\n")
133-
for name, params := range s.diagnostics {
134-
fmt.Fprintf(&b, "\t%s (version %d):\n", name, params.Version)
135-
for _, d := range params.Diagnostics {
136-
fmt.Fprintf(&b, "\t\t%d:%d [%s]: %s\n", d.Range.Start.Line, d.Range.Start.Character, d.Source, d.Message)
137-
}
138-
}
139-
b.WriteString("\n")
140-
b.WriteString("#### outstanding work:\n")
141-
for token, state := range s.work {
142-
if state.complete {
143-
continue
144-
}
145-
name := state.title
146-
if name == "" {
147-
name = fmt.Sprintf("!NO NAME(token: %s)", token)
148-
}
149-
fmt.Fprintf(&b, "\t%s: %.2f\n", name, state.percent)
150-
}
151-
b.WriteString("#### completed work:\n")
152-
for name, count := range s.completedWork {
153-
fmt.Fprintf(&b, "\t%s: %d\n", name, count)
154-
}
155-
return b.String()
117+
type awaitResult struct {
118+
verdict Verdict
119+
reason string
156120
}
157121

158-
// A condition is satisfied when all expectations are simultaneously
159-
// met. At that point, the 'met' channel is closed. On any failure, err is set
160-
// and the failed channel is closed.
122+
// A condition is satisfied when its expectation is [Met] or [Unmeetable]. The
123+
// result is sent on the verdict channel.
161124
type condition struct {
162-
expectations []Expectation
163-
verdict chan Verdict
125+
expectation Expectation
126+
verdict chan awaitResult
164127
}
165128

166129
func (a *Awaiter) onDiagnostics(_ context.Context, d *protocol.PublishDiagnosticsParams) error {
@@ -334,27 +297,13 @@ func (a *Awaiter) onUnregisterCapability(_ context.Context, m *protocol.Unregist
334297

335298
func (a *Awaiter) checkConditionsLocked() {
336299
for id, condition := range a.waiters {
337-
if v, _ := checkExpectations(a.state, condition.expectations); v != Unmet {
300+
if v, why := condition.expectation.Check(a.state); v != Unmet {
338301
delete(a.waiters, id)
339-
condition.verdict <- v
302+
condition.verdict <- awaitResult{v, why}
340303
}
341304
}
342305
}
343306

344-
// checkExpectations reports whether s meets all expectations.
345-
func checkExpectations(s State, expectations []Expectation) (Verdict, string) {
346-
finalVerdict := Met
347-
var summary strings.Builder
348-
for _, e := range expectations {
349-
v := e.Check(s)
350-
if v > finalVerdict {
351-
finalVerdict = v
352-
}
353-
fmt.Fprintf(&summary, "%v: %s\n", v, e.Description)
354-
}
355-
return finalVerdict, summary.String()
356-
}
357-
358307
// Await blocks until the given expectations are all simultaneously met.
359308
//
360309
// Generally speaking Await should be avoided because it blocks indefinitely if
@@ -363,38 +312,38 @@ func checkExpectations(s State, expectations []Expectation) (Verdict, string) {
363312
// waiting.
364313
func (e *Env) Await(expectations ...Expectation) {
365314
e.T.Helper()
366-
if err := e.Awaiter.Await(e.Ctx, expectations...); err != nil {
315+
if err := e.Awaiter.Await(e.Ctx, AllOf(expectations...)); err != nil {
367316
e.T.Fatal(err)
368317
}
369318
}
370319

371320
// OnceMet blocks until the precondition is met by the state or becomes
372321
// unmeetable. If it was met, OnceMet checks that the state meets all
373322
// expectations in mustMeets.
374-
func (e *Env) OnceMet(precondition Expectation, mustMeets ...Expectation) {
323+
func (e *Env) OnceMet(pre Expectation, mustMeets ...Expectation) {
375324
e.T.Helper()
376-
e.Await(OnceMet(precondition, mustMeets...))
325+
e.Await(OnceMet(pre, AllOf(mustMeets...)))
377326
}
378327

379328
// Await waits for all expectations to simultaneously be met. It should only be
380329
// called from the main test goroutine.
381-
func (a *Awaiter) Await(ctx context.Context, expectations ...Expectation) error {
330+
func (a *Awaiter) Await(ctx context.Context, expectation Expectation) error {
382331
a.mu.Lock()
383332
// Before adding the waiter, we check if the condition is currently met or
384333
// failed to avoid a race where the condition was realized before Await was
385334
// called.
386-
switch verdict, summary := checkExpectations(a.state, expectations); verdict {
335+
switch verdict, why := expectation.Check(a.state); verdict {
387336
case Met:
388337
a.mu.Unlock()
389338
return nil
390339
case Unmeetable:
391-
err := fmt.Errorf("unmeetable expectations:\n%s\nstate:\n%v", summary, a.state)
340+
err := fmt.Errorf("unmeetable expectation:\n%s\nreason:\n%s", indent(expectation.Description), indent(why))
392341
a.mu.Unlock()
393342
return err
394343
}
395344
cond := &condition{
396-
expectations: expectations,
397-
verdict: make(chan Verdict),
345+
expectation: expectation,
346+
verdict: make(chan awaitResult),
398347
}
399348
a.waiters[nextAwaiterRegistration.Add(1)] = cond
400349
a.mu.Unlock()
@@ -403,19 +352,17 @@ func (a *Awaiter) Await(ctx context.Context, expectations ...Expectation) error
403352
select {
404353
case <-ctx.Done():
405354
err = ctx.Err()
406-
case v := <-cond.verdict:
407-
if v != Met {
408-
err = fmt.Errorf("condition has final verdict %v", v)
355+
case res := <-cond.verdict:
356+
if res.verdict != Met {
357+
err = fmt.Errorf("the following condition is %s:\n%s\nreason:\n%s",
358+
res.verdict, indent(expectation.Description), indent(res.reason))
409359
}
410360
}
411-
a.mu.Lock()
412-
defer a.mu.Unlock()
413-
_, summary := checkExpectations(a.state, expectations)
361+
return err
362+
}
414363

415-
// Debugging an unmet expectation can be tricky, so we put some effort into
416-
// nicely formatting the failure.
417-
if err != nil {
418-
return fmt.Errorf("waiting on:\n%s\nerr:%v\n\nstate:\n%v", summary, err, a.state)
419-
}
420-
return nil
364+
// indent indents all lines of msg, including the first.
365+
func indent(msg string) string {
366+
const prefix = " "
367+
return prefix + strings.ReplaceAll(msg, "\n", "\n"+prefix)
421368
}

0 commit comments

Comments
 (0)