Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit babff93

Browse files
committedFeb 10, 2020
internal/lsp/lsprpc: add test for empty diagnostics in deleted files
Add a test for the bug reported in golang/go#37049: we are missing empty diagnostics for deleted files. Doing this involved added a missing RemoveFile method on the fake.Watcher type. Skip the test for now, as it is failing. Updates golang/go#37049 Updates golang/go#36879 Change-Id: Ib3b6907455cc44a2e6af00c2254aa444e9480749 Reviewed-on: https://go-review.googlesource.com/c/tools/+/218278 Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent f958729 commit babff93

File tree

2 files changed

+182
-48
lines changed

2 files changed

+182
-48
lines changed
 

‎internal/lsp/fake/workspace.go

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,33 @@ func (w *Workspace) ReadFile(path string) (string, error) {
116116
return string(b), nil
117117
}
118118

119+
// RemoveFile removes a workspace-relative file path.
120+
func (w *Workspace) RemoveFile(ctx context.Context, path string) error {
121+
fp := w.filePath(path)
122+
if err := os.Remove(fp); err != nil {
123+
return fmt.Errorf("removing %q: %v", path, err)
124+
}
125+
evts := []FileEvent{{
126+
Path: path,
127+
ProtocolEvent: protocol.FileEvent{
128+
URI: w.URI(path),
129+
Type: protocol.Deleted,
130+
},
131+
}}
132+
w.sendEvents(ctx, evts)
133+
return nil
134+
}
135+
136+
func (w *Workspace) sendEvents(ctx context.Context, evts []FileEvent) {
137+
w.watcherMu.Lock()
138+
watchers := make([]func(context.Context, []FileEvent), len(w.watchers))
139+
copy(watchers, w.watchers)
140+
w.watcherMu.Unlock()
141+
for _, w := range watchers {
142+
go w(ctx, evts)
143+
}
144+
}
145+
119146
// WriteFile writes text file content to a workspace-relative path.
120147
func (w *Workspace) WriteFile(ctx context.Context, path, content string) error {
121148
fp := w.filePath(path)
@@ -129,22 +156,18 @@ func (w *Workspace) WriteFile(ctx context.Context, path, content string) error {
129156
} else {
130157
changeType = protocol.Changed
131158
}
132-
werr := w.writeFileData(path, []byte(content))
133-
w.watcherMu.Lock()
134-
watchers := make([]func(context.Context, []FileEvent), len(w.watchers))
135-
copy(watchers, w.watchers)
136-
w.watcherMu.Unlock()
159+
if err := w.writeFileData(path, []byte(content)); err != nil {
160+
return err
161+
}
137162
evts := []FileEvent{{
138163
Path: path,
139164
ProtocolEvent: protocol.FileEvent{
140165
URI: w.URI(path),
141166
Type: changeType,
142167
},
143168
}}
144-
for _, w := range watchers {
145-
go w(ctx, evts)
146-
}
147-
return werr
169+
w.sendEvents(ctx, evts)
170+
return nil
148171
}
149172

150173
func (w *Workspace) writeFileData(path string, data []byte) error {

‎internal/lsp/lsprpc/diagnostics_test.go

Lines changed: 150 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ type testEnvironment struct {
3636
ws *fake.Workspace
3737
ts *servertest.Server
3838

39-
diagnostics <-chan *protocol.PublishDiagnosticsParams
39+
dw diagnosticsWatcher
4040
}
4141

42-
func setupEnv(t *testing.T) (context.Context, testEnvironment, func()) {
42+
func setupEnv(t *testing.T, txt string) (context.Context, testEnvironment, func()) {
4343
t.Helper()
4444
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
4545

46-
ws, err := fake.NewWorkspace("get-diagnostics", []byte(exampleProgram))
46+
ws, err := fake.NewWorkspace("get-diagnostics", []byte(txt))
4747
if err != nil {
4848
t.Fatal(err)
4949
}
@@ -55,24 +55,63 @@ func setupEnv(t *testing.T) (context.Context, testEnvironment, func()) {
5555
if err != nil {
5656
t.Fatal(err)
5757
}
58-
diags := make(chan *protocol.PublishDiagnosticsParams, 10)
59-
editor.Client().OnDiagnostics(func(_ context.Context, params *protocol.PublishDiagnosticsParams) error {
60-
diags <- params
61-
return nil
62-
})
58+
dw := newDiagWatcher(ws)
59+
editor.Client().OnDiagnostics(dw.onDiagnostics)
6360
cleanup := func() {
6461
cancel()
6562
ts.Close()
6663
ws.Close()
6764
}
6865
return ctx, testEnvironment{
69-
editor: editor,
70-
ws: ws,
71-
ts: ts,
72-
diagnostics: diags,
66+
editor: editor,
67+
ws: ws,
68+
ts: ts,
69+
dw: dw,
7370
}, cleanup
7471
}
7572

73+
type diagnosticsWatcher struct {
74+
diagnostics chan *protocol.PublishDiagnosticsParams
75+
ws *fake.Workspace
76+
}
77+
78+
func newDiagWatcher(ws *fake.Workspace) diagnosticsWatcher {
79+
return diagnosticsWatcher{
80+
// Allow an arbitrarily large buffer, as we should never want onDiagnostics
81+
// to block.
82+
diagnostics: make(chan *protocol.PublishDiagnosticsParams, 1000),
83+
ws: ws,
84+
}
85+
}
86+
87+
func (w diagnosticsWatcher) onDiagnostics(_ context.Context, p *protocol.PublishDiagnosticsParams) error {
88+
w.diagnostics <- p
89+
return nil
90+
}
91+
92+
func (w diagnosticsWatcher) await(ctx context.Context, expected ...string) (map[string]*protocol.PublishDiagnosticsParams, error) {
93+
expectedSet := make(map[string]bool)
94+
for _, e := range expected {
95+
expectedSet[e] = true
96+
}
97+
got := make(map[string]*protocol.PublishDiagnosticsParams)
98+
for len(got) < len(expectedSet) {
99+
select {
100+
case <-ctx.Done():
101+
return nil, ctx.Err()
102+
case d := <-w.diagnostics:
103+
pth, err := w.ws.URIToPath(d.URI)
104+
if err != nil {
105+
return nil, err
106+
}
107+
if expectedSet[pth] {
108+
got[pth] = d
109+
}
110+
}
111+
}
112+
return got, nil
113+
}
114+
76115
func checkDiagnosticLocation(params *protocol.PublishDiagnosticsParams, filename string, line, col int) error {
77116
if got, want := params.URI, filename; got != want {
78117
return fmt.Errorf("got diagnostics for URI %q, want %q", got, want)
@@ -89,7 +128,7 @@ func checkDiagnosticLocation(params *protocol.PublishDiagnosticsParams, filename
89128

90129
func TestDiagnosticErrorInEditedFile(t *testing.T) {
91130
t.Parallel()
92-
ctx, env, cleanup := setupEnv(t)
131+
ctx, env, cleanup := setupEnv(t, exampleProgram)
93132
defer cleanup()
94133

95134
// Deleting the 'n' at the end of Println should generate a single error
@@ -107,15 +146,18 @@ func TestDiagnosticErrorInEditedFile(t *testing.T) {
107146
if err := env.editor.EditBuffer(ctx, "main.go", edits); err != nil {
108147
t.Fatal(err)
109148
}
110-
params := awaitDiagnostics(ctx, t, env.diagnostics)
111-
if err := checkDiagnosticLocation(params, env.ws.URI("main.go"), 5, 5); err != nil {
149+
diags, err := env.dw.await(ctx, "main.go")
150+
if err != nil {
151+
t.Fatal(err)
152+
}
153+
if err := checkDiagnosticLocation(diags["main.go"], env.ws.URI("main.go"), 5, 5); err != nil {
112154
t.Fatal(err)
113155
}
114156
}
115157

116158
func TestSimultaneousEdits(t *testing.T) {
117159
t.Parallel()
118-
ctx, env, cleanup := setupEnv(t)
160+
ctx, env, cleanup := setupEnv(t, exampleProgram)
119161
defer cleanup()
120162

121163
// Set up a second editor session connected to the same server, using the
@@ -125,12 +167,8 @@ func TestSimultaneousEdits(t *testing.T) {
125167
if err != nil {
126168
t.Fatal(err)
127169
}
128-
diags2 := make(chan *protocol.PublishDiagnosticsParams, 10)
129-
editor2.Client().OnDiagnostics(func(_ context.Context, params *protocol.PublishDiagnosticsParams) error {
130-
diags2 <- params
131-
return nil
132-
})
133-
170+
dw2 := newDiagWatcher(env.ws)
171+
editor2.Client().OnDiagnostics(dw2.onDiagnostics)
134172
// In editor #1, break fmt.Println as before.
135173
edits1 := []fake.Edit{{
136174
Start: fake.Pos{Line: 5, Column: 11},
@@ -156,23 +194,19 @@ func TestSimultaneousEdits(t *testing.T) {
156194
if err := editor2.EditBuffer(ctx, "main.go", edits2); err != nil {
157195
t.Fatal(err)
158196
}
159-
params1 := awaitDiagnostics(ctx, t, env.diagnostics)
160-
params2 := awaitDiagnostics(ctx, t, diags2)
161-
if err := checkDiagnosticLocation(params1, env.ws.URI("main.go"), 5, 5); err != nil {
197+
diags1, err := env.dw.await(ctx, "main.go")
198+
if err != nil {
162199
t.Fatal(err)
163200
}
164-
if err := checkDiagnosticLocation(params2, env.ws.URI("main.go"), 7, 0); err != nil {
201+
diags2, err := dw2.await(ctx, "main.go")
202+
if err != nil {
165203
t.Fatal(err)
166204
}
167-
}
168-
169-
func awaitDiagnostics(ctx context.Context, t *testing.T, diags <-chan *protocol.PublishDiagnosticsParams) *protocol.PublishDiagnosticsParams {
170-
t.Helper()
171-
select {
172-
case <-ctx.Done():
173-
panic(ctx.Err())
174-
case d := <-diags:
175-
return d
205+
if err := checkDiagnosticLocation(diags1["main.go"], env.ws.URI("main.go"), 5, 5); err != nil {
206+
t.Fatal(err)
207+
}
208+
if err := checkDiagnosticLocation(diags2["main.go"], env.ws.URI("main.go"), 7, 0); err != nil {
209+
t.Fatal(err)
176210
}
177211
}
178212

@@ -183,14 +217,91 @@ const Foo = "abc
183217

184218
func TestDiagnosticErrorInNewFile(t *testing.T) {
185219
t.Parallel()
186-
ctx, env, cleanup := setupEnv(t)
220+
ctx, env, cleanup := setupEnv(t, exampleProgram)
187221
defer cleanup()
188222

189223
if err := env.editor.CreateBuffer(ctx, "broken.go", brokenFile); err != nil {
190224
t.Fatal(err)
191225
}
192-
params := awaitDiagnostics(ctx, t, env.diagnostics)
193-
if got, want := params.URI, env.ws.URI("broken.go"); got != want {
194-
t.Fatalf("got diagnostics for URI %q, want %q", got, want)
226+
_, err := env.dw.await(ctx, "broken.go")
227+
if err != nil {
228+
t.Fatal(err)
229+
}
230+
}
231+
232+
// badPackage contains a duplicate definition of the 'a' const.
233+
const badPackage = `
234+
-- go.mod --
235+
module mod
236+
237+
go 1.12
238+
-- a.go --
239+
package consts
240+
241+
const a = 1
242+
-- b.go --
243+
package consts
244+
245+
const a = 2
246+
`
247+
248+
func TestDiagnosticClearingOnEdit(t *testing.T) {
249+
t.Parallel()
250+
ctx, env, cleanup := setupEnv(t, badPackage)
251+
defer cleanup()
252+
253+
if err := env.editor.OpenFile(ctx, "b.go"); err != nil {
254+
t.Fatal(err)
255+
}
256+
_, err := env.dw.await(ctx, "a.go", "b.go")
257+
if err != nil {
258+
t.Fatal(err)
259+
}
260+
261+
// In editor #2 remove the closing brace.
262+
edits := []fake.Edit{{
263+
Start: fake.Pos{Line: 2, Column: 6},
264+
End: fake.Pos{Line: 2, Column: 7},
265+
Text: "b",
266+
}}
267+
if err := env.editor.EditBuffer(ctx, "b.go", edits); err != nil {
268+
t.Fatal(err)
269+
}
270+
diags, err := env.dw.await(ctx, "a.go", "b.go")
271+
if err != nil {
272+
t.Fatal(err)
273+
}
274+
for pth, d := range diags {
275+
if len(d.Diagnostics) != 0 {
276+
t.Errorf("non-empty diagnostics for %q", pth)
277+
}
278+
}
279+
}
280+
281+
func TestDiagnosticClearingOnDelete(t *testing.T) {
282+
t.Skip("skipping due to golang.org/issues/37049")
283+
284+
t.Parallel()
285+
ctx, env, cleanup := setupEnv(t, badPackage)
286+
defer cleanup()
287+
288+
if err := env.editor.OpenFile(ctx, "a.go"); err != nil {
289+
t.Fatal(err)
290+
}
291+
_, err := env.dw.await(ctx, "a.go", "b.go")
292+
if err != nil {
293+
t.Fatal(err)
294+
}
295+
env.ws.RemoveFile(ctx, "b.go")
296+
297+
// TODO(golang.org/issues/37049): here we only get diagnostics for a.go.
298+
diags, err := env.dw.await(ctx, "a.go", "b.go")
299+
if err != nil {
300+
t.Fatal(err)
301+
}
302+
for pth, d := range diags {
303+
if len(d.Diagnostics) != 0 {
304+
t.Errorf("non-empty diagnostics for %q", pth)
305+
}
195306
}
196307
}

0 commit comments

Comments
 (0)
Please sign in to comment.