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.
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"
24 type fakeClient struct {
30 func (c fakeClient) LogMessage(ctx context.Context, params *protocol.LogMessageParams) error {
31 c.logs <- params.Message
35 // fakeServer is intended to be embedded in the test fakes below, to trivially
36 // implement Shutdown.
37 type fakeServer struct {
41 func (fakeServer) Shutdown(ctx context.Context) error {
45 type pingServer struct{ fakeServer }
47 func (s pingServer) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
48 event.Log(ctx, "ping")
52 func TestClientLogging(t *testing.T) {
53 ctx, cancel := context.WithCancel(context.Background())
56 server := pingServer{}
57 client := fakeClient{logs: make(chan string, 10)}
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)
65 cc.Go(ctx, protocol.ClientHandler(client, jsonrpc2.MethodNotFound))
67 protocol.ServerDispatcher(cc).DidOpen(ctx, &protocol.DidOpenTextDocumentParams{})
70 case got := <-client.logs:
72 matched, err := regexp.MatchString(want, got)
77 t.Errorf("got log %q, want a log containing %q", got, want)
79 case <-time.After(1 * time.Second):
80 t.Error("timeout waiting for client log")
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 {
93 func (s waitableServer) Hover(ctx context.Context, _ *protocol.HoverParams) (*protocol.Hover, error) {
94 s.started <- struct{}{}
98 case <-time.After(200 * time.Millisecond):
100 return &protocol.Hover{}, nil
103 func (s waitableServer) Resolve(_ context.Context, item *protocol.CompletionItem) (*protocol.CompletionItem, error) {
107 func checkClose(t *testing.T, closer func() error) {
109 if err := closer(); err != nil {
110 t.Errorf("closing: %v", err)
114 func setupForwarding(ctx context.Context, t *testing.T, s protocol.Server) (direct, forwarded servertest.Connector, cleanup func()) {
116 serveCtx := debug.WithInstance(ctx, "", "")
117 ss := NewStreamServer(cache.New(serveCtx, nil), false)
119 tsDirect := servertest.NewTCPServer(serveCtx, ss, nil)
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)
130 func TestRequestCancellation(t *testing.T) {
131 ctx := context.Background()
132 server := waitableServer{
133 started: make(chan struct{}),
135 tsDirect, tsForwarded, cleanup := setupForwarding(ctx, t, server)
139 ts servertest.Connector
141 {"direct", tsDirect},
142 {"forwarder", tsForwarded},
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)
151 jsonrpc2.MethodNotFound))
153 ctx := context.Background()
154 ctx1, cancel1 := context.WithCancel(ctx)
162 _, err1 = sd.Hover(ctx1, &protocol.HoverParams{})
166 _, err2 = sd.Resolve(ctx, &protocol.CompletionItem{})
168 // Wait for the Hover request to start.
173 t.Errorf("cancelled Hover(): got nil err")
176 t.Errorf("uncancelled Hover(): err: %v", err2)
178 if _, err := sd.Resolve(ctx, &protocol.CompletionItem{}); err != nil {
179 t.Errorf("subsequent Hover(): %v", err)
185 const exampleProgram = `
196 fmt.Println("Hello World.")
199 func TestDebugInfoLifecycle(t *testing.T) {
200 sb, err := fake.NewSandbox(&fake.SandboxConfig{Files: exampleProgram})
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
211 t.Logf("closing workspace failed: %v", err)
215 baseCtx, cancel := context.WithCancel(context.Background())
217 clientCtx := debug.WithInstance(baseCtx, "", "")
218 serverCtx := debug.WithInstance(baseCtx, "", "")
220 cache := cache.New(serverCtx, nil)
221 ss := NewStreamServer(cache, false)
222 tsBackend := servertest.NewTCPServer(serverCtx, ss, nil)
224 forwarder := NewForwarder("tcp", tsBackend.Addr)
225 tsForwarder := servertest.NewPipeServer(clientCtx, forwarder, nil)
227 conn1 := tsForwarder.Connect(clientCtx)
228 ed1, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(clientCtx, conn1, fake.ClientHooks{})
232 defer ed1.Close(clientCtx)
233 conn2 := tsBackend.Connect(baseCtx)
234 ed2, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(baseCtx, conn2, fake.ClientHooks{})
238 defer ed2.Close(baseCtx)
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)
244 if got, want := len(serverDebug.State.Sessions()), 2; got != want {
245 t.Errorf("len(server:Sessions) = %d, want %d", got, want)
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)
251 // Close one of the connections to verify that the client and session were
253 if err := ed1.Close(clientCtx); err != nil {
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
263 delay := time.Millisecond
264 const maxWait = time.Second
265 for len(serverDebug.State.Clients()) > 1 {
266 if time.Since(start) > maxWait {
272 if got, want := len(serverDebug.State.Clients()), 1; got != want {
273 t.Errorf("len(server:Clients) = %d, want %d", got, want)
275 if got, want := len(serverDebug.State.Sessions()), 1; got != want {
276 t.Errorf("len(server:Sessions()) = %d, want %d", got, want)
280 type initServer struct {
283 params *protocol.ParamInitialize
286 func (s *initServer) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) {
288 return &protocol.InitializeResult{}, nil
291 func TestEnvForwarding(t *testing.T) {
292 testenv.NeedsGo1Point(t, 13)
293 server := &initServer{}
294 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
296 _, tsForwarded, cleanup := setupForwarding(ctx, t, server)
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",
308 _, err := dispatch.Initialize(ctx, initParams)
312 if server.params == nil {
313 t.Fatalf("initialize params are unset")
315 env := server.params.InitializationOptions.(map[string]interface{})["env"].(map[string]interface{})
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")
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")