| 🛟 Updated. 28d5985 k33g 19h ago | 1 | package lsp |
| 2 | |
| 3 | import ( |
| 4 | "bufio" |
| 5 | "encoding/json" |
| 6 | "io" |
| 7 | "net" |
| 8 | "sync" |
| 9 | "testing" |
| 10 | "time" |
| 11 | ) |
| 12 | |
| 13 | // message is a JSON-RPC frame as the fake server writes and reads it. |
| 14 | // |
| 15 | // It is the server's own struct rather than the client's, deliberately. This |
| 16 | // file plays the *peer*: a peer that shared the client's types could not catch |
| 17 | // the client encoding a field wrongly, because both sides would be wrong in |
| 18 | // the same way. |
| 19 | type message struct { |
| 20 | JSONRPC string `json:"jsonrpc"` |
| 21 | ID json.RawMessage `json:"id,omitempty"` |
| 22 | Method string `json:"method,omitempty"` |
| 23 | Params json.RawMessage `json:"params,omitempty"` |
| 24 | Result json.RawMessage `json:"result,omitempty"` |
| 25 | Error *ResponseError `json:"error,omitempty"` |
| 26 | } |
| 27 | |
| 28 | // fakeServer is a language server living in this process, at the far end of an |
| 29 | // in-memory pipe. |
| 30 | // |
| 31 | // Testing the client against it exercises the real framing, the real |
| 32 | // concurrency and the real decoding, with no gopls to install, no subprocess |
| 33 | // to reap and no timing to get lucky with. |
| 34 | type fakeServer struct { |
| 35 | t *testing.T |
| 36 | stream net.Conn |
| 37 | reader *bufio.Reader |
| 38 | |
| 39 | replies chan message // answers the client sent to our own requests |
| 40 | |
| 41 | mu sync.Mutex |
| 42 | received []string // the methods the client has sent, in order |
| 43 | |
| 44 | // handle answers a request. Returning a nil result and a nil error sends |
| 45 | // a null result, which is a perfectly ordinary answer. |
| 46 | handle func(method string, params json.RawMessage) (any, *ResponseError) |
| 47 | } |
| 48 | |
| 49 | // newFakeServer returns a client wired to a fake server, both already running. |
| 50 | func newFakeServer(t *testing.T) (*Client, *fakeServer) { |
| 51 | t.Helper() |
| 52 | |
| 53 | clientSide, serverSide := net.Pipe() |
| 54 | server := &fakeServer{ |
| 55 | t: t, |
| 56 | stream: serverSide, |
| 57 | reader: bufio.NewReader(serverSide), |
| 58 | replies: make(chan message, 4), |
| 59 | } |
| 60 | server.handle = func(string, json.RawMessage) (any, *ResponseError) { return nil, nil } |
| 61 | |
| 62 | client := NewClient(clientSide, t.TempDir(), "Turbo Test") |
| 63 | go client.Run() //nolint:errcheck // the test observes failures through the client |
| 64 | go server.serve() |
| 65 | |
| 66 | t.Cleanup(func() { |
| 67 | clientSide.Close() |
| 68 | serverSide.Close() |
| 69 | }) |
| 70 | return client, server |
| 71 | } |
| 72 | |
| 73 | // serve answers messages until the pipe is closed. |
| 74 | func (s *fakeServer) serve() { |
| 75 | for { |
| 76 | body, err := ReadMessage(s.reader) |
| 77 | if err != nil { |
| 78 | return |
| 79 | } |
| 80 | |
| 81 | var msg message |
| 82 | if err := json.Unmarshal(body, &msg); err != nil { |
| 83 | return |
| 84 | } |
| 85 | s.record(msg.Method) |
| 86 | |
| 87 | switch { |
| 88 | case msg.Method == "": |
| 89 | // An answer to something we asked. Only this goroutine ever reads |
| 90 | // the stream, so the waiting test is handed the message instead of |
| 91 | // reading it for itself. |
| 92 | s.replies <- msg |
| 93 | case len(msg.ID) == 0: |
| 94 | // A notification: nothing to answer. |
| 95 | default: |
| 96 | if err := s.reply(msg); err != nil { |
| 97 | return |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | // reply answers one request through the test's handler. |
| 104 | func (s *fakeServer) reply(msg message) error { |
| 105 | result, responseErr := s.handler()(msg.Method, msg.Params) |
| 106 | |
| 107 | reply := message{JSONRPC: "2.0", ID: msg.ID, Error: responseErr} |
| 108 | if responseErr == nil { |
| 109 | encoded, err := json.Marshal(result) |
| 110 | if err != nil { |
| 111 | return err |
| 112 | } |
| 113 | reply.Result = encoded |
| 114 | } |
| 115 | |
| 116 | body, err := json.Marshal(reply) |
| 117 | if err != nil { |
| 118 | return err |
| 119 | } |
| 120 | return WriteMessage(s.stream, body) |
| 121 | } |
| 122 | |
| 123 | // handler returns the current handler under the lock. |
| 124 | func (s *fakeServer) handler() func(string, json.RawMessage) (any, *ResponseError) { |
| 125 | s.mu.Lock() |
| 126 | defer s.mu.Unlock() |
| 127 | return s.handle |
| 128 | } |
| 129 | |
| 130 | // setHandler replaces the request handler. |
| 131 | func (s *fakeServer) setHandler(h func(string, json.RawMessage) (any, *ResponseError)) { |
| 132 | s.mu.Lock() |
| 133 | defer s.mu.Unlock() |
| 134 | s.handle = h |
| 135 | } |
| 136 | |
| 137 | // record notes a method the client sent. |
| 138 | func (s *fakeServer) record(method string) { |
| 139 | if method == "" { |
| 140 | return |
| 141 | } |
| 142 | s.mu.Lock() |
| 143 | defer s.mu.Unlock() |
| 144 | s.received = append(s.received, method) |
| 145 | } |
| 146 | |
| 147 | // methods returns everything the client has sent so far. |
| 148 | func (s *fakeServer) methods() []string { |
| 149 | s.mu.Lock() |
| 150 | defer s.mu.Unlock() |
| 151 | return append([]string(nil), s.received...) |
| 152 | } |
| 153 | |
| 154 | // notify pushes a notification at the client. |
| 155 | func (s *fakeServer) notify(method string, params any) { |
| 156 | encoded, err := json.Marshal(params) |
| 157 | if err != nil { |
| 158 | s.t.Errorf("encoding the notification: %v", err) |
| 159 | return |
| 160 | } |
| 161 | |
| 162 | body, err := json.Marshal(message{JSONRPC: "2.0", Method: method, Params: encoded}) |
| 163 | if err != nil { |
| 164 | s.t.Errorf("encoding the message: %v", err) |
| 165 | return |
| 166 | } |
| 167 | if err := WriteMessage(s.stream, body); err != nil && err != io.ErrClosedPipe { |
| 168 | s.t.Errorf("writing the notification: %v", err) |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | // request asks the client something, the way gopls asks for configuration, and |
| 173 | // returns the client's answer. |
| 174 | func (s *fakeServer) request(method string, params any) (json.RawMessage, *ResponseError) { |
| 175 | encodedParams, err := json.Marshal(params) |
| 176 | if err != nil { |
| 177 | s.t.Fatalf("encoding the parameters: %v", err) |
| 178 | } |
| 179 | |
| 180 | body, err := json.Marshal(message{ |
| 181 | JSONRPC: "2.0", |
| 182 | ID: json.RawMessage(`9001`), |
| 183 | Method: method, |
| 184 | Params: encodedParams, |
| 185 | }) |
| 186 | if err != nil { |
| 187 | s.t.Fatalf("encoding the request: %v", err) |
| 188 | } |
| 189 | if err := WriteMessage(s.stream, body); err != nil { |
| 190 | s.t.Fatalf("writing the request: %v", err) |
| 191 | } |
| 192 | |
| 193 | select { |
| 194 | case reply := <-s.replies: |
| 195 | return reply.Result, reply.Error |
| 196 | case <-time.After(2 * time.Second): |
| 197 | s.t.Fatalf("the client never answered %s", method) |
| 198 | return nil, nil |
| 199 | } |
| 200 | } |