Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / internal / lsp / lsprpc / lsprpc_test.go
1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package lsprpc
6
7 import (
8         "context"
9         "regexp"
10         "sync"
11         "testing"
12         "time"
13
14         "golang.org/x/tools/internal/event"
15         "golang.org/x/tools/internal/jsonrpc2"
16         "golang.org/x/tools/internal/jsonrpc2/servertest"
17         "golang.org/x/tools/internal/lsp/cache"
18         "golang.org/x/tools/internal/lsp/debug"
19         "golang.org/x/tools/internal/lsp/fake"
20         "golang.org/x/tools/internal/lsp/protocol"
21         "golang.org/x/tools/internal/testenv"
22 )
23
24 type fakeClient struct {
25         protocol.Client
26
27         logs chan string
28 }
29
30 func (c fakeClient) LogMessage(ctx context.Context, params *protocol.LogMessageParams) error {
31         c.logs <- params.Message
32         return nil
33 }
34
35 // fakeServer is intended to be embedded in the test fakes below, to trivially
36 // implement Shutdown.
37 type fakeServer struct {
38         protocol.Server
39 }
40
41 func (fakeServer) Shutdown(ctx context.Context) error {
42         return nil
43 }
44
45 type pingServer struct{ fakeServer }
46
47 func (s pingServer) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
48         event.Log(ctx, "ping")
49         return nil
50 }
51
52 func TestClientLogging(t *testing.T) {
53         ctx, cancel := context.WithCancel(context.Background())
54         defer cancel()
55
56         server := pingServer{}
57         client := fakeClient{logs: make(chan string, 10)}
58
59         ctx = debug.WithInstance(ctx, "", "")
60         ss := NewStreamServer(cache.New(ctx, nil), false)
61         ss.serverForTest = server
62         ts := servertest.NewPipeServer(ctx, ss, nil)
63         defer checkClose(t, ts.Close)
64         cc := ts.Connect(ctx)
65         cc.Go(ctx, protocol.ClientHandler(client, jsonrpc2.MethodNotFound))
66
67         protocol.ServerDispatcher(cc).DidOpen(ctx, &protocol.DidOpenTextDocumentParams{})
68
69         select {
70         case got := <-client.logs:
71                 want := "ping"
72                 matched, err := regexp.MatchString(want, got)
73                 if err != nil {
74                         t.Fatal(err)
75                 }
76                 if !matched {
77                         t.Errorf("got log %q, want a log containing %q", got, want)
78                 }
79         case <-time.After(1 * time.Second):
80                 t.Error("timeout waiting for client log")
81         }
82 }
83
84 // waitableServer instruments LSP request so that we can control their timing.
85 // The requests chosen are arbitrary: we simply needed one that blocks, and
86 // another that doesn't.
87 type waitableServer struct {
88         fakeServer
89
90         started chan struct{}
91 }
92
93 func (s waitableServer) Hover(ctx context.Context, _ *protocol.HoverParams) (*protocol.Hover, error) {
94         s.started <- struct{}{}
95         select {
96         case <-ctx.Done():
97                 return nil, ctx.Err()
98         case <-time.After(200 * time.Millisecond):
99         }
100         return &protocol.Hover{}, nil
101 }
102
103 func (s waitableServer) Resolve(_ context.Context, item *protocol.CompletionItem) (*protocol.CompletionItem, error) {
104         return item, nil
105 }
106
107 func checkClose(t *testing.T, closer func() error) {
108         t.Helper()
109         if err := closer(); err != nil {
110                 t.Errorf("closing: %v", err)
111         }
112 }
113
114 func setupForwarding(ctx context.Context, t *testing.T, s protocol.Server) (direct, forwarded servertest.Connector, cleanup func()) {
115         t.Helper()
116         serveCtx := debug.WithInstance(ctx, "", "")
117         ss := NewStreamServer(cache.New(serveCtx, nil), false)
118         ss.serverForTest = s
119         tsDirect := servertest.NewTCPServer(serveCtx, ss, nil)
120
121         forwarderCtx := debug.WithInstance(ctx, "", "")
122         forwarder := NewForwarder("tcp", tsDirect.Addr)
123         tsForwarded := servertest.NewPipeServer(forwarderCtx, forwarder, nil)
124         return tsDirect, tsForwarded, func() {
125                 checkClose(t, tsDirect.Close)
126                 checkClose(t, tsForwarded.Close)
127         }
128 }
129
130 func TestRequestCancellation(t *testing.T) {
131         ctx := context.Background()
132         server := waitableServer{
133                 started: make(chan struct{}),
134         }
135         tsDirect, tsForwarded, cleanup := setupForwarding(ctx, t, server)
136         defer cleanup()
137         tests := []struct {
138                 serverType string
139                 ts         servertest.Connector
140         }{
141                 {"direct", tsDirect},
142                 {"forwarder", tsForwarded},
143         }
144
145         for _, test := range tests {
146                 t.Run(test.serverType, func(t *testing.T) {
147                         cc := test.ts.Connect(ctx)
148                         sd := protocol.ServerDispatcher(cc)
149                         cc.Go(ctx,
150                                 protocol.Handlers(
151                                         jsonrpc2.MethodNotFound))
152
153                         ctx := context.Background()
154                         ctx1, cancel1 := context.WithCancel(ctx)
155                         var (
156                                 err1, err2 error
157                                 wg         sync.WaitGroup
158                         )
159                         wg.Add(2)
160                         go func() {
161                                 defer wg.Done()
162                                 _, err1 = sd.Hover(ctx1, &protocol.HoverParams{})
163                         }()
164                         go func() {
165                                 defer wg.Done()
166                                 _, err2 = sd.Resolve(ctx, &protocol.CompletionItem{})
167                         }()
168                         // Wait for the Hover request to start.
169                         <-server.started
170                         cancel1()
171                         wg.Wait()
172                         if err1 == nil {
173                                 t.Errorf("cancelled Hover(): got nil err")
174                         }
175                         if err2 != nil {
176                                 t.Errorf("uncancelled Hover(): err: %v", err2)
177                         }
178                         if _, err := sd.Resolve(ctx, &protocol.CompletionItem{}); err != nil {
179                                 t.Errorf("subsequent Hover(): %v", err)
180                         }
181                 })
182         }
183 }
184
185 const exampleProgram = `
186 -- go.mod --
187 module mod
188
189 go 1.12
190 -- main.go --
191 package main
192
193 import "fmt"
194
195 func main() {
196         fmt.Println("Hello World.")
197 }`
198
199 func TestDebugInfoLifecycle(t *testing.T) {
200         sb, err := fake.NewSandbox(&fake.SandboxConfig{Files: exampleProgram})
201         if err != nil {
202                 t.Fatal(err)
203         }
204         defer func() {
205                 if err := sb.Close(); err != nil {
206                         // TODO(golang/go#38490): we can't currently make this an error because
207                         // it fails on Windows: the workspace directory is still locked by a
208                         // separate Go process.
209                         // Once we have a reliable way to wait for proper shutdown, make this an
210                         // error.
211                         t.Logf("closing workspace failed: %v", err)
212                 }
213         }()
214
215         baseCtx, cancel := context.WithCancel(context.Background())
216         defer cancel()
217         clientCtx := debug.WithInstance(baseCtx, "", "")
218         serverCtx := debug.WithInstance(baseCtx, "", "")
219
220         cache := cache.New(serverCtx, nil)
221         ss := NewStreamServer(cache, false)
222         tsBackend := servertest.NewTCPServer(serverCtx, ss, nil)
223
224         forwarder := NewForwarder("tcp", tsBackend.Addr)
225         tsForwarder := servertest.NewPipeServer(clientCtx, forwarder, nil)
226
227         conn1 := tsForwarder.Connect(clientCtx)
228         ed1, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(clientCtx, conn1, fake.ClientHooks{})
229         if err != nil {
230                 t.Fatal(err)
231         }
232         defer ed1.Close(clientCtx)
233         conn2 := tsBackend.Connect(baseCtx)
234         ed2, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(baseCtx, conn2, fake.ClientHooks{})
235         if err != nil {
236                 t.Fatal(err)
237         }
238         defer ed2.Close(baseCtx)
239
240         serverDebug := debug.GetInstance(serverCtx)
241         if got, want := len(serverDebug.State.Clients()), 2; got != want {
242                 t.Errorf("len(server:Clients) = %d, want %d", got, want)
243         }
244         if got, want := len(serverDebug.State.Sessions()), 2; got != want {
245                 t.Errorf("len(server:Sessions) = %d, want %d", got, want)
246         }
247         clientDebug := debug.GetInstance(clientCtx)
248         if got, want := len(clientDebug.State.Servers()), 1; got != want {
249                 t.Errorf("len(client:Servers) = %d, want %d", got, want)
250         }
251         // Close one of the connections to verify that the client and session were
252         // dropped.
253         if err := ed1.Close(clientCtx); err != nil {
254                 t.Fatal(err)
255         }
256         /*TODO: at this point we have verified the editor is closed
257         However there is no way currently to wait for all associated go routines to
258         go away, and we need to wait for those to trigger the client drop
259         for now we just give it a little bit of time, but we need to fix this
260         in a principled way
261         */
262         start := time.Now()
263         delay := time.Millisecond
264         const maxWait = time.Second
265         for len(serverDebug.State.Clients()) > 1 {
266                 if time.Since(start) > maxWait {
267                         break
268                 }
269                 time.Sleep(delay)
270                 delay *= 2
271         }
272         if got, want := len(serverDebug.State.Clients()), 1; got != want {
273                 t.Errorf("len(server:Clients) = %d, want %d", got, want)
274         }
275         if got, want := len(serverDebug.State.Sessions()), 1; got != want {
276                 t.Errorf("len(server:Sessions()) = %d, want %d", got, want)
277         }
278 }
279
280 type initServer struct {
281         fakeServer
282
283         params *protocol.ParamInitialize
284 }
285
286 func (s *initServer) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) {
287         s.params = params
288         return &protocol.InitializeResult{}, nil
289 }
290
291 func TestEnvForwarding(t *testing.T) {
292         testenv.NeedsGo1Point(t, 13)
293         server := &initServer{}
294         ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
295         defer cancel()
296         _, tsForwarded, cleanup := setupForwarding(ctx, t, server)
297         defer cleanup()
298
299         conn := tsForwarded.Connect(ctx)
300         conn.Go(ctx, jsonrpc2.MethodNotFound)
301         dispatch := protocol.ServerDispatcher(conn)
302         initParams := &protocol.ParamInitialize{}
303         initParams.InitializationOptions = map[string]interface{}{
304                 "env": map[string]interface{}{
305                         "GONOPROXY": "example.com",
306                 },
307         }
308         _, err := dispatch.Initialize(ctx, initParams)
309         if err != nil {
310                 t.Fatal(err)
311         }
312         if server.params == nil {
313                 t.Fatalf("initialize params are unset")
314         }
315         env := server.params.InitializationOptions.(map[string]interface{})["env"].(map[string]interface{})
316
317         // Check for an arbitrary Go variable. It should be set.
318         if _, ok := env["GOPRIVATE"]; !ok {
319                 t.Errorf("Go environment variable GOPRIVATE unset in initialization options")
320         }
321         // Check that the variable present in our user config was not overwritten.
322         if v := env["GONOPROXY"]; v != "example.com" {
323                 t.Errorf("GONOPROXY environment variable was overwritten")
324         }
325 }