.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.0 / internal / lsp / lsprpc / lsprpc_test.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.0/internal/lsp/lsprpc/lsprpc_test.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.0/internal/lsp/lsprpc/lsprpc_test.go
new file mode 100644 (file)
index 0000000..1b546e7
--- /dev/null
@@ -0,0 +1,325 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package lsprpc
+
+import (
+       "context"
+       "regexp"
+       "sync"
+       "testing"
+       "time"
+
+       "golang.org/x/tools/internal/event"
+       "golang.org/x/tools/internal/jsonrpc2"
+       "golang.org/x/tools/internal/jsonrpc2/servertest"
+       "golang.org/x/tools/internal/lsp/cache"
+       "golang.org/x/tools/internal/lsp/debug"
+       "golang.org/x/tools/internal/lsp/fake"
+       "golang.org/x/tools/internal/lsp/protocol"
+       "golang.org/x/tools/internal/testenv"
+)
+
+type fakeClient struct {
+       protocol.Client
+
+       logs chan string
+}
+
+func (c fakeClient) LogMessage(ctx context.Context, params *protocol.LogMessageParams) error {
+       c.logs <- params.Message
+       return nil
+}
+
+// fakeServer is intended to be embedded in the test fakes below, to trivially
+// implement Shutdown.
+type fakeServer struct {
+       protocol.Server
+}
+
+func (fakeServer) Shutdown(ctx context.Context) error {
+       return nil
+}
+
+type pingServer struct{ fakeServer }
+
+func (s pingServer) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
+       event.Log(ctx, "ping")
+       return nil
+}
+
+func TestClientLogging(t *testing.T) {
+       ctx, cancel := context.WithCancel(context.Background())
+       defer cancel()
+
+       server := pingServer{}
+       client := fakeClient{logs: make(chan string, 10)}
+
+       ctx = debug.WithInstance(ctx, "", "")
+       ss := NewStreamServer(cache.New(ctx, nil), false)
+       ss.serverForTest = server
+       ts := servertest.NewPipeServer(ctx, ss, nil)
+       defer checkClose(t, ts.Close)
+       cc := ts.Connect(ctx)
+       cc.Go(ctx, protocol.ClientHandler(client, jsonrpc2.MethodNotFound))
+
+       protocol.ServerDispatcher(cc).DidOpen(ctx, &protocol.DidOpenTextDocumentParams{})
+
+       select {
+       case got := <-client.logs:
+               want := "ping"
+               matched, err := regexp.MatchString(want, got)
+               if err != nil {
+                       t.Fatal(err)
+               }
+               if !matched {
+                       t.Errorf("got log %q, want a log containing %q", got, want)
+               }
+       case <-time.After(1 * time.Second):
+               t.Error("timeout waiting for client log")
+       }
+}
+
+// waitableServer instruments LSP request so that we can control their timing.
+// The requests chosen are arbitrary: we simply needed one that blocks, and
+// another that doesn't.
+type waitableServer struct {
+       fakeServer
+
+       started chan struct{}
+}
+
+func (s waitableServer) Hover(ctx context.Context, _ *protocol.HoverParams) (*protocol.Hover, error) {
+       s.started <- struct{}{}
+       select {
+       case <-ctx.Done():
+               return nil, ctx.Err()
+       case <-time.After(200 * time.Millisecond):
+       }
+       return &protocol.Hover{}, nil
+}
+
+func (s waitableServer) Resolve(_ context.Context, item *protocol.CompletionItem) (*protocol.CompletionItem, error) {
+       return item, nil
+}
+
+func checkClose(t *testing.T, closer func() error) {
+       t.Helper()
+       if err := closer(); err != nil {
+               t.Errorf("closing: %v", err)
+       }
+}
+
+func setupForwarding(ctx context.Context, t *testing.T, s protocol.Server) (direct, forwarded servertest.Connector, cleanup func()) {
+       t.Helper()
+       serveCtx := debug.WithInstance(ctx, "", "")
+       ss := NewStreamServer(cache.New(serveCtx, nil), false)
+       ss.serverForTest = s
+       tsDirect := servertest.NewTCPServer(serveCtx, ss, nil)
+
+       forwarderCtx := debug.WithInstance(ctx, "", "")
+       forwarder := NewForwarder("tcp", tsDirect.Addr)
+       tsForwarded := servertest.NewPipeServer(forwarderCtx, forwarder, nil)
+       return tsDirect, tsForwarded, func() {
+               checkClose(t, tsDirect.Close)
+               checkClose(t, tsForwarded.Close)
+       }
+}
+
+func TestRequestCancellation(t *testing.T) {
+       ctx := context.Background()
+       server := waitableServer{
+               started: make(chan struct{}),
+       }
+       tsDirect, tsForwarded, cleanup := setupForwarding(ctx, t, server)
+       defer cleanup()
+       tests := []struct {
+               serverType string
+               ts         servertest.Connector
+       }{
+               {"direct", tsDirect},
+               {"forwarder", tsForwarded},
+       }
+
+       for _, test := range tests {
+               t.Run(test.serverType, func(t *testing.T) {
+                       cc := test.ts.Connect(ctx)
+                       sd := protocol.ServerDispatcher(cc)
+                       cc.Go(ctx,
+                               protocol.Handlers(
+                                       jsonrpc2.MethodNotFound))
+
+                       ctx := context.Background()
+                       ctx1, cancel1 := context.WithCancel(ctx)
+                       var (
+                               err1, err2 error
+                               wg         sync.WaitGroup
+                       )
+                       wg.Add(2)
+                       go func() {
+                               defer wg.Done()
+                               _, err1 = sd.Hover(ctx1, &protocol.HoverParams{})
+                       }()
+                       go func() {
+                               defer wg.Done()
+                               _, err2 = sd.Resolve(ctx, &protocol.CompletionItem{})
+                       }()
+                       // Wait for the Hover request to start.
+                       <-server.started
+                       cancel1()
+                       wg.Wait()
+                       if err1 == nil {
+                               t.Errorf("cancelled Hover(): got nil err")
+                       }
+                       if err2 != nil {
+                               t.Errorf("uncancelled Hover(): err: %v", err2)
+                       }
+                       if _, err := sd.Resolve(ctx, &protocol.CompletionItem{}); err != nil {
+                               t.Errorf("subsequent Hover(): %v", err)
+                       }
+               })
+       }
+}
+
+const exampleProgram = `
+-- go.mod --
+module mod
+
+go 1.12
+-- main.go --
+package main
+
+import "fmt"
+
+func main() {
+       fmt.Println("Hello World.")
+}`
+
+func TestDebugInfoLifecycle(t *testing.T) {
+       sb, err := fake.NewSandbox(&fake.SandboxConfig{Files: exampleProgram})
+       if err != nil {
+               t.Fatal(err)
+       }
+       defer func() {
+               if err := sb.Close(); err != nil {
+                       // TODO(golang/go#38490): we can't currently make this an error because
+                       // it fails on Windows: the workspace directory is still locked by a
+                       // separate Go process.
+                       // Once we have a reliable way to wait for proper shutdown, make this an
+                       // error.
+                       t.Logf("closing workspace failed: %v", err)
+               }
+       }()
+
+       baseCtx, cancel := context.WithCancel(context.Background())
+       defer cancel()
+       clientCtx := debug.WithInstance(baseCtx, "", "")
+       serverCtx := debug.WithInstance(baseCtx, "", "")
+
+       cache := cache.New(serverCtx, nil)
+       ss := NewStreamServer(cache, false)
+       tsBackend := servertest.NewTCPServer(serverCtx, ss, nil)
+
+       forwarder := NewForwarder("tcp", tsBackend.Addr)
+       tsForwarder := servertest.NewPipeServer(clientCtx, forwarder, nil)
+
+       conn1 := tsForwarder.Connect(clientCtx)
+       ed1, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(clientCtx, conn1, fake.ClientHooks{})
+       if err != nil {
+               t.Fatal(err)
+       }
+       defer ed1.Close(clientCtx)
+       conn2 := tsBackend.Connect(baseCtx)
+       ed2, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(baseCtx, conn2, fake.ClientHooks{})
+       if err != nil {
+               t.Fatal(err)
+       }
+       defer ed2.Close(baseCtx)
+
+       serverDebug := debug.GetInstance(serverCtx)
+       if got, want := len(serverDebug.State.Clients()), 2; got != want {
+               t.Errorf("len(server:Clients) = %d, want %d", got, want)
+       }
+       if got, want := len(serverDebug.State.Sessions()), 2; got != want {
+               t.Errorf("len(server:Sessions) = %d, want %d", got, want)
+       }
+       clientDebug := debug.GetInstance(clientCtx)
+       if got, want := len(clientDebug.State.Servers()), 1; got != want {
+               t.Errorf("len(client:Servers) = %d, want %d", got, want)
+       }
+       // Close one of the connections to verify that the client and session were
+       // dropped.
+       if err := ed1.Close(clientCtx); err != nil {
+               t.Fatal(err)
+       }
+       /*TODO: at this point we have verified the editor is closed
+       However there is no way currently to wait for all associated go routines to
+       go away, and we need to wait for those to trigger the client drop
+       for now we just give it a little bit of time, but we need to fix this
+       in a principled way
+       */
+       start := time.Now()
+       delay := time.Millisecond
+       const maxWait = time.Second
+       for len(serverDebug.State.Clients()) > 1 {
+               if time.Since(start) > maxWait {
+                       break
+               }
+               time.Sleep(delay)
+               delay *= 2
+       }
+       if got, want := len(serverDebug.State.Clients()), 1; got != want {
+               t.Errorf("len(server:Clients) = %d, want %d", got, want)
+       }
+       if got, want := len(serverDebug.State.Sessions()), 1; got != want {
+               t.Errorf("len(server:Sessions()) = %d, want %d", got, want)
+       }
+}
+
+type initServer struct {
+       fakeServer
+
+       params *protocol.ParamInitialize
+}
+
+func (s *initServer) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) {
+       s.params = params
+       return &protocol.InitializeResult{}, nil
+}
+
+func TestEnvForwarding(t *testing.T) {
+       testenv.NeedsGo1Point(t, 13)
+       server := &initServer{}
+       ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+       defer cancel()
+       _, tsForwarded, cleanup := setupForwarding(ctx, t, server)
+       defer cleanup()
+
+       conn := tsForwarded.Connect(ctx)
+       conn.Go(ctx, jsonrpc2.MethodNotFound)
+       dispatch := protocol.ServerDispatcher(conn)
+       initParams := &protocol.ParamInitialize{}
+       initParams.InitializationOptions = map[string]interface{}{
+               "env": map[string]interface{}{
+                       "GONOPROXY": "example.com",
+               },
+       }
+       _, err := dispatch.Initialize(ctx, initParams)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if server.params == nil {
+               t.Fatalf("initialize params are unset")
+       }
+       env := server.params.InitializationOptions.(map[string]interface{})["env"].(map[string]interface{})
+
+       // Check for an arbitrary Go variable. It should be set.
+       if _, ok := env["GOPRIVATE"]; !ok {
+               t.Errorf("Go environment variable GOPRIVATE unset in initialization options")
+       }
+       // Check that the variable present in our user config was not overwritten.
+       if v := env["GONOPROXY"]; v != "example.com" {
+               t.Errorf("GONOPROXY environment variable was overwritten")
+       }
+}