Skip to content

Commit 6551be4

Browse files
committed
test(lsp): white-box benchmarks for the JSON-RPC server loop
dispatch + document handlers (didOpen/didChange/didClose), readMessage/ writeMessage, respond/notify, publishDiagnostics, and LSPServe (cancelled-ctx entry). Converted to package core so the unexported server path is reachable. Co-Authored-By: Virgil <virgil@lethean.io>
1 parent 476593e commit 6551be4

1 file changed

Lines changed: 111 additions & 15 deletions

File tree

lsp_bench_test.go

Lines changed: 111 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
// SPDX-License-Identifier: EUPL-1.2
22

3-
// Benchmarks for the LSP diagnostic pipeline in lsp.go. The four
4-
// built-in sources (ax-7 naming, result-shape, spor, test-imports)
5-
// are registered in init(), so LSPComputeDiagnostics exercises the
6-
// whole scan + helper chain on each call. Content is crafted to enter
7-
// the result-shape and import-extraction paths so their helpers run.
3+
// Benchmarks for lsp.go. White-box (package core) so the JSON-RPC server
4+
// loop — dispatch, the document handlers, readMessage/writeMessage,
5+
// respond/notify, publishDiagnostics — can be driven directly. These are
6+
// unexported and editor-rate (fire per keystroke), not reachable from a
7+
// black-box bench. Output goes to Discard so the numbers are the protocol
8+
// machinery, not the terminal.
89
//
9-
// The JSON-RPC server loop (run/readMessage/dispatch/handle*) is driven
10-
// by stdin and is exercised by the functional tests, not benchmarked
11-
// here — it is request-rate bound, not a per-token hot path.
12-
//
13-
// Run: go test -bench='BenchmarkLSP' -benchmem -run='^$' .
14-
15-
package core_test
10+
// Run: go test -bench='BenchmarkLSP|BenchmarkLsp' -benchmem -run='^$' .
1611

17-
import (
18-
. "dappco.re/go"
19-
)
12+
package core
2013

2114
var (
2215
lspSink []LSPDiagnostic
2316
lspSinkStr []string
17+
lspSinkRes Result
2418
)
2519

20+
func lspBenchServer() *lspServer {
21+
return &lspServer{out: Discard, documents: NewRegistry[[]byte]()}
22+
}
23+
24+
// --- diagnostic pipeline (exported) ---
25+
2626
func BenchmarkLSPComputeDiagnostics(b *B) {
2727
uri := "file:///agent/worker.go"
2828
content := []byte(`package worker
@@ -63,3 +63,99 @@ func BenchmarkLSPDiagnosticSources(b *B) {
6363
lspSinkStr = LSPDiagnosticSources()
6464
}
6565
}
66+
67+
// --- JSON-RPC server loop (white-box) ---
68+
69+
func BenchmarkLspServer_dispatch_Initialize(b *B) {
70+
srv := lspBenchServer()
71+
raw := []byte(`{"jsonrpc":"2.0","id":1,"method":"initialize"}`)
72+
b.ReportAllocs()
73+
for i := 0; i < b.N; i++ {
74+
srv.dispatch(raw)
75+
}
76+
}
77+
78+
func BenchmarkLspServer_dispatch_DidOpen(b *B) {
79+
srv := lspBenchServer()
80+
raw := []byte(`{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///x.go","text":"package x\n"}}}`)
81+
b.ReportAllocs()
82+
for i := 0; i < b.N; i++ {
83+
srv.dispatch(raw) // → handleDocumentSync → lspExtractDocument → publishDiagnostics → notify → writeMessage
84+
}
85+
}
86+
87+
func BenchmarkLspServer_dispatch_DidChange(b *B) {
88+
srv := lspBenchServer()
89+
raw := []byte(`{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///x.go"},"contentChanges":[{"text":"package x\n"}]}}`)
90+
b.ReportAllocs()
91+
for i := 0; i < b.N; i++ {
92+
srv.dispatch(raw) // → handleDocumentChange → lspExtractDocumentChange
93+
}
94+
}
95+
96+
func BenchmarkLspServer_dispatch_DidClose(b *B) {
97+
srv := lspBenchServer()
98+
srv.dispatch([]byte(`{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///x.go","text":"package x\n"}}}`))
99+
raw := []byte(`{"jsonrpc":"2.0","method":"textDocument/didClose","params":{"textDocument":{"uri":"file:///x.go"}}}`)
100+
b.ReportAllocs()
101+
for i := 0; i < b.N; i++ {
102+
srv.dispatch(raw) // → handleDocumentClose
103+
}
104+
}
105+
106+
func BenchmarkLspServer_readMessage(b *B) {
107+
frame := "Content-Length: 17\r\n\r\n{\"jsonrpc\":\"2.0\"}"
108+
b.ReportAllocs()
109+
for i := 0; i < b.N; i++ {
110+
srv := lspBenchServer()
111+
srv.in = NewBufReader(NewReader(frame))
112+
lspSinkRes = srv.readMessage()
113+
}
114+
}
115+
116+
func BenchmarkLspServer_writeMessage(b *B) {
117+
srv := lspBenchServer()
118+
payload := lspMessage{JSONRPC: "2.0", Method: "textDocument/publishDiagnostics"}
119+
b.ReportAllocs()
120+
for i := 0; i < b.N; i++ {
121+
lspSinkRes = srv.writeMessage(payload)
122+
}
123+
}
124+
125+
func BenchmarkLspServer_respond(b *B) {
126+
srv := lspBenchServer()
127+
id := 1
128+
b.ReportAllocs()
129+
for i := 0; i < b.N; i++ {
130+
srv.respond(&id, nil, nil)
131+
}
132+
}
133+
134+
func BenchmarkLspServer_notify(b *B) {
135+
srv := lspBenchServer()
136+
b.ReportAllocs()
137+
for i := 0; i < b.N; i++ {
138+
srv.notify("textDocument/publishDiagnostics", nil)
139+
}
140+
}
141+
142+
func BenchmarkLspServer_publishDiagnostics(b *B) {
143+
srv := lspBenchServer()
144+
content := []byte("package x\n")
145+
b.ReportAllocs()
146+
for i := 0; i < b.N; i++ {
147+
srv.publishDiagnostics("file:///x.go", content)
148+
}
149+
}
150+
151+
// LSPServe with an already-cancelled context: the run loop checks ctx
152+
// before reading stdin, so it returns immediately — covers the entry +
153+
// shutdown path without blocking on input.
154+
func BenchmarkLSPServe(b *B) {
155+
b.ReportAllocs()
156+
for i := 0; i < b.N; i++ {
157+
ctx, cancel := WithCancel(Background())
158+
cancel()
159+
lspSinkRes = LSPServe(ctx)
160+
}
161+
}

0 commit comments

Comments
 (0)