github.com/v2fly/tools@v0.100.0/internal/lsp/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 "regexp" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/v2fly/tools/internal/event" 15 "github.com/v2fly/tools/internal/jsonrpc2" 16 "github.com/v2fly/tools/internal/jsonrpc2/servertest" 17 "github.com/v2fly/tools/internal/lsp/cache" 18 "github.com/v2fly/tools/internal/lsp/debug" 19 "github.com/v2fly/tools/internal/lsp/fake" 20 "github.com/v2fly/tools/internal/lsp/protocol" 21 "github.com/v2fly/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(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(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(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 } 326 327 func TestListenParsing(t *testing.T) { 328 tests := []struct { 329 input, wantNetwork, wantAddr string 330 }{ 331 {"127.0.0.1:0", "tcp", "127.0.0.1:0"}, 332 {"unix;/tmp/sock", "unix", "/tmp/sock"}, 333 {"auto", "auto", ""}, 334 {"auto;foo", "auto", "foo"}, 335 } 336 337 for _, test := range tests { 338 gotNetwork, gotAddr := ParseAddr(test.input) 339 if gotNetwork != test.wantNetwork { 340 t.Errorf("network = %q, want %q", gotNetwork, test.wantNetwork) 341 } 342 if gotAddr != test.wantAddr { 343 t.Errorf("addr = %q, want %q", gotAddr, test.wantAddr) 344 } 345 } 346 }