1
0
mirror of https://github.com/golang/go synced 2024-09-30 14:18:32 -06:00

internal/lsp/debug: move all debug state onto the Instance

For testability, and to support the exchange of debug information across
Forwarder and server, it is helpful to encapsulate all debug information
on the instance object.

This CL moves all state in the debug package into a new 'State' type,
that is added as a field on the debug.Instance. While doing so, common
functionality for object collections is factored out into the objset
helper type.

Also add two new debug object types: Client and Server. These aren't yet
used, but will be in a later CL (and frankly it was easier to leave them
in this CL than to more carefully rewrite history...).

Updates golang/go#34111

Change-Id: Ib809cd14cb957b41a9bcbd94a991f804531a76ea
Reviewed-on: https://go-review.googlesource.com/c/tools/+/220078
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
This commit is contained in:
Rob Findley 2020-02-19 10:17:48 -05:00 committed by Robert Findley
parent 023911ca70
commit e02f5847d1
16 changed files with 382 additions and 179 deletions

View File

@ -41,7 +41,7 @@ func testCommandLine(t *testing.T, exporter packagestest.Exporter) {
}
data := tests.Load(t, exporter, testdata)
ctx := tests.Context(t)
cache := cache.New(commandLineOptions)
cache := cache.New(commandLineOptions, nil)
ss := lsprpc.NewStreamServer(cache, false)
ts := servertest.NewTCPServer(ctx, ss)
for _, data := range data {

View File

@ -19,15 +19,19 @@ import (
"golang.org/x/tools/internal/span"
)
func New(options func(*source.Options)) *Cache {
func New(options func(*source.Options), debugState *debug.State) *Cache {
if debugState == nil {
debugState = &debug.State{}
}
index := atomic.AddInt64(&cacheIndex, 1)
c := &Cache{
fs: &nativeFileSystem{},
id: strconv.FormatInt(index, 10),
fset: token.NewFileSet(),
options: options,
debug: debugState,
}
debug.AddCache(debugCache{c})
debugState.AddCache(debugCache{c})
return c
}
@ -36,6 +40,7 @@ type Cache struct {
id string
fset *token.FileSet
options func(*source.Options)
debug *debug.State
store memoize.Store
}
@ -82,7 +87,7 @@ func (c *Cache) NewSession() *Session {
options: source.DefaultOptions(),
overlays: make(map[span.URI]*overlay),
}
debug.AddSession(debugSession{s})
c.debug.AddSession(debugSession{s})
return s
}

View File

@ -11,7 +11,6 @@ import (
"sync"
"sync/atomic"
"golang.org/x/tools/internal/lsp/debug"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/telemetry/trace"
@ -78,7 +77,7 @@ func (s *Session) Shutdown(ctx context.Context) {
}
s.views = nil
s.viewMap = nil
debug.DropSession(debugSession{s})
s.cache.debug.DropSession(debugSession{s})
}
func (s *Session) Cache() source.Cache {
@ -142,7 +141,7 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
// Initialize the view without blocking.
go v.initialize(xcontext.Detach(ctx), v.snapshot)
debug.AddView(debugView{v})
v.session.cache.debug.AddView(debugView{v})
return v, v.snapshot, nil
}

View File

@ -22,7 +22,6 @@ import (
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/imports"
"golang.org/x/tools/internal/lsp/debug"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/telemetry"
"golang.org/x/tools/internal/memoize"
@ -483,7 +482,7 @@ func (v *view) shutdown(context.Context) {
os.Remove(v.tempMod.Filename())
os.Remove(tempSumFile(v.tempMod.Filename()))
}
debug.DropView(debugView{v})
v.session.cache.debug.DropView(debugView{v})
}
// Ignore checks if the given URI is a URI we ignore.

View File

@ -39,7 +39,7 @@ func TestCapabilities(t *testing.T) {
params.Capabilities.Workspace.Configuration = true
// Send an initialize request to the server.
c.Server = lsp.NewServer(cache.New(app.options).NewSession(), c.Client)
c.Server = lsp.NewServer(cache.New(app.options, nil).NewSession(), c.Client)
result, err := c.Server.Initialize(ctx, params)
if err != nil {
t.Fatal(err)

View File

@ -18,7 +18,6 @@ import (
"os"
"strings"
"sync"
"time"
"golang.org/x/tools/internal/jsonrpc2"
"golang.org/x/tools/internal/lsp"
@ -70,7 +69,7 @@ type Application struct {
// It is primarily to allow the behavior of gopls to be modified by hooks.
PrepareOptions func(*source.Options)
debug debug.Instance
debug *debug.Instance
}
// New returns a new Application ready to run.
@ -132,12 +131,7 @@ gopls flags are:
// If no arguments are passed it will invoke the server sub command, as a
// temporary measure for compatibility.
func (app *Application) Run(ctx context.Context, args ...string) error {
app.debug = debug.Instance{
StartTime: time.Now(),
Workdir: app.wd,
OCAgentConfig: app.OCAgent,
}
app.debug.Prepare(ctx)
app.debug = debug.NewInstance(app.wd, app.OCAgent)
app.Serve.app = app
if len(args) == 0 {
return tool.Run(ctx, &app.Serve, args)
@ -197,7 +191,7 @@ func (app *Application) connect(ctx context.Context) (*connection, error) {
switch {
case app.Remote == "":
connection := newConnection(app)
connection.Server = lsp.NewServer(cache.New(app.options).NewSession(), connection.Client)
connection.Server = lsp.NewServer(cache.New(app.options, nil).NewSession(), connection.Client)
ctx = protocol.WithClient(ctx, connection.Client)
return connection, connection.initialize(ctx, app.options)
case strings.HasPrefix(app.Remote, "internal@"):

View File

@ -46,7 +46,7 @@ func testCommandLine(t *testing.T, exporter packagestest.Exporter) {
}
func testServer(ctx context.Context) *servertest.TCPServer {
cache := cache.New(nil)
cache := cache.New(nil, nil)
ss := lsprpc.NewStreamServer(cache, false)
return servertest.NewTCPServer(ctx, ss)
}

View File

@ -68,7 +68,7 @@ func (s *Serve) Run(ctx context.Context, args ...string) error {
network, addr := parseAddr(s.app.Remote)
ss = lsprpc.NewForwarder(network, addr, true)
} else {
ss = lsprpc.NewStreamServer(cache.New(s.app.options), true)
ss = lsprpc.NewStreamServer(cache.New(s.app.options, s.app.debug.State), true)
}
if s.Address != "" {

View File

@ -35,13 +35,15 @@ import (
"golang.org/x/tools/internal/telemetry/tag"
)
// An Instance holds all debug information associated with a gopls instance.
type Instance struct {
Logfile string
StartTime time.Time
ServerAddress string
DebugAddress string
Workdir string
OCAgentConfig string
Logfile string
StartTime time.Time
ServerAddress string
DebugAddress string
ListenedDebugAddress string
Workdir string
OCAgentConfig string
LogWriter io.Writer
@ -49,14 +51,130 @@ type Instance struct {
prometheus *prometheus.Exporter
rpcs *rpcs
traces *traces
State *State
}
// State holds debugging information related to the server state.
type State struct {
mu sync.Mutex
caches objset
sessions objset
views objset
clients objset
servers objset
}
type ider interface {
ID() string
}
type objset struct {
objs []ider
}
func (s *objset) add(elem ider) {
s.objs = append(s.objs, elem)
}
func (s *objset) drop(elem ider) {
var newobjs []ider
for _, obj := range s.objs {
if obj.ID() != elem.ID() {
newobjs = append(newobjs, obj)
}
}
s.objs = newobjs
}
func (s *objset) find(id string) ider {
for _, e := range s.objs {
if e.ID() == id {
return e
}
}
return nil
}
// Caches returns the set of Cache objects currently being served.
func (st *State) Caches() []Cache {
st.mu.Lock()
defer st.mu.Unlock()
caches := make([]Cache, len(st.caches.objs))
for i, c := range st.caches.objs {
caches[i] = c.(Cache)
}
return caches
}
// Sessions returns the set of Session objects currently being served.
func (st *State) Sessions() []Session {
st.mu.Lock()
defer st.mu.Unlock()
sessions := make([]Session, len(st.sessions.objs))
for i, s := range st.sessions.objs {
sessions[i] = s.(Session)
}
return sessions
}
// Views returns the set of View objects currently being served.
func (st *State) Views() []View {
st.mu.Lock()
defer st.mu.Unlock()
views := make([]View, len(st.views.objs))
for i, v := range st.views.objs {
views[i] = v.(View)
}
return views
}
// Clients returns the set of Clients currently being served.
func (st *State) Clients() []Client {
st.mu.Lock()
defer st.mu.Unlock()
clients := make([]Client, len(st.clients.objs))
for i, c := range st.clients.objs {
clients[i] = c.(Client)
}
return clients
}
// Servers returns the set of Servers the instance is currently connected to.
func (st *State) Servers() []Server {
st.mu.Lock()
defer st.mu.Unlock()
servers := make([]Server, len(st.servers.objs))
for i, s := range st.servers.objs {
servers[i] = s.(Server)
}
return servers
}
// A Client is an incoming connection from a remote client.
type Client interface {
ID() string
Session() Session
DebugAddr() string
Logfile() string
ServerID() string
}
// A Server is an outgoing connection to a remote LSP server.
type Server interface {
ID() string
DebugAddr() string
Logfile() string
ClientID() string
}
// A Cache is an in-memory cache.
type Cache interface {
ID() string
FileSet() *token.FileSet
MemStats() map[reflect.Type]int
}
// A Session is an LSP serving session.
type Session interface {
ID() string
Cache() Cache
@ -64,6 +182,7 @@ type Session interface {
File(hash string) *File
}
// A View is a root directory within a Session.
type View interface {
ID() string
Name() string
@ -71,6 +190,7 @@ type View interface {
Session() Session
}
// A File is is a file within a session.
type File struct {
Session Session
URI span.URI
@ -79,55 +199,91 @@ type File struct {
Hash string
}
var (
mu sync.Mutex
data = struct {
Caches []Cache
Sessions []Session
Views []View
}{}
)
// AddCache adds a cache to the set being served
func AddCache(cache Cache) {
mu.Lock()
defer mu.Unlock()
data.Caches = append(data.Caches, cache)
// AddCache adds a cache to the set being served.
func (st *State) AddCache(cache Cache) {
st.mu.Lock()
defer st.mu.Unlock()
st.caches.add(cache)
}
// DropCache drops a cache from the set being served
func DropCache(cache Cache) {
mu.Lock()
defer mu.Unlock()
//find and remove the cache
if i, _ := findCache(cache.ID()); i >= 0 {
copy(data.Caches[i:], data.Caches[i+1:])
data.Caches[len(data.Caches)-1] = nil
data.Caches = data.Caches[:len(data.Caches)-1]
}
// DropCache drops a cache from the set being served.
func (st *State) DropCache(cache Cache) {
st.mu.Lock()
defer st.mu.Unlock()
st.caches.drop(cache)
}
func findCache(id string) (int, Cache) {
for i, c := range data.Caches {
if c.ID() == id {
return i, c
}
}
return -1, nil
// AddSession adds a session to the set being served.
func (st *State) AddSession(session Session) {
st.mu.Lock()
defer st.mu.Unlock()
st.sessions.add(session)
}
func getCache(r *http.Request) interface{} {
mu.Lock()
defer mu.Unlock()
// DropSession drops a session from the set being served.
func (st *State) DropSession(session Session) {
st.mu.Lock()
defer st.mu.Unlock()
st.sessions.drop(session)
}
// AddView adds a view to the set being served.
func (st *State) AddView(view View) {
st.mu.Lock()
defer st.mu.Unlock()
st.views.add(view)
}
// DropView drops a view from the set being served.
func (st *State) DropView(view View) {
st.mu.Lock()
defer st.mu.Unlock()
st.views.drop(view)
}
// AddClient adds a client to the set being served.
func (st *State) AddClient(client Client) {
st.mu.Lock()
defer st.mu.Unlock()
st.clients.add(client)
}
// DropClient adds a client to the set being served.
func (st *State) DropClient(client Client) {
st.mu.Lock()
defer st.mu.Unlock()
st.clients.drop(client)
}
// AddServer adds a server to the set being queried. In practice, there should
// be at most one remote server.
func (st *State) AddServer(server Server) {
st.mu.Lock()
defer st.mu.Unlock()
st.servers.add(server)
}
// DropServer drops a server to the set being queried.
func (st *State) DropServer(server Server) {
st.mu.Lock()
defer st.mu.Unlock()
st.servers.drop(server)
}
func (i *Instance) getCache(r *http.Request) interface{} {
i.State.mu.Lock()
defer i.State.mu.Unlock()
id := path.Base(r.URL.Path)
result := struct {
Cache
Sessions []Session
}{}
_, result.Cache = findCache(id)
}{
Cache: i.State.caches.find(id).(Cache),
}
// now find all the views that belong to this session
for _, v := range data.Sessions {
for _, vd := range i.State.sessions.objs {
v := vd.(Session)
if v.Cache().ID() == id {
result.Sessions = append(result.Sessions, v)
}
@ -135,28 +291,19 @@ func getCache(r *http.Request) interface{} {
return result
}
func findSession(id string) (int, Session) {
for i, c := range data.Sessions {
if c.ID() == id {
return i, c
}
}
return -1, nil
}
func getSession(r *http.Request) interface{} {
mu.Lock()
defer mu.Unlock()
func (i *Instance) getSession(r *http.Request) interface{} {
i.State.mu.Lock()
defer i.State.mu.Unlock()
id := path.Base(r.URL.Path)
_, session := findSession(id)
result := struct {
Session
Views []View
}{
Session: session,
Session: i.State.sessions.find(id).(Session),
}
// now find all the views that belong to this session
for _, v := range data.Views {
for _, vd := range i.State.views.objs {
v := vd.(View)
if v.Session().ID() == id {
result.Views = append(result.Views, v)
}
@ -164,38 +311,39 @@ func getSession(r *http.Request) interface{} {
return result
}
func findView(id string) (int, View) {
for i, c := range data.Views {
if c.ID() == id {
return i, c
}
}
return -1, nil
}
func getView(r *http.Request) interface{} {
mu.Lock()
defer mu.Unlock()
func (i Instance) getClient(r *http.Request) interface{} {
i.State.mu.Lock()
defer i.State.mu.Unlock()
id := path.Base(r.URL.Path)
_, v := findView(id)
return v
return i.State.clients.find(id).(Client)
}
func getFile(r *http.Request) interface{} {
mu.Lock()
defer mu.Unlock()
func (i Instance) getServer(r *http.Request) interface{} {
i.State.mu.Lock()
defer i.State.mu.Unlock()
id := path.Base(r.URL.Path)
return i.State.servers.find(id).(Server)
}
func (i Instance) getView(r *http.Request) interface{} {
i.State.mu.Lock()
defer i.State.mu.Unlock()
id := path.Base(r.URL.Path)
return i.State.views.find(id).(View)
}
func (i *Instance) getFile(r *http.Request) interface{} {
i.State.mu.Lock()
defer i.State.mu.Unlock()
hash := path.Base(r.URL.Path)
sid := path.Base(path.Dir(r.URL.Path))
_, session := findSession(sid)
return session.File(hash)
return i.State.sessions.find(sid).(Session).File(hash)
}
func (i *Instance) getInfo() dataFunc {
return func(r *http.Request) interface{} {
buf := &bytes.Buffer{}
i.PrintServerInfo(buf)
return template.HTML(buf.String())
}
func (i *Instance) getInfo(r *http.Request) interface{} {
buf := &bytes.Buffer{}
i.PrintServerInfo(buf)
return template.HTML(buf.String())
}
func getMemory(r *http.Request) interface{} {
@ -204,49 +352,13 @@ func getMemory(r *http.Request) interface{} {
return m
}
// AddSession adds a session to the set being served
func AddSession(session Session) {
mu.Lock()
defer mu.Unlock()
data.Sessions = append(data.Sessions, session)
}
// DropSession drops a session from the set being served
func DropSession(session Session) {
mu.Lock()
defer mu.Unlock()
if i, _ := findSession(session.ID()); i >= 0 {
copy(data.Sessions[i:], data.Sessions[i+1:])
data.Sessions[len(data.Sessions)-1] = nil
data.Sessions = data.Sessions[:len(data.Sessions)-1]
// NewInstance creates debug instance ready for use using the supplied configuration.
func NewInstance(workdir, agent string) *Instance {
i := &Instance{
StartTime: time.Now(),
Workdir: workdir,
OCAgentConfig: agent,
}
}
// AddView adds a view to the set being served
func AddView(view View) {
mu.Lock()
defer mu.Unlock()
data.Views = append(data.Views, view)
}
// DropView drops a view from the set being served
func DropView(view View) {
mu.Lock()
defer mu.Unlock()
//find and remove the view
if i, _ := findView(view.ID()); i >= 0 {
copy(data.Views[i:], data.Views[i+1:])
data.Views[len(data.Views)-1] = nil
data.Views = data.Views[:len(data.Views)-1]
}
}
// Prepare gets a debug instance ready for use using the supplied configuration.
func (i *Instance) Prepare(ctx context.Context) {
mu.Lock()
defer mu.Unlock()
i.LogWriter = os.Stderr
ocConfig := ocagent.Discover()
//TODO: we should not need to adjust the discovered configuration
@ -255,9 +367,12 @@ func (i *Instance) Prepare(ctx context.Context) {
i.prometheus = prometheus.New()
i.rpcs = &rpcs{}
i.traces = &traces{}
i.State = &State{}
export.AddExporters(i.ocagent, i.prometheus, i.rpcs, i.traces)
return i
}
// SetLogFile sets the logfile for use with this instance.
func (i *Instance) SetLogFile(logfile string) (func(), error) {
// TODO: probably a better solution for deferring closure to the caller would
// be for the debug instance to itself be closed, but this fixes the
@ -285,8 +400,6 @@ func (i *Instance) SetLogFile(logfile string) (func(), error) {
// It also logs the port the server starts on, to allow for :0 auto assigned
// ports.
func (i *Instance) Serve(ctx context.Context) error {
mu.Lock()
defer mu.Unlock()
if i.DebugAddress == "" {
return nil
}
@ -294,6 +407,7 @@ func (i *Instance) Serve(ctx context.Context) error {
if err != nil {
return err
}
i.ListenedDebugAddress = listener.Addr().String()
port := listener.Addr().(*net.TCPAddr).Port
if strings.HasSuffix(i.DebugAddress, ":0") {
@ -302,7 +416,7 @@ func (i *Instance) Serve(ctx context.Context) error {
log.Print(ctx, "Debug serving", tag.Of("Port", port))
go func() {
mux := http.NewServeMux()
mux.HandleFunc("/", render(mainTmpl, func(*http.Request) interface{} { return data }))
mux.HandleFunc("/", render(mainTmpl, func(*http.Request) interface{} { return i }))
mux.HandleFunc("/debug/", render(debugTmpl, nil))
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
@ -318,11 +432,13 @@ func (i *Instance) Serve(ctx context.Context) error {
if i.traces != nil {
mux.HandleFunc("/trace/", render(traceTmpl, i.traces.getData))
}
mux.HandleFunc("/cache/", render(cacheTmpl, getCache))
mux.HandleFunc("/session/", render(sessionTmpl, getSession))
mux.HandleFunc("/view/", render(viewTmpl, getView))
mux.HandleFunc("/file/", render(fileTmpl, getFile))
mux.HandleFunc("/info", render(infoTmpl, i.getInfo()))
mux.HandleFunc("/cache/", render(cacheTmpl, i.getCache))
mux.HandleFunc("/session/", render(sessionTmpl, i.getSession))
mux.HandleFunc("/view/", render(viewTmpl, i.getView))
mux.HandleFunc("/client/", render(clientTmpl, i.getClient))
mux.HandleFunc("/server/", render(serverTmpl, i.getServer))
mux.HandleFunc("/file/", render(fileTmpl, i.getFile))
mux.HandleFunc("/info", render(infoTmpl, i.getInfo))
mux.HandleFunc("/memory", render(memoryTmpl, getMemory))
if err := http.Serve(listener, mux); err != nil {
log.Error(ctx, "Debug server failed", err)
@ -333,6 +449,7 @@ func (i *Instance) Serve(ctx context.Context) error {
return nil
}
// MonitorMemory starts recording memory statistics each second.
func (i *Instance) MonitorMemory(ctx context.Context) {
tick := time.NewTicker(time.Second)
nextThresholdGiB := uint64(1)
@ -386,6 +503,7 @@ func render(tmpl *template.Template, fun dataFunc) func(http.ResponseWriter, *ht
}
if err := tmpl.Execute(w, data); err != nil {
log.Error(context.Background(), "", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
}
@ -441,23 +559,30 @@ Unknown page
</html>
{{define "cachelink"}}<a href="/cache/{{.}}">Cache {{.}}</a>{{end}}
{{define "clientlink"}}<a href="/client/{{.}}">Client {{.}}</a>{{end}}
{{define "serverlink"}}<a href="/server/{{.}}">Server {{.}}</a>{{end}}
{{define "sessionlink"}}<a href="/session/{{.}}">Session {{.}}</a>{{end}}
{{define "viewlink"}}<a href="/view/{{.}}">View {{.}}</a>{{end}}
{{define "filelink"}}<a href="/file/{{.Session.ID}}/{{.Hash}}">{{.URI}}</a>{{end}}
`)).Funcs(template.FuncMap{
"fuint64": fuint64,
"fuint32": fuint32,
"url": func(s string) template.URL { return template.URL(s) },
})
var mainTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
{{define "title"}}GoPls server information{{end}}
{{define "body"}}
<h2>Caches</h2>
<ul>{{range .Caches}}<li>{{template "cachelink" .ID}}</li>{{end}}</ul>
<ul>{{range .State.Caches}}<li>{{template "cachelink" .ID}}</li>{{end}}</ul>
<h2>Sessions</h2>
<ul>{{range .Sessions}}<li>{{template "sessionlink" .ID}} from {{template "cachelink" .Cache.ID}}</li>{{end}}</ul>
<ul>{{range .State.Sessions}}<li>{{template "sessionlink" .ID}} from {{template "cachelink" .Cache.ID}}</li>{{end}}</ul>
<h2>Views</h2>
<ul>{{range .Views}}<li>{{.Name}} is {{template "viewlink" .ID}} from {{template "sessionlink" .Session.ID}} in {{.Folder}}</li>{{end}}</ul>
<ul>{{range .State.Views}}<li>{{.Name}} is {{template "viewlink" .ID}} from {{template "sessionlink" .Session.ID}} in {{.Folder}}</li>{{end}}</ul>
<h2>Clients</h2>
<ul>{{range .State.Clients}}<li>{{template "clientlink" .ID}}</li>{{end}}</ul>
<h2>Servers</h2>
<ul>{{range .State.Servers}}<li>{{template "serverlink" .ID}}</li>{{end}}</ul>
{{end}}
`))
@ -515,6 +640,23 @@ var cacheTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
{{end}}
`))
var clientTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
{{define "title"}}Client {{.ID}}{{end}}
{{define "body"}}
Using session: <b>{{template "sessionlink" .Session.ID}}</b><br>
Debug this client at: <a href="http://{{url .DebugAddr}}">{{.DebugAddr}}</a><br>
Logfile: {{.Logfile}}<br>
{{end}}
`))
var serverTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
{{define "title"}}Server {{.ID}}{{end}}
{{define "body"}}
Debug this server at: <a href="http://{{.DebugAddr}}">{{.DebugAddr}}</a><br>
Logfile: {{.Logfile}}<br>
{{end}}
`))
var sessionTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
{{define "title"}}Session {{.ID}}{{end}}
{{define "body"}}

View File

@ -0,0 +1,54 @@
// 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 debug
import "testing"
type fakeCache struct {
Cache
id string
}
func (c fakeCache) ID() string {
return c.id
}
func TestState(t *testing.T) {
c1 := fakeCache{id: "1"}
c2 := fakeCache{id: "2"}
c3 := fakeCache{id: "3"}
var s State
s.AddCache(c1)
s.AddCache(c2)
s.AddCache(c3)
compareCaches := func(desc string, want []fakeCache) {
t.Run(desc, func(t *testing.T) {
caches := s.Caches()
if gotLen, wantLen := len(caches), len(want); gotLen != wantLen {
t.Fatalf("len(Caches) = %d, want %d", gotLen, wantLen)
}
for i, got := range caches {
if got != want[i] {
t.Errorf("Caches[%d] = %v, want %v", i, got, want[i])
}
}
})
}
compareCaches("initial load", []fakeCache{c1, c2, c3})
s.DropCache(c2)
compareCaches("dropped cache 2", []fakeCache{c1, c3})
s.DropCache(c2)
compareCaches("duplicate drop", []fakeCache{c1, c3})
s.AddCache(c2)
compareCaches("re-add cache 2", []fakeCache{c1, c3, c2})
s.DropCache(c1)
s.DropCache(c2)
s.DropCache(c3)
compareCaches("drop all", []fakeCache{})
}

View File

@ -49,7 +49,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
for _, datum := range data {
defer datum.Exported.Cleanup()
cache := cache.New(nil)
cache := cache.New(nil, nil)
session := cache.NewSession()
options := tests.DefaultOptions()
session.SetOptions(options)

View File

@ -25,9 +25,10 @@ import (
// streams as a new LSP session, using a shared cache.
type StreamServer struct {
withTelemetry bool
cache *cache.Cache
// accept is mutable for testing.
accept func(protocol.Client) protocol.Server
// If set, serverForTest is used instead of an actual lsp.Server.
serverForTest protocol.Server
}
// NewStreamServer creates a StreamServer using the shared cache. If
@ -36,10 +37,7 @@ type StreamServer struct {
func NewStreamServer(cache *cache.Cache, withTelemetry bool) *StreamServer {
s := &StreamServer{
withTelemetry: withTelemetry,
}
s.accept = func(c protocol.Client) protocol.Server {
session := cache.NewSession()
return lsp.NewServer(session, c)
cache: cache,
}
return s
}
@ -49,7 +47,10 @@ func NewStreamServer(cache *cache.Cache, withTelemetry bool) *StreamServer {
func (s *StreamServer) ServeStream(ctx context.Context, stream jsonrpc2.Stream) error {
conn := jsonrpc2.NewConn(stream)
client := protocol.ClientDispatcher(conn)
server := s.accept(client)
server := s.serverForTest
if server == nil {
server = lsp.NewServer(s.cache.NewSession(), client)
}
conn.AddHandler(protocol.ServerHandler(server))
conn.AddHandler(protocol.Canceller{})
if s.withTelemetry {

View File

@ -12,6 +12,7 @@ import (
"time"
"golang.org/x/tools/internal/jsonrpc2/servertest"
"golang.org/x/tools/internal/lsp/cache"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/telemetry/log"
)
@ -41,11 +42,8 @@ func TestClientLogging(t *testing.T) {
server := pingServer{}
client := fakeClient{logs: make(chan string, 10)}
ss := &StreamServer{
accept: func(c protocol.Client) protocol.Server {
return server
},
}
ss := NewStreamServer(cache.New(nil, nil), false)
ss.serverForTest = server
ts := servertest.NewPipeServer(ctx, ss)
cc := ts.Connect(ctx)
cc.AddHandler(protocol.ClientHandler(client))
@ -94,11 +92,8 @@ func TestRequestCancellation(t *testing.T) {
server := waitableServer{
started: make(chan struct{}),
}
ss := &StreamServer{
accept: func(c protocol.Client) protocol.Server {
return server
},
}
ss := NewStreamServer(cache.New(nil, nil), false)
ss.serverForTest = server
ctx := context.Background()
tsDirect := servertest.NewTCPServer(ctx, ss)
@ -149,4 +144,18 @@ func TestRequestCancellation(t *testing.T) {
}
}
const exampleProgram = `
-- go.mod --
module mod
go 1.12
-- main.go --
package main
import "fmt"
func main() {
fmt.Println("Hello World.")
}`
// TODO: add a test for telemetry.

View File

@ -23,7 +23,7 @@ func TestMain(m *testing.M) {
func TestModfileRemainsUnchanged(t *testing.T) {
ctx := tests.Context(t)
cache := cache.New(nil)
cache := cache.New(nil, nil)
session := cache.NewSession()
options := tests.DefaultOptions()
options.TempModfile = true

View File

@ -79,7 +79,7 @@ func (r *Runner) getTestServer() *servertest.TCPServer {
r.mu.Lock()
defer r.mu.Unlock()
if r.ts == nil {
ss := lsprpc.NewStreamServer(cache.New(nil), false)
ss := lsprpc.NewStreamServer(cache.New(nil, nil), false)
r.ts = servertest.NewTCPServer(context.Background(), ss)
}
return r.ts
@ -184,7 +184,7 @@ func (r *Runner) RunInMode(modes EnvMode, t *testing.T, filedata string, test fu
}
func (r *Runner) singletonEnv(ctx context.Context, t *testing.T) (servertest.Connector, func()) {
ss := lsprpc.NewStreamServer(cache.New(nil), false)
ss := lsprpc.NewStreamServer(cache.New(nil, nil), false)
ts := servertest.NewPipeServer(ctx, ss)
cleanup := func() {
ts.Close()

View File

@ -47,7 +47,7 @@ func testSource(t *testing.T, exporter packagestest.Exporter) {
for _, datum := range data {
defer datum.Exported.Cleanup()
cache := cache.New(nil)
cache := cache.New(nil, nil)
session := cache.NewSession()
options := tests.DefaultOptions()
options.Env = datum.Config.Env