--- /dev/null
+// Copyright 2019 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.
+
+// Replay logs. See README.md
+package main
+
+import (
+ "bufio"
+ "context"
+ "flag"
+ "fmt"
+ exec "golang.org/x/sys/execabs"
+ "log"
+ "os"
+ "sort"
+ "strconv"
+ "strings"
+
+ "golang.org/x/tools/gopls/integration/parse"
+ "golang.org/x/tools/internal/fakenet"
+ "golang.org/x/tools/internal/jsonrpc2"
+ p "golang.org/x/tools/internal/lsp/protocol"
+)
+
+var (
+ command = flag.String("cmd", "", "location of server to send to, looks for gopls")
+ cmp = flag.Bool("cmp", false, "only compare log and /tmp/seen")
+ logrdr *bufio.Scanner
+ msgs []*parse.Logmsg
+ // requests and responses/errors, by id
+ clreq = make(map[string]*parse.Logmsg)
+ clresp = make(map[string]*parse.Logmsg)
+ svreq = make(map[string]*parse.Logmsg)
+ svresp = make(map[string]*parse.Logmsg)
+)
+
+func main() {
+ log.SetFlags(log.Lshortfile)
+ flag.Usage = func() {
+ fmt.Fprintln(flag.CommandLine.Output(), "replay [options] <logfile>")
+ flag.PrintDefaults()
+ }
+ flag.Parse()
+ if flag.NArg() != 1 {
+ flag.Usage()
+ os.Exit(2)
+ }
+ logf := flag.Arg(0)
+
+ orig, err := parse.ToRlog(logf)
+ if err != nil {
+ log.Fatalf("error parsing logfile %q: %v", logf, err)
+ }
+ ctx := context.Background()
+ msgs = orig.Logs
+ log.Printf("old %d, hist:%s", len(msgs), orig.Histogram)
+
+ if !*cmp {
+ log.Print("calling mimic")
+ mimic(ctx)
+ }
+ seen, err := parse.ToRlog("/tmp/seen")
+ if err != nil {
+ log.Fatal(err)
+ }
+ newMsgs := seen.Logs
+ log.Printf("new %d, hist:%s", len(newMsgs), seen.Histogram)
+
+ ok := make(map[string]int)
+ f := func(x []*parse.Logmsg, label string, diags map[p.DocumentURI][]p.Diagnostic) {
+ counts := make(map[parse.MsgType]int)
+ for _, l := range x {
+ if l.Method == "window/logMessage" {
+ // don't care
+ //continue
+ }
+ if l.Method == "textDocument/publishDiagnostics" {
+ v, ok := l.Body.(*p.PublishDiagnosticsParams)
+ if !ok {
+ log.Fatalf("got %T expected PublishDiagnosticsParams", l.Body)
+ }
+ diags[v.URI] = v.Diagnostics
+ }
+ counts[l.Type]++
+ // notifications only
+ if l.Type != parse.ToServer && l.Type != parse.ToClient {
+ continue
+ }
+ s := fmt.Sprintf("%s %s %s", strings.Replace(l.Hdr, "\r", "", -1), label, l.Type)
+ if i := strings.Index(s, "notification"); i != -1 {
+ s = s[i+12:]
+ }
+ if len(s) > 120 {
+ s = s[:120]
+ }
+ ok[s]++
+ }
+ msg := ""
+ for i := parse.ClRequest; i <= parse.ReportErr; i++ {
+ msg += fmt.Sprintf("%s:%d ", i, counts[i])
+ }
+ log.Printf("%s: %s", label, msg)
+ }
+ mdiags := make(map[p.DocumentURI][]p.Diagnostic)
+ f(msgs, "old", mdiags)
+ vdiags := make(map[p.DocumentURI][]p.Diagnostic)
+ f(newMsgs, "new", vdiags)
+ buf := []string{}
+ for k := range ok {
+ buf = append(buf, fmt.Sprintf("%s %d", k, ok[k]))
+ }
+ if len(buf) > 0 {
+ log.Printf("counts of notifications")
+ sort.Strings(buf)
+ for _, k := range buf {
+ log.Print(k)
+ }
+ }
+ buf = buf[0:0]
+ for k, v := range mdiags {
+ va := vdiags[k]
+ if len(v) != len(va) {
+ buf = append(buf, fmt.Sprintf("new has %d, old has %d for %s",
+ len(va), len(v), k))
+ }
+ }
+ for ka := range vdiags {
+ if _, ok := mdiags[ka]; !ok {
+ buf = append(buf, fmt.Sprintf("new diagnostics, but no old ones, for %s",
+ ka))
+ }
+ }
+ if len(buf) > 0 {
+ log.Print("diagnostics differ:")
+ for _, s := range buf {
+ log.Print(s)
+ }
+ }
+}
+
+func send(ctx context.Context, l *parse.Logmsg, stream jsonrpc2.Stream, id *jsonrpc2.ID) {
+ if id == nil {
+ // need to use the number version of ID
+ n, err := strconv.Atoi(l.ID)
+ if err != nil {
+ n = 0
+ }
+ nid := jsonrpc2.NewIntID(int64(n))
+ id = &nid
+ }
+ var msg jsonrpc2.Message
+ var err error
+ switch l.Type {
+ case parse.ClRequest:
+ msg, err = jsonrpc2.NewCall(*id, l.Method, l.Body)
+ case parse.SvResponse:
+ msg, err = jsonrpc2.NewResponse(*id, l.Body, nil)
+ case parse.ToServer:
+ msg, err = jsonrpc2.NewNotification(l.Method, l.Body)
+ default:
+ log.Fatalf("sending %s", l.Type)
+ }
+ if err != nil {
+ log.Fatal(err)
+ }
+ stream.Write(ctx, msg)
+}
+
+func respond(ctx context.Context, c *jsonrpc2.Call, stream jsonrpc2.Stream) {
+ // c is a server request
+ // pick out the id, and look for the response in msgs
+ id := c.ID()
+ idstr := fmt.Sprint(id)
+ for _, l := range msgs {
+ if l.ID == idstr && l.Type == parse.SvResponse {
+ // check that the methods match?
+ // need to send back the same ID we got.
+ send(ctx, l, stream, &id)
+ return
+ }
+ }
+ log.Fatalf("no response found %q %+v %+v", c.Method(), c.ID(), c)
+}
+
+func findgopls() string {
+ totry := [][]string{{"GOBIN", "/gopls"}, {"GOPATH", "/bin/gopls"}, {"HOME", "/go/bin/gopls"}}
+ // looks in the places go install would install:
+ // GOBIN, else GOPATH/bin, else HOME/go/bin
+ ok := func(s string) bool {
+ fd, err := os.Open(s)
+ if err != nil {
+ return false
+ }
+ fi, err := fd.Stat()
+ if err != nil {
+ return false
+ }
+ return fi.Mode()&0111 != 0
+ }
+ for _, t := range totry {
+ g := os.Getenv(t[0])
+ if g != "" && ok(g+t[1]) {
+ gopls := g + t[1]
+ log.Printf("using gopls at %s", gopls)
+ return gopls
+ }
+ }
+ log.Fatal("could not find gopls")
+ return ""
+}
+
+func mimic(ctx context.Context) {
+ log.Printf("mimic %d", len(msgs))
+ if *command == "" {
+ *command = findgopls()
+ }
+ cmd := exec.Command(*command, "-logfile", "/tmp/seen", "-rpc.trace")
+ toServer, err := cmd.StdinPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fromServer, err := cmd.StdoutPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = cmd.Start()
+ if err != nil {
+ log.Fatal(err)
+ }
+ conn := fakenet.NewConn("stdio", fromServer, toServer)
+ stream := jsonrpc2.NewHeaderStream(conn)
+ rchan := make(chan jsonrpc2.Message, 10) // do we need buffering?
+ rdr := func() {
+ for {
+ msg, _, err := stream.Read(ctx)
+ if err != nil {
+ rchan <- nil // close it instead?
+ return
+ }
+ rchan <- msg
+ }
+ }
+ go rdr()
+ // send as many as possible: all clrequests and toservers up to a clresponse
+ // and loop
+ seenids := make(map[string]bool) // id's that have been responded to:
+big:
+ for _, l := range msgs {
+ switch l.Type {
+ case parse.ToServer: // just send these as we get to them
+ send(ctx, l, stream, nil)
+ case parse.ClRequest:
+ send(ctx, l, stream, nil) // for now, wait for a response, to make sure code is ok
+ fallthrough
+ case parse.ClResponse, parse.ReportErr: // don't go past these until they're received
+ if seenids[l.ID] {
+ break // onward, as it has been received already
+ }
+ done:
+ for {
+ msg := <-rchan
+ if msg == nil {
+ break big
+ }
+ // if it's svrequest, do something
+ // if it's clresponse or reporterr, add to seenids, and if it
+ // is l.id, break out of the loop, and continue the outer loop
+
+ switch msg := msg.(type) {
+ case *jsonrpc2.Call:
+ if parse.FromServer(msg.Method()) {
+ respond(ctx, msg, stream)
+ continue done // still waiting
+ }
+ case *jsonrpc2.Response:
+ id := fmt.Sprint(msg.ID())
+ seenids[id] = true
+ if id == l.ID {
+ break done
+ }
+ }
+ }
+ case parse.SvRequest: // not ours to send
+ continue
+ case parse.SvResponse: // sent by us, if the request arrives
+ continue
+ case parse.ToClient: // we don't send these
+ continue
+ }
+ }
+}