github.com/saibing/bingo@v0.0.0-20190331051950-76bcd777316d/langserver/lsp_test.go (about)

     1  package langserver
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"flag"
     8  	"fmt"
     9  	"log"
    10  	"net"
    11  	"os"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  	"testing"
    16  
    17  	"golang.org/x/tools/go/packages/packagestest"
    18  
    19  	"github.com/saibing/bingo/langserver/internal/cache"
    20  	"github.com/saibing/bingo/langserver/internal/util"
    21  
    22  	"github.com/sourcegraph/go-lsp"
    23  	"github.com/sourcegraph/jsonrpc2"
    24  
    25  	_ "net/http/pprof"
    26  )
    27  
    28  const (
    29  	goroot         = "goroot"
    30  	gomodule       = "gomodule"
    31  	rootImportPath = "github.com/saibing/bingo/langserver/test/pkg"
    32  )
    33  
    34  var (
    35  	gopathDir    = getGOPATH()
    36  	githubModule = "pkg/mod/github.com/saibing/dep@v1.0.2"
    37  	gomoduleDir  = filepath.Join(gopathDir, githubModule)
    38  )
    39  
    40  func getGOPATH() string {
    41  	gopath := os.Getenv("GOPATH")
    42  	if gopath == "" {
    43  		return filepath.Join(os.Getenv("HOME"), "go")
    44  	}
    45  
    46  	paths := strings.Split(gopath, string(os.PathListSeparator))
    47  	return paths[0]
    48  }
    49  
    50  func TestMain(m *testing.M) {
    51  	flag.Parse()
    52  	code := m.Run()
    53  	tearDown()
    54  	os.Exit(code)
    55  }
    56  
    57  func tearDown() {
    58  	completionContext.tearDown()
    59  	definitionContext.tearDown()
    60  	symbolContext.tearDown()
    61  	formatContext.tearDown()
    62  	hoverContext.tearDown()
    63  	implementationContext.tearDown()
    64  	referencesContext.tearDown()
    65  	renameContext.tearDown()
    66  	signatureContext.tearDown()
    67  	typeDefinitionContext.tearDown()
    68  	workspaceReferencesContext.tearDown()
    69  	workspaceSymbolContext.tearDown()
    70  	xDefinitionContext.tearDown()
    71  }
    72  
    73  type TestContext struct {
    74  	h          jsonrpc2.Handler
    75  	conn       *jsonrpc2.Conn
    76  	connServer *jsonrpc2.Conn
    77  	ctx        context.Context
    78  	exported   *packagestest.Exported
    79  }
    80  
    81  func newTestContext(style cache.CacheStyle) *TestContext {
    82  	cfg := NewDefaultConfig()
    83  	cfg.DisableFuncSnippet = false
    84  	cfg.GlobalCacheStyle = string(style)
    85  
    86  	h := NewHandler(cfg)
    87  	ctx := context.Background()
    88  	return &TestContext{
    89  		h:   h,
    90  		ctx: ctx,
    91  	}
    92  }
    93  
    94  func (tx *TestContext) setup(t *testing.T) {
    95  	t.Helper()
    96  	tx.exported = packagestest.Export(t, packagestest.Modules, testdata)
    97  	tx.initServer(t)
    98  }
    99  
   100  func (tx *TestContext) tearDown() {
   101  	if tx.exported != nil {
   102  		fmt.Printf("clean up module project %s\n", tx.root())
   103  		tx.exported.Cleanup()
   104  	}
   105  
   106  	if tx.conn != nil {
   107  		if err := tx.conn.Close(); err != nil {
   108  			log.Fatal("conn.Close:", err)
   109  		}
   110  	}
   111  
   112  	if tx.connServer != nil {
   113  		if err := tx.connServer.Close(); err != nil {
   114  			log.Fatal("connServer.Close:", err)
   115  		}
   116  	}
   117  }
   118  
   119  func (tx *TestContext) root() string {
   120  	return tx.exported.Config.Dir
   121  }
   122  
   123  func (tx *TestContext) initServer(t *testing.T) {
   124  	t.Helper()
   125  	rootDir := tx.root()
   126  	os.Chdir(rootDir)
   127  	rootURI := util.PathToURI(filepath.ToSlash(rootDir))
   128  	t.Logf("rootUri:=%q", rootURI)
   129  
   130  	// Prepare the connection.
   131  	client, server := net.Pipe()
   132  	tx.connServer = jsonrpc2.NewConn(tx.ctx, jsonrpc2.NewBufferedStream(server, jsonrpc2.VSCodeObjectCodec{}), tx.h)
   133  	tx.conn = jsonrpc2.NewConn(tx.ctx, jsonrpc2.NewBufferedStream(client, jsonrpc2.VSCodeObjectCodec{}), tx.h)
   134  
   135  	tdCap := lsp.TextDocumentClientCapabilities{}
   136  	tdCap.Completion.CompletionItemKind.ValueSet = []lsp.CompletionItemKind{lsp.CIKConstant}
   137  	params := InitializeParams{
   138  		InitializeParams: lsp.InitializeParams{
   139  			RootURI:      rootURI,
   140  			Capabilities: lsp.ClientCapabilities{TextDocument: tdCap},
   141  		},
   142  
   143  		RootImportPath: rootImportPath,
   144  	}
   145  	if err := tx.conn.Call(tx.ctx, "initialize", params, nil); err != nil {
   146  		t.Fatal("conn.Call initialize:", err)
   147  	}
   148  }
   149  
   150  // tbRun calls (testing.T).Run or (testing.B).Run.
   151  func tbRun(t testing.TB, name string, f func(testing.TB)) bool {
   152  	t.Helper()
   153  	switch tb := t.(type) {
   154  	case *testing.B:
   155  		return tb.Run(name, func(b *testing.B) {
   156  			b.Helper()
   157  			f(b)
   158  		})
   159  	case *testing.T:
   160  		return tb.Run(name, func(t *testing.T) {
   161  			t.Helper()
   162  			f(t)
   163  		})
   164  	default:
   165  		panic(fmt.Sprintf("unexpected %T, want *testing.B or *testing.T", tb))
   166  	}
   167  }
   168  
   169  func parsePos(s string) (file string, line, char int, err error) {
   170  	parts := strings.Split(s, ":")
   171  	if len(parts) != 3 {
   172  		err = fmt.Errorf("invalid pos %q (%d parts)", s, len(parts))
   173  		return
   174  	}
   175  	file = parts[0]
   176  	line, err = strconv.Atoi(parts[1])
   177  	if err != nil {
   178  		err = fmt.Errorf("invalid line in %q: %s", s, err)
   179  		return
   180  	}
   181  	char, err = strconv.Atoi(parts[2])
   182  	if err != nil {
   183  		err = fmt.Errorf("invalid char in %q: %s", s, err)
   184  		return
   185  	}
   186  	return file, line - 1, char - 1, nil // LSP is 0-indexed
   187  }
   188  
   189  func uriJoin(base lsp.DocumentURI, file string) lsp.DocumentURI {
   190  	return lsp.DocumentURI(string(base) + "/" + file)
   191  }
   192  
   193  func qualifiedName(s lsp.SymbolInformation) string {
   194  	if s.ContainerName != "" {
   195  		return s.ContainerName + "." + s.Name
   196  	}
   197  	return s.Name
   198  }
   199  
   200  type markedStrings []lsp.MarkedString
   201  
   202  func (v *markedStrings) UnmarshalJSON(data []byte) error {
   203  	if len(data) == 0 {
   204  		return errors.New("invalid empty JSON")
   205  	}
   206  	if data[0] == '[' {
   207  		var ms []markedString
   208  		if err := json.Unmarshal(data, &ms); err != nil {
   209  			return err
   210  		}
   211  		for _, ms := range ms {
   212  			*v = append(*v, lsp.MarkedString(ms))
   213  		}
   214  		return nil
   215  	}
   216  	*v = []lsp.MarkedString{{}}
   217  	return json.Unmarshal(data, &(*v)[0])
   218  }
   219  
   220  type markedString lsp.MarkedString
   221  
   222  func (v *markedString) UnmarshalJSON(data []byte) error {
   223  	if len(data) == 0 {
   224  		return errors.New("invalid empty JSON")
   225  	}
   226  	if data[0] == '{' {
   227  		return json.Unmarshal(data, (*lsp.MarkedString)(v))
   228  	}
   229  
   230  	// String
   231  	*v = markedString{}
   232  	return json.Unmarshal(data, &v.Value)
   233  }
   234  
   235  type locations []lsp.Location
   236  
   237  func (v *locations) UnmarshalJSON(data []byte) error {
   238  	if len(data) == 0 {
   239  		return errors.New("invalid empty JSON")
   240  	}
   241  	if data[0] == '[' {
   242  		return json.Unmarshal(data, (*[]lsp.Location)(v))
   243  	}
   244  	*v = []lsp.Location{{}}
   245  	return json.Unmarshal(data, &(*v)[0])
   246  }
   247  
   248  func makePath(elem ...string) string {
   249  	path := filepath.Join(elem...)
   250  	return util.LowerDriver(filepath.ToSlash(path))
   251  }