turbo-editors/turbo-corepublic Fork 0
v0.9.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

fakeserver_test.go · 200 lines · 5.2 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 17h ago1package lsp
2
3import (
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.
19type 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.
34type 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.
50func 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.
74func (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.
104func (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.
124func (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.
131func (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.
138func (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.
148func (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.
155func (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.
174func (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}