1 // Copyright 2019 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.
5 // Replay logs. See README.md
20 "golang.org/x/tools/gopls/integration/parse"
21 "golang.org/x/tools/internal/fakenet"
22 "golang.org/x/tools/internal/jsonrpc2"
23 p "golang.org/x/tools/internal/lsp/protocol"
27 command = flag.String("cmd", "", "location of server to send to, looks for gopls")
28 cmp = flag.Bool("cmp", false, "only compare log and /tmp/seen")
31 // requests and responses/errors, by id
32 clreq = make(map[string]*parse.Logmsg)
33 clresp = make(map[string]*parse.Logmsg)
34 svreq = make(map[string]*parse.Logmsg)
35 svresp = make(map[string]*parse.Logmsg)
39 log.SetFlags(log.Lshortfile)
41 fmt.Fprintln(flag.CommandLine.Output(), "replay [options] <logfile>")
51 orig, err := parse.ToRlog(logf)
53 log.Fatalf("error parsing logfile %q: %v", logf, err)
55 ctx := context.Background()
57 log.Printf("old %d, hist:%s", len(msgs), orig.Histogram)
60 log.Print("calling mimic")
63 seen, err := parse.ToRlog("/tmp/seen")
68 log.Printf("new %d, hist:%s", len(newMsgs), seen.Histogram)
70 ok := make(map[string]int)
71 f := func(x []*parse.Logmsg, label string, diags map[p.DocumentURI][]p.Diagnostic) {
72 counts := make(map[parse.MsgType]int)
74 if l.Method == "window/logMessage" {
78 if l.Method == "textDocument/publishDiagnostics" {
79 v, ok := l.Body.(*p.PublishDiagnosticsParams)
81 log.Fatalf("got %T expected PublishDiagnosticsParams", l.Body)
83 diags[v.URI] = v.Diagnostics
87 if l.Type != parse.ToServer && l.Type != parse.ToClient {
90 s := fmt.Sprintf("%s %s %s", strings.Replace(l.Hdr, "\r", "", -1), label, l.Type)
91 if i := strings.Index(s, "notification"); i != -1 {
100 for i := parse.ClRequest; i <= parse.ReportErr; i++ {
101 msg += fmt.Sprintf("%s:%d ", i, counts[i])
103 log.Printf("%s: %s", label, msg)
105 mdiags := make(map[p.DocumentURI][]p.Diagnostic)
106 f(msgs, "old", mdiags)
107 vdiags := make(map[p.DocumentURI][]p.Diagnostic)
108 f(newMsgs, "new", vdiags)
111 buf = append(buf, fmt.Sprintf("%s %d", k, ok[k]))
114 log.Printf("counts of notifications")
116 for _, k := range buf {
121 for k, v := range mdiags {
123 if len(v) != len(va) {
124 buf = append(buf, fmt.Sprintf("new has %d, old has %d for %s",
128 for ka := range vdiags {
129 if _, ok := mdiags[ka]; !ok {
130 buf = append(buf, fmt.Sprintf("new diagnostics, but no old ones, for %s",
135 log.Print("diagnostics differ:")
136 for _, s := range buf {
142 func send(ctx context.Context, l *parse.Logmsg, stream jsonrpc2.Stream, id *jsonrpc2.ID) {
144 // need to use the number version of ID
145 n, err := strconv.Atoi(l.ID)
149 nid := jsonrpc2.NewIntID(int64(n))
152 var msg jsonrpc2.Message
155 case parse.ClRequest:
156 msg, err = jsonrpc2.NewCall(*id, l.Method, l.Body)
157 case parse.SvResponse:
158 msg, err = jsonrpc2.NewResponse(*id, l.Body, nil)
160 msg, err = jsonrpc2.NewNotification(l.Method, l.Body)
162 log.Fatalf("sending %s", l.Type)
167 stream.Write(ctx, msg)
170 func respond(ctx context.Context, c *jsonrpc2.Call, stream jsonrpc2.Stream) {
171 // c is a server request
172 // pick out the id, and look for the response in msgs
174 idstr := fmt.Sprint(id)
175 for _, l := range msgs {
176 if l.ID == idstr && l.Type == parse.SvResponse {
177 // check that the methods match?
178 // need to send back the same ID we got.
179 send(ctx, l, stream, &id)
183 log.Fatalf("no response found %q %+v %+v", c.Method(), c.ID(), c)
186 func findgopls() string {
187 totry := [][]string{{"GOBIN", "/gopls"}, {"GOPATH", "/bin/gopls"}, {"HOME", "/go/bin/gopls"}}
188 // looks in the places go install would install:
189 // GOBIN, else GOPATH/bin, else HOME/go/bin
190 ok := func(s string) bool {
191 fd, err := os.Open(s)
199 return fi.Mode()&0111 != 0
201 for _, t := range totry {
203 if g != "" && ok(g+t[1]) {
205 log.Printf("using gopls at %s", gopls)
209 log.Fatal("could not find gopls")
213 func mimic(ctx context.Context) {
214 log.Printf("mimic %d", len(msgs))
216 *command = findgopls()
218 cmd := exec.Command(*command, "-logfile", "/tmp/seen", "-rpc.trace")
219 toServer, err := cmd.StdinPipe()
223 fromServer, err := cmd.StdoutPipe()
231 conn := fakenet.NewConn("stdio", fromServer, toServer)
232 stream := jsonrpc2.NewHeaderStream(conn)
233 rchan := make(chan jsonrpc2.Message, 10) // do we need buffering?
236 msg, _, err := stream.Read(ctx)
238 rchan <- nil // close it instead?
245 // send as many as possible: all clrequests and toservers up to a clresponse
247 seenids := make(map[string]bool) // id's that have been responded to:
249 for _, l := range msgs {
251 case parse.ToServer: // just send these as we get to them
252 send(ctx, l, stream, nil)
253 case parse.ClRequest:
254 send(ctx, l, stream, nil) // for now, wait for a response, to make sure code is ok
256 case parse.ClResponse, parse.ReportErr: // don't go past these until they're received
258 break // onward, as it has been received already
266 // if it's svrequest, do something
267 // if it's clresponse or reporterr, add to seenids, and if it
268 // is l.id, break out of the loop, and continue the outer loop
270 switch msg := msg.(type) {
272 if parse.FromServer(msg.Method()) {
273 respond(ctx, msg, stream)
274 continue done // still waiting
276 case *jsonrpc2.Response:
277 id := fmt.Sprint(msg.ID())
284 case parse.SvRequest: // not ours to send
286 case parse.SvResponse: // sent by us, if the request arrives
288 case parse.ToClient: // we don't send these