turbo-editors/turbo-corepublic Fork 0
v1.0.2
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 · 219 lines · 5.8 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 18h ago1package app
2
3import (
4 "bufio"
5 "encoding/json"
6 "net"
7 "sync"
8 "testing"
9 "time"
10
📦 Turbo Core f3ade8d k33g 11h ago11 "rickub.com/turbo-editors/turbo-core/lsp"
🛟 Updated. 28d5985 k33g 18h 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 9h ago24 mu sync.Mutex
25 seen map[string]int
📦 Turbo Core — a save that creates a file tells the server (workspace/didChangeWatchedFiles), so moon-lsp diagnoses a new .mbt from its first save 3561e52 k33g 9h ago26 order []string // every method, in the order it arrived
📦 Turbo Core — canonical paths: symbolic links resolved in URIs and document keys (moon-lsp on macOS) b91316e k33g 9h ago27 answers map[string]json.RawMessage
28 lastOpened json.RawMessage // the params of the last textDocument/didOpen
📦 Turbo Core — a save that creates a file tells the server (workspace/didChangeWatchedFiles), so moon-lsp diagnoses a new .mbt from its first save 3561e52 k33g 9h ago29 lastFiles json.RawMessage // the params of the last workspace/didChangeWatchedFiles
🛟 Updated. 28d5985 k33g 18h ago30}
31
32// frame is one JSON-RPC message, reduced to what this fake needs to look at.
33type frame struct {
34 JSONRPC string `json:"jsonrpc"`
35 ID json.RawMessage `json:"id,omitempty"`
36 Method string `json:"method,omitempty"`
📦 Turbo Core — canonical paths: symbolic links resolved in URIs and document keys (moon-lsp on macOS) b91316e k33g 9h ago37 Params json.RawMessage `json:"params,omitempty"`
🛟 Updated. 28d5985 k33g 18h ago38 Result json.RawMessage `json:"result,omitempty"`
39}
40
41// newFakeLanguage returns an initialised client wired to a fake server. The
42// client is not attached to the app yet — connectLanguage does that, so a test
43// can check what happens on either side of the moment it becomes ready.
44func newFakeLanguage(t *testing.T, _ *App) (*lsp.Client, *fakeLSP) {
45 t.Helper()
46
47 clientSide, serverSide := net.Pipe()
48 server := &fakeLSP{
49 stream: serverSide,
50 reader: bufio.NewReader(serverSide),
51 seen: map[string]int{},
52 answers: map[string]json.RawMessage{},
53 }
54
55 client := lsp.NewClient(clientSide, t.TempDir(), "Turbo Test")
56 go client.Run() //nolint:errcheck // failures surface through the client
57 go server.serve()
58
59 t.Cleanup(func() {
60 clientSide.Close()
61 serverSide.Close()
62 })
63
64 if err := client.Initialize(t.Context()); err != nil {
65 t.Fatalf("Initialize() error = %v", err)
66 }
67 return client, server
68}
69
70// connectLanguage makes the app talk to a client, as a successful start-up
71// would.
72func connectLanguage(a *App, client *lsp.Client) {
73 a.language.attach(client)
74}
75
76// serve answers every request with null and records every method it sees.
77func (s *fakeLSP) serve() {
78 for {
79 body, err := lsp.ReadMessage(s.reader)
80 if err != nil {
81 return
82 }
83
84 var msg frame
85 if err := json.Unmarshal(body, &msg); err != nil {
86 return
87 }
88 s.record(msg.Method)
📦 Turbo Core — canonical paths: symbolic links resolved in URIs and document keys (moon-lsp on macOS) b91316e k33g 9h ago89 if msg.Method == "textDocument/didOpen" {
90 s.mu.Lock()
91 s.lastOpened = msg.Params
92 s.mu.Unlock()
93 }
📦 Turbo Core — a save that creates a file tells the server (workspace/didChangeWatchedFiles), so moon-lsp diagnoses a new .mbt from its first save 3561e52 k33g 9h ago94 if msg.Method == "workspace/didChangeWatchedFiles" {
95 s.mu.Lock()
96 s.lastFiles = msg.Params
97 s.mu.Unlock()
98 }
🛟 Updated. 28d5985 k33g 18h ago99
100 if len(msg.ID) == 0 || msg.Method == "" {
101 continue // a notification, or an answer to something we asked
102 }
103 reply, err := json.Marshal(frame{JSONRPC: "2.0", ID: msg.ID, Result: s.answerTo(msg.Method)})
104 if err != nil {
105 return
106 }
107 if err := lsp.WriteMessage(s.stream, reply); err != nil {
108 return
109 }
110 }
111}
112
113// answerTo returns what this server has been told to answer a method with,
114// and null when it has been told nothing — which is what every request got
115// before results could be set, and what most tests still want.
116func (s *fakeLSP) answerTo(method string) json.RawMessage {
117 s.mu.Lock()
118 defer s.mu.Unlock()
119 if answer, set := s.answers[method]; set {
120 return answer
121 }
122 return json.RawMessage("null")
123}
124
125// setAnswer tells the server what to reply to one method.
126func (s *fakeLSP) setAnswer(t *testing.T, method string, result any) {
127 t.Helper()
128
129 encoded, err := json.Marshal(result)
130 if err != nil {
131 t.Fatalf("cannot encode the answer for %s: %v", method, err)
132 }
133 s.mu.Lock()
134 defer s.mu.Unlock()
135 s.answers[method] = encoded
136}
137
138// record counts one method.
139func (s *fakeLSP) record(method string) {
140 if method == "" {
141 return
142 }
143 s.mu.Lock()
144 defer s.mu.Unlock()
145 s.seen[method]++
📦 Turbo Core — a save that creates a file tells the server (workspace/didChangeWatchedFiles), so moon-lsp diagnoses a new .mbt from its first save 3561e52 k33g 9h ago146 s.order = append(s.order, method)
147}
148
149// methodsSeen returns every method the client has sent, in order.
150func (s *fakeLSP) methodsSeen() []string {
151 s.mu.Lock()
152 defer s.mu.Unlock()
153 return append([]string(nil), s.order...)
🛟 Updated. 28d5985 k33g 18h ago154}
155
📦 Turbo Core — canonical paths: symbolic links resolved in URIs and document keys (moon-lsp on macOS) b91316e k33g 9h ago156// lastOpenedURI returns the URI the client announced in its most recent
157// textDocument/didOpen — the spelling the server was actually given, which is
158// what a server that resolves symbolic links cares about.
159func (s *fakeLSP) lastOpenedURI() string {
160 waitForMethodQuietly(s, "textDocument/didOpen")
161 s.mu.Lock()
162 defer s.mu.Unlock()
163 var params struct {
164 TextDocument struct {
165 URI string `json:"uri"`
166 } `json:"textDocument"`
167 }
168 _ = json.Unmarshal(s.lastOpened, &params)
169 return params.TextDocument.URI
170}
171
📦 Turbo Core — a save that creates a file tells the server (workspace/didChangeWatchedFiles), so moon-lsp diagnoses a new .mbt from its first save 3561e52 k33g 9h ago172// lastFileEvents returns the changes the client reported in its most recent
173// workspace/didChangeWatchedFiles.
174func (s *fakeLSP) lastFileEvents() []lsp.FileEvent {
175 waitForMethodQuietly(s, "workspace/didChangeWatchedFiles")
176 s.mu.Lock()
177 defer s.mu.Unlock()
178 var params lsp.DidChangeWatchedFilesParams
179 _ = json.Unmarshal(s.lastFiles, &params)
180 return params.Changes
181}
182
📦 Turbo Core — canonical paths: symbolic links resolved in URIs and document keys (moon-lsp on macOS) b91316e k33g 9h ago183// waitForMethodQuietly is waitForMethod for a caller that reports its own
184// failure: it gives up after two seconds and returns.
185func waitForMethodQuietly(server *fakeLSP, method string) {
186 deadline := time.After(2 * time.Second)
187 for server.methodCount(method) == 0 {
188 select {
189 case <-deadline:
190 return
191 case <-time.After(time.Millisecond):
192 }
193 }
194}
195
🛟 Updated. 28d5985 k33g 18h ago196// methodCount returns how many times the client has sent a method.
197func (s *fakeLSP) methodCount(method string) int {
198 s.mu.Lock()
199 defer s.mu.Unlock()
200 return s.seen[method]
201}
202
203// waitForMethod blocks until the server has seen a method, so a test never
204// races the notification it is about to assert on.
205func waitForMethod(t *testing.T, server *fakeLSP, method string) {
206 t.Helper()
207
208 deadline := time.After(2 * time.Second)
209 for {
210 if server.methodCount(method) > 0 {
211 return
212 }
213 select {
214 case <-deadline:
215 t.Fatalf("the server never saw %q", method)
216 case <-time.After(time.Millisecond):
217 }
218 }
219}