golang.org/x/tools/gopls@v0.15.3/internal/lsprpc/lsprpc_test.go (about) 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 "encoding/json" 10 "errors" 11 "regexp" 12 "strings" 13 "testing" 14 "time" 15 16 "golang.org/x/tools/gopls/internal/cache" 17 "golang.org/x/tools/gopls/internal/debug" 18 "golang.org/x/tools/gopls/internal/protocol" 19 "golang.org/x/tools/gopls/internal/test/integration/fake" 20 "golang.org/x/tools/internal/event" 21 "golang.org/x/tools/internal/jsonrpc2" 22 "golang.org/x/tools/internal/jsonrpc2/servertest" 23 "golang.org/x/tools/internal/testenv" 24 ) 25 26 type FakeClient struct { 27 protocol.Client 28 29 Logs chan string 30 } 31 32 func (c FakeClient) LogMessage(ctx context.Context, params *protocol.LogMessageParams) error { 33 c.Logs <- params.Message 34 return nil 35 } 36 37 // fakeServer is intended to be embedded in the test fakes below, to trivially 38 // implement Shutdown. 39 type fakeServer struct { 40 protocol.Server 41 } 42 43 func (fakeServer) Shutdown(ctx context.Context) error { 44 return nil 45 } 46 47 type PingServer struct{ fakeServer } 48 49 func (s PingServer) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error { 50 event.Log(ctx, "ping") 51 return nil 52 } 53 54 func TestClientLogging(t *testing.T) { 55 ctx, cancel := context.WithCancel(context.Background()) 56 defer cancel() 57 58 server := PingServer{} 59 client := FakeClient{Logs: make(chan string, 10)} 60 61 ctx = debug.WithInstance(ctx, "") 62 ss := NewStreamServer(cache.New(nil), false, nil).(*StreamServer) 63 ss.serverForTest = server 64 ts := servertest.NewPipeServer(ss, nil) 65 defer checkClose(t, ts.Close) 66 cc := ts.Connect(ctx) 67 cc.Go(ctx, protocol.ClientHandler(client, jsonrpc2.MethodNotFound)) 68 69 if err := protocol.ServerDispatcher(cc).DidOpen(ctx, &protocol.DidOpenTextDocumentParams{}); err != nil { 70 t.Errorf("DidOpen: %v", err) 71 } 72 73 select { 74 case got := <-client.Logs: 75 want := "ping" 76 matched, err := regexp.MatchString(want, got) 77 if err != nil { 78 t.Fatal(err) 79 } 80 if !matched { 81 t.Errorf("got log %q, want a log containing %q", got, want) 82 } 83 case <-time.After(1 * time.Second): 84 t.Error("timeout waiting for client log") 85 } 86 } 87 88 // WaitableServer instruments LSP request so that we can control their timing. 89 // The requests chosen are arbitrary: we simply needed one that blocks, and 90 // another that doesn't. 91 type WaitableServer struct { 92 fakeServer 93 94 Started chan struct{} 95 Completed chan error 96 } 97 98 func (s WaitableServer) Hover(ctx context.Context, _ *protocol.HoverParams) (_ *protocol.Hover, err error) { 99 s.Started <- struct{}{} 100 defer func() { 101 s.Completed <- err 102 }() 103 select { 104 case <-ctx.Done(): 105 return nil, errors.New("cancelled hover") 106 case <-time.After(10 * time.Second): 107 } 108 return &protocol.Hover{}, nil 109 } 110 111 func (s WaitableServer) ResolveCompletionItem(_ context.Context, item *protocol.CompletionItem) (*protocol.CompletionItem, error) { 112 return item, nil 113 } 114 115 func checkClose(t *testing.T, closer func() error) { 116 t.Helper() 117 if err := closer(); err != nil { 118 t.Errorf("closing: %v", err) 119 } 120 } 121 122 func setupForwarding(ctx context.Context, t *testing.T, s protocol.Server) (direct, forwarded servertest.Connector, cleanup func()) { 123 t.Helper() 124 serveCtx := debug.WithInstance(ctx, "") 125 ss := NewStreamServer(cache.New(nil), false, nil).(*StreamServer) 126 ss.serverForTest = s 127 tsDirect := servertest.NewTCPServer(serveCtx, ss, nil) 128 129 forwarder, err := NewForwarder("tcp;"+tsDirect.Addr, nil) 130 if err != nil { 131 t.Fatal(err) 132 } 133 tsForwarded := servertest.NewPipeServer(forwarder, nil) 134 return tsDirect, tsForwarded, func() { 135 checkClose(t, tsDirect.Close) 136 checkClose(t, tsForwarded.Close) 137 } 138 } 139 140 func TestRequestCancellation(t *testing.T) { 141 ctx := context.Background() 142 server := WaitableServer{ 143 Started: make(chan struct{}), 144 Completed: make(chan error), 145 } 146 tsDirect, tsForwarded, cleanup := setupForwarding(ctx, t, server) 147 defer cleanup() 148 tests := []struct { 149 serverType string 150 ts servertest.Connector 151 }{ 152 {"direct", tsDirect}, 153 {"forwarder", tsForwarded}, 154 } 155 156 for _, test := range tests { 157 t.Run(test.serverType, func(t *testing.T) { 158 cc := test.ts.Connect(ctx) 159 sd := protocol.ServerDispatcher(cc) 160 cc.Go(ctx, 161 protocol.Handlers( 162 jsonrpc2.MethodNotFound)) 163 164 ctx := context.Background() 165 ctx, cancel := context.WithCancel(ctx) 166 167 result := make(chan error) 168 go func() { 169 _, err := sd.Hover(ctx, &protocol.HoverParams{}) 170 result <- err 171 }() 172 // Wait for the Hover request to start. 173 <-server.Started 174 cancel() 175 if err := <-result; err == nil { 176 t.Error("nil error for cancelled Hover(), want non-nil") 177 } 178 if err := <-server.Completed; err == nil || !strings.Contains(err.Error(), "cancelled hover") { 179 t.Errorf("Hover(): unexpected server-side error %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: fake.UnpackTxt(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(nil) 221 ss := NewStreamServer(cache, false, nil) 222 tsBackend := servertest.NewTCPServer(serverCtx, ss, nil) 223 224 forwarder, err := NewForwarder("tcp;"+tsBackend.Addr, nil) 225 if err != nil { 226 t.Fatal(err) 227 } 228 tsForwarder := servertest.NewPipeServer(forwarder, nil) 229 230 const skipApplyEdits = false 231 ed1, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(clientCtx, tsForwarder, fake.ClientHooks{}, skipApplyEdits) 232 if err != nil { 233 t.Fatal(err) 234 } 235 defer ed1.Close(clientCtx) 236 ed2, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(baseCtx, tsBackend, fake.ClientHooks{}, skipApplyEdits) 237 if err != nil { 238 t.Fatal(err) 239 } 240 defer ed2.Close(baseCtx) 241 242 serverDebug := debug.GetInstance(serverCtx) 243 if got, want := len(serverDebug.State.Clients()), 2; got != want { 244 t.Errorf("len(server:Clients) = %d, want %d", got, want) 245 } 246 if got, want := len(serverDebug.State.Sessions()), 2; got != want { 247 t.Errorf("len(server:Sessions) = %d, want %d", got, want) 248 } 249 clientDebug := debug.GetInstance(clientCtx) 250 if got, want := len(clientDebug.State.Servers()), 1; got != want { 251 t.Errorf("len(client:Servers) = %d, want %d", got, want) 252 } 253 // Close one of the connections to verify that the client and session were 254 // dropped. 255 if err := ed1.Close(clientCtx); err != nil { 256 t.Fatal(err) 257 } 258 /*TODO: at this point we have verified the editor is closed 259 However there is no way currently to wait for all associated go routines to 260 go away, and we need to wait for those to trigger the client drop 261 for now we just give it a little bit of time, but we need to fix this 262 in a principled way 263 */ 264 start := time.Now() 265 delay := time.Millisecond 266 const maxWait = time.Second 267 for len(serverDebug.State.Clients()) > 1 { 268 if time.Since(start) > maxWait { 269 break 270 } 271 time.Sleep(delay) 272 delay *= 2 273 } 274 if got, want := len(serverDebug.State.Clients()), 1; got != want { 275 t.Errorf("len(server:Clients) = %d, want %d", got, want) 276 } 277 if got, want := len(serverDebug.State.Sessions()), 1; got != want { 278 t.Errorf("len(server:Sessions()) = %d, want %d", got, want) 279 } 280 } 281 282 type initServer struct { 283 fakeServer 284 285 params *protocol.ParamInitialize 286 } 287 288 func (s *initServer) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) { 289 s.params = params 290 return &protocol.InitializeResult{}, nil 291 } 292 293 func TestEnvForwarding(t *testing.T) { 294 testenv.NeedsTool(t, "go") 295 296 ctx := context.Background() 297 298 server := &initServer{} 299 _, tsForwarded, cleanup := setupForwarding(ctx, t, server) 300 defer cleanup() 301 302 conn := tsForwarded.Connect(ctx) 303 conn.Go(ctx, jsonrpc2.MethodNotFound) 304 dispatch := protocol.ServerDispatcher(conn) 305 initParams := &protocol.ParamInitialize{} 306 initParams.InitializationOptions = map[string]interface{}{ 307 "env": map[string]interface{}{ 308 "GONOPROXY": "example.com", 309 }, 310 } 311 _, err := dispatch.Initialize(ctx, initParams) 312 if err != nil { 313 t.Fatal(err) 314 } 315 if server.params == nil { 316 t.Fatalf("initialize params are unset") 317 } 318 env := server.params.InitializationOptions.(map[string]interface{})["env"].(map[string]interface{}) 319 320 // Check for an arbitrary Go variable. It should be set. 321 if _, ok := env["GOPRIVATE"]; !ok { 322 t.Errorf("Go environment variable GOPRIVATE unset in initialization options") 323 } 324 // Check that the variable present in our user config was not overwritten. 325 if v := env["GONOPROXY"]; v != "example.com" { 326 t.Errorf("GONOPROXY environment variable was overwritten") 327 } 328 } 329 330 func TestListenParsing(t *testing.T) { 331 tests := []struct { 332 input, wantNetwork, wantAddr string 333 }{ 334 {"127.0.0.1:0", "tcp", "127.0.0.1:0"}, 335 {"unix;/tmp/sock", "unix", "/tmp/sock"}, 336 {"auto", "auto", ""}, 337 {"auto;foo", "auto", "foo"}, 338 } 339 340 for _, test := range tests { 341 gotNetwork, gotAddr := ParseAddr(test.input) 342 if gotNetwork != test.wantNetwork { 343 t.Errorf("network = %q, want %q", gotNetwork, test.wantNetwork) 344 } 345 if gotAddr != test.wantAddr { 346 t.Errorf("addr = %q, want %q", gotAddr, test.wantAddr) 347 } 348 } 349 } 350 351 // For #59479, verify that empty slices are serialized as []. 352 func TestEmptySlices(t *testing.T) { 353 // The LSP would prefer that empty slices be sent as [] rather than null. 354 const bad = `{"a":null}` 355 const good = `{"a":[]}` 356 var x struct { 357 A []string `json:"a"` 358 } 359 buf, _ := json.Marshal(x) 360 if string(buf) != bad { 361 // uninitialized is ezpected to give null 362 t.Errorf("unexpectedly got %s, want %s", buf, bad) 363 } 364 x.A = make([]string, 0) 365 buf, _ = json.Marshal(x) 366 if string(buf) != good { 367 // expect [] 368 t.Errorf("unexpectedly got %s, want %s", buf, good) 369 } 370 x.A = []string{} 371 buf, _ = json.Marshal(x) 372 if string(buf) != good { 373 // expect [] 374 t.Errorf("unexpectedly got %s, want %s", buf, good) 375 } 376 }