github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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  	"errors"
    10  	"regexp"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/powerman/golang-tools/internal/event"
    16  	"github.com/powerman/golang-tools/internal/jsonrpc2"
    17  	"github.com/powerman/golang-tools/internal/jsonrpc2/servertest"
    18  	"github.com/powerman/golang-tools/internal/lsp/cache"
    19  	"github.com/powerman/golang-tools/internal/lsp/debug"
    20  	"github.com/powerman/golang-tools/internal/lsp/fake"
    21  	"github.com/powerman/golang-tools/internal/lsp/protocol"
    22  	"github.com/powerman/golang-tools/internal/testenv"
    23  )
    24  
    25  type FakeClient struct {
    26  	protocol.Client
    27  
    28  	Logs chan string
    29  }
    30  
    31  func (c FakeClient) LogMessage(ctx context.Context, params *protocol.LogMessageParams) error {
    32  	c.Logs <- params.Message
    33  	return nil
    34  }
    35  
    36  // fakeServer is intended to be embedded in the test fakes below, to trivially
    37  // implement Shutdown.
    38  type fakeServer struct {
    39  	protocol.Server
    40  }
    41  
    42  func (fakeServer) Shutdown(ctx context.Context) error {
    43  	return nil
    44  }
    45  
    46  type PingServer struct{ fakeServer }
    47  
    48  func (s PingServer) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
    49  	event.Log(ctx, "ping")
    50  	return nil
    51  }
    52  
    53  func TestClientLogging(t *testing.T) {
    54  	ctx, cancel := context.WithCancel(context.Background())
    55  	defer cancel()
    56  
    57  	server := PingServer{}
    58  	client := FakeClient{Logs: make(chan string, 10)}
    59  
    60  	ctx = debug.WithInstance(ctx, "", "")
    61  	ss := NewStreamServer(cache.New(nil), false)
    62  	ss.serverForTest = server
    63  	ts := servertest.NewPipeServer(ctx, ss, nil)
    64  	defer checkClose(t, ts.Close)
    65  	cc := ts.Connect(ctx)
    66  	cc.Go(ctx, protocol.ClientHandler(client, jsonrpc2.MethodNotFound))
    67  
    68  	if err := protocol.ServerDispatcher(cc).DidOpen(ctx, &protocol.DidOpenTextDocumentParams{}); err != nil {
    69  		t.Errorf("DidOpen: %v", err)
    70  	}
    71  
    72  	select {
    73  	case got := <-client.Logs:
    74  		want := "ping"
    75  		matched, err := regexp.MatchString(want, got)
    76  		if err != nil {
    77  			t.Fatal(err)
    78  		}
    79  		if !matched {
    80  			t.Errorf("got log %q, want a log containing %q", got, want)
    81  		}
    82  	case <-time.After(1 * time.Second):
    83  		t.Error("timeout waiting for client log")
    84  	}
    85  }
    86  
    87  // WaitableServer instruments LSP request so that we can control their timing.
    88  // The requests chosen are arbitrary: we simply needed one that blocks, and
    89  // another that doesn't.
    90  type WaitableServer struct {
    91  	fakeServer
    92  
    93  	Started   chan struct{}
    94  	Completed chan error
    95  }
    96  
    97  func (s WaitableServer) Hover(ctx context.Context, _ *protocol.HoverParams) (_ *protocol.Hover, err error) {
    98  	s.Started <- struct{}{}
    99  	defer func() {
   100  		s.Completed <- err
   101  	}()
   102  	select {
   103  	case <-ctx.Done():
   104  		return nil, errors.New("cancelled hover")
   105  	case <-time.After(10 * time.Second):
   106  	}
   107  	return &protocol.Hover{}, nil
   108  }
   109  
   110  func (s WaitableServer) ResolveCompletionItem(_ context.Context, item *protocol.CompletionItem) (*protocol.CompletionItem, error) {
   111  	return item, nil
   112  }
   113  
   114  func checkClose(t *testing.T, closer func() error) {
   115  	t.Helper()
   116  	if err := closer(); err != nil {
   117  		t.Errorf("closing: %v", err)
   118  	}
   119  }
   120  
   121  func setupForwarding(ctx context.Context, t *testing.T, s protocol.Server) (direct, forwarded servertest.Connector, cleanup func()) {
   122  	t.Helper()
   123  	serveCtx := debug.WithInstance(ctx, "", "")
   124  	ss := NewStreamServer(cache.New(nil), false)
   125  	ss.serverForTest = s
   126  	tsDirect := servertest.NewTCPServer(serveCtx, ss, nil)
   127  
   128  	forwarderCtx := debug.WithInstance(ctx, "", "")
   129  	forwarder, err := NewForwarder("tcp;"+tsDirect.Addr, nil)
   130  	if err != nil {
   131  		t.Fatal(err)
   132  	}
   133  	tsForwarded := servertest.NewPipeServer(forwarderCtx, 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)
   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(clientCtx, forwarder, nil)
   229  
   230  	conn1 := tsForwarder.Connect(clientCtx)
   231  	ed1, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(clientCtx, conn1, fake.ClientHooks{})
   232  	if err != nil {
   233  		t.Fatal(err)
   234  	}
   235  	defer ed1.Close(clientCtx)
   236  	conn2 := tsBackend.Connect(baseCtx)
   237  	ed2, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(baseCtx, conn2, fake.ClientHooks{})
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	defer ed2.Close(baseCtx)
   242  
   243  	serverDebug := debug.GetInstance(serverCtx)
   244  	if got, want := len(serverDebug.State.Clients()), 2; got != want {
   245  		t.Errorf("len(server:Clients) = %d, want %d", got, want)
   246  	}
   247  	if got, want := len(serverDebug.State.Sessions()), 2; got != want {
   248  		t.Errorf("len(server:Sessions) = %d, want %d", got, want)
   249  	}
   250  	clientDebug := debug.GetInstance(clientCtx)
   251  	if got, want := len(clientDebug.State.Servers()), 1; got != want {
   252  		t.Errorf("len(client:Servers) = %d, want %d", got, want)
   253  	}
   254  	// Close one of the connections to verify that the client and session were
   255  	// dropped.
   256  	if err := ed1.Close(clientCtx); err != nil {
   257  		t.Fatal(err)
   258  	}
   259  	/*TODO: at this point we have verified the editor is closed
   260  	However there is no way currently to wait for all associated go routines to
   261  	go away, and we need to wait for those to trigger the client drop
   262  	for now we just give it a little bit of time, but we need to fix this
   263  	in a principled way
   264  	*/
   265  	start := time.Now()
   266  	delay := time.Millisecond
   267  	const maxWait = time.Second
   268  	for len(serverDebug.State.Clients()) > 1 {
   269  		if time.Since(start) > maxWait {
   270  			break
   271  		}
   272  		time.Sleep(delay)
   273  		delay *= 2
   274  	}
   275  	if got, want := len(serverDebug.State.Clients()), 1; got != want {
   276  		t.Errorf("len(server:Clients) = %d, want %d", got, want)
   277  	}
   278  	if got, want := len(serverDebug.State.Sessions()), 1; got != want {
   279  		t.Errorf("len(server:Sessions()) = %d, want %d", got, want)
   280  	}
   281  }
   282  
   283  type initServer struct {
   284  	fakeServer
   285  
   286  	params *protocol.ParamInitialize
   287  }
   288  
   289  func (s *initServer) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) {
   290  	s.params = params
   291  	return &protocol.InitializeResult{}, nil
   292  }
   293  
   294  func TestEnvForwarding(t *testing.T) {
   295  	testenv.NeedsGo1Point(t, 13)
   296  	server := &initServer{}
   297  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
   298  	defer cancel()
   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  }