turbo-editors/turbo-corepublic Fork 0
v1.0.1
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.

fakelsp_test.go · 193 lines · 5.0 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 17h ago1package app
2
3import (
4 "bufio"
5 "encoding/json"
6 "net"
7 "sync"
8 "testing"
9 "time"
10
📦 Turbo Core f3ade8d k33g 10h ago11 "rickub.com/turbo-editors/turbo-core/lsp"
🛟 Updated. 28d5985 k33g 17h ago12)
13
14// fakeLSP is a language server living in this process, at the far end of an
15// in-memory pipe.
16//
17// The `lsp` package has one of these too, for testing the client. This one
18// exists to test what the *editor* says to a server, which is a different
19// question and belongs here.
20type fakeLSP struct {
21 stream net.Conn
22 reader *bufio.Reader
23
📦 Turbo Core — canonical paths: symbolic links resolved in URIs and document keys (moon-lsp on macOS) b91316e k33g 8h ago24 mu sync.Mutex
25 seen map[string]int
26 answers map[string]json.RawMessage
27 lastOpened json.RawMessage // the params of the last textDocument/didOpen
🛟 Updated. 28d5985 k33g 17h ago28}
29
30// frame is one JSON-RPC message, reduced to what this fake needs to look at.
31type frame struct {
32 JSONRPC string `json:"jsonrpc"`
33 ID json.RawMessage `json:"id,omitempty"`
34 Method string `json:"method,omitempty"`
📦 Turbo Core — canonical paths: symbolic links resolved in URIs and document keys (moon-lsp on macOS) b91316e k33g 8h ago35 Params json.RawMessage `json:"params,omitempty"`
🛟 Updated. 28d5985 k33g 17h ago36 Result json.RawMessage `json:"result,omitempty"`
37}
38
39// newFakeLanguage returns an initialised client wired to a fake server. The
40// client is not attached to the app yet — connectLanguage does that, so a test
41// can check what happens on either side of the moment it becomes ready.
42func newFakeLanguage(t *testing.T, _ *App) (*lsp.Client, *fakeLSP) {
43 t.Helper()
44
45 clientSide, serverSide := net.Pipe()
46 server := &fakeLSP{
47 stream: serverSide,
48 reader: bufio.NewReader(serverSide),
49 seen: map[string]int{},
50 answers: map[string]json.RawMessage{},
51 }
52
53 client := lsp.NewClient(clientSide, t.TempDir(), "Turbo Test")
54 go client.Run() //nolint:errcheck // failures surface through the client
55 go server.serve()
56
57 t.Cleanup(func() {
58 clientSide.Close()
59 serverSide.Close()
60 })
61
62 if err := client.Initialize(t.Context()); err != nil {
63 t.Fatalf("Initialize() error = %v", err)
64 }
65 return client, server
66}
67
68// connectLanguage makes the app talk to a client, as a successful start-up
69// would.
70func connectLanguage(a *App, client *lsp.Client) {
71 a.language.attach(client)
72}
73
74// serve answers every request with null and records every method it sees.
75func (s *fakeLSP) serve() {
76 for {
77 body, err := lsp.ReadMessage(s.reader)
78 if err != nil {
79 return
80 }
81
82 var msg frame
83 if err := json.Unmarshal(body, &msg); err != nil {
84 return
85 }
86 s.record(msg.Method)
📦 Turbo Core — canonical paths: symbolic links resolved in URIs and document keys (moon-lsp on macOS) b91316e k33g 8h ago87 if msg.Method == "textDocument/didOpen" {
88 s.mu.Lock()
89 s.lastOpened = msg.Params
90 s.mu.Unlock()
91 }
🛟 Updated. 28d5985 k33g 17h ago92
93 if len(msg.ID) == 0 || msg.Method == "" {
94 continue // a notification, or an answer to something we asked
95 }
96 reply, err := json.Marshal(frame{JSONRPC: "2.0", ID: msg.ID, Result: s.answerTo(msg.Method)})
97 if err != nil {
98 return
99 }
100 if err := lsp.WriteMessage(s.stream, reply); err != nil {
101 return
102 }
103 }
104}
105
106// answerTo returns what this server has been told to answer a method with,
107// and null when it has been told nothing — which is what every request got
108// before results could be set, and what most tests still want.
109func (s *fakeLSP) answerTo(method string) json.RawMessage {
110 s.mu.Lock()
111 defer s.mu.Unlock()
112 if answer, set := s.answers[method]; set {
113 return answer
114 }
115 return json.RawMessage("null")
116}
117
118// setAnswer tells the server what to reply to one method.
119func (s *fakeLSP) setAnswer(t *testing.T, method string, result any) {
120 t.Helper()
121
122 encoded, err := json.Marshal(result)
123 if err != nil {
124 t.Fatalf("cannot encode the answer for %s: %v", method, err)
125 }
126 s.mu.Lock()
127 defer s.mu.Unlock()
128 s.answers[method] = encoded
129}
130
131// record counts one method.
132func (s *fakeLSP) record(method string) {
133 if method == "" {
134 return
135 }
136 s.mu.Lock()
137 defer s.mu.Unlock()
138 s.seen[method]++
139}
140
📦 Turbo Core — canonical paths: symbolic links resolved in URIs and document keys (moon-lsp on macOS) b91316e k33g 8h ago141// lastOpenedURI returns the URI the client announced in its most recent
142// textDocument/didOpen — the spelling the server was actually given, which is
143// what a server that resolves symbolic links cares about.
144func (s *fakeLSP) lastOpenedURI() string {
145 waitForMethodQuietly(s, "textDocument/didOpen")
146 s.mu.Lock()
147 defer s.mu.Unlock()
148 var params struct {
149 TextDocument struct {
150 URI string `json:"uri"`
151 } `json:"textDocument"`
152 }
153 _ = json.Unmarshal(s.lastOpened, &params)
154 return params.TextDocument.URI
155}
156
157// waitForMethodQuietly is waitForMethod for a caller that reports its own
158// failure: it gives up after two seconds and returns.
159func waitForMethodQuietly(server *fakeLSP, method string) {
160 deadline := time.After(2 * time.Second)
161 for server.methodCount(method) == 0 {
162 select {
163 case <-deadline:
164 return
165 case <-time.After(time.Millisecond):
166 }
167 }
168}
169
🛟 Updated. 28d5985 k33g 17h ago170// methodCount returns how many times the client has sent a method.
171func (s *fakeLSP) methodCount(method string) int {
172 s.mu.Lock()
173 defer s.mu.Unlock()
174 return s.seen[method]
175}
176
177// waitForMethod blocks until the server has seen a method, so a test never
178// races the notification it is about to assert on.
179func waitForMethod(t *testing.T, server *fakeLSP, method string) {
180 t.Helper()
181
182 deadline := time.After(2 * time.Second)
183 for {
184 if server.methodCount(method) > 0 {
185 return
186 }
187 select {
188 case <-deadline:
189 t.Fatalf("the server never saw %q", method)
190 case <-time.After(time.Millisecond):
191 }
192 }
193}