turbo-editors/turbo-corepublic Fork 0
28d59854361aeda8541d853093e732126f3d7bff
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 · 157 lines · 3.9 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 16h ago1package app
2
3import (
4 "bufio"
5 "encoding/json"
6 "net"
7 "sync"
8 "testing"
9 "time"
10
11 "codeberg.org/turbo-editors/turbo-core/lsp"
12)
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
24 mu sync.Mutex
25 seen map[string]int
26 answers map[string]json.RawMessage
27}
28
29// frame is one JSON-RPC message, reduced to what this fake needs to look at.
30type frame struct {
31 JSONRPC string `json:"jsonrpc"`
32 ID json.RawMessage `json:"id,omitempty"`
33 Method string `json:"method,omitempty"`
34 Result json.RawMessage `json:"result,omitempty"`
35}
36
37// newFakeLanguage returns an initialised client wired to a fake server. The
38// client is not attached to the app yet — connectLanguage does that, so a test
39// can check what happens on either side of the moment it becomes ready.
40func newFakeLanguage(t *testing.T, _ *App) (*lsp.Client, *fakeLSP) {
41 t.Helper()
42
43 clientSide, serverSide := net.Pipe()
44 server := &fakeLSP{
45 stream: serverSide,
46 reader: bufio.NewReader(serverSide),
47 seen: map[string]int{},
48 answers: map[string]json.RawMessage{},
49 }
50
51 client := lsp.NewClient(clientSide, t.TempDir(), "Turbo Test")
52 go client.Run() //nolint:errcheck // failures surface through the client
53 go server.serve()
54
55 t.Cleanup(func() {
56 clientSide.Close()
57 serverSide.Close()
58 })
59
60 if err := client.Initialize(t.Context()); err != nil {
61 t.Fatalf("Initialize() error = %v", err)
62 }
63 return client, server
64}
65
66// connectLanguage makes the app talk to a client, as a successful start-up
67// would.
68func connectLanguage(a *App, client *lsp.Client) {
69 a.language.attach(client)
70}
71
72// serve answers every request with null and records every method it sees.
73func (s *fakeLSP) serve() {
74 for {
75 body, err := lsp.ReadMessage(s.reader)
76 if err != nil {
77 return
78 }
79
80 var msg frame
81 if err := json.Unmarshal(body, &msg); err != nil {
82 return
83 }
84 s.record(msg.Method)
85
86 if len(msg.ID) == 0 || msg.Method == "" {
87 continue // a notification, or an answer to something we asked
88 }
89 reply, err := json.Marshal(frame{JSONRPC: "2.0", ID: msg.ID, Result: s.answerTo(msg.Method)})
90 if err != nil {
91 return
92 }
93 if err := lsp.WriteMessage(s.stream, reply); err != nil {
94 return
95 }
96 }
97}
98
99// answerTo returns what this server has been told to answer a method with,
100// and null when it has been told nothing — which is what every request got
101// before results could be set, and what most tests still want.
102func (s *fakeLSP) answerTo(method string) json.RawMessage {
103 s.mu.Lock()
104 defer s.mu.Unlock()
105 if answer, set := s.answers[method]; set {
106 return answer
107 }
108 return json.RawMessage("null")
109}
110
111// setAnswer tells the server what to reply to one method.
112func (s *fakeLSP) setAnswer(t *testing.T, method string, result any) {
113 t.Helper()
114
115 encoded, err := json.Marshal(result)
116 if err != nil {
117 t.Fatalf("cannot encode the answer for %s: %v", method, err)
118 }
119 s.mu.Lock()
120 defer s.mu.Unlock()
121 s.answers[method] = encoded
122}
123
124// record counts one method.
125func (s *fakeLSP) record(method string) {
126 if method == "" {
127 return
128 }
129 s.mu.Lock()
130 defer s.mu.Unlock()
131 s.seen[method]++
132}
133
134// methodCount returns how many times the client has sent a method.
135func (s *fakeLSP) methodCount(method string) int {
136 s.mu.Lock()
137 defer s.mu.Unlock()
138 return s.seen[method]
139}
140
141// waitForMethod blocks until the server has seen a method, so a test never
142// races the notification it is about to assert on.
143func waitForMethod(t *testing.T, server *fakeLSP, method string) {
144 t.Helper()
145
146 deadline := time.After(2 * time.Second)
147 for {
148 if server.methodCount(method) > 0 {
149 return
150 }
151 select {
152 case <-deadline:
153 t.Fatalf("the server never saw %q", method)
154 case <-time.After(time.Millisecond):
155 }
156 }
157}