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  }