@@ -8,7 +8,6 @@ use anyhow::{bail, Context};
88use pgls_cli:: SocketTransport ;
99#[ cfg( unix) ]
1010use pgls_workspace:: workspace:: { TransportRequest , WorkspaceTransport } ;
11- #[ cfg( unix) ]
1211use serde_json:: Value ;
1312#[ cfg( unix) ]
1413use tokio:: net:: UnixStream ;
@@ -25,6 +24,10 @@ impl flags::LeakCheck {
2524 bail ! ( "`xtask leak-check` is currently implemented only for macOS (`leaks` tool)." ) ;
2625 }
2726
27+ if Command :: new ( "leaks" ) . arg ( "--help" ) . output ( ) . is_err ( ) {
28+ bail ! ( "`leaks` not found — install Xcode Command Line Tools (`xcode-select --install`)" ) ;
29+ }
30+
2831 let iterations = self . iterations . unwrap_or ( DEFAULT_ITERATIONS ) ;
2932 let pause = Duration :: from_millis ( self . pause_ms . unwrap_or ( DEFAULT_PAUSE_MS ) ) ;
3033 let probe = self . probe . unwrap_or_else ( || "lsp" . to_string ( ) ) ;
@@ -126,7 +129,8 @@ fn run_cli_timeout_probe(iterations: usize) -> anyhow::Result<()> {
126129 drop ( stream_b) ;
127130
128131 let ( read, write) = stream_a. into_split ( ) ;
129- let transport = SocketTransport :: open ( runtime, read, write) ;
132+ let transport =
133+ SocketTransport :: open_with_timeout ( runtime, read, write, Duration :: from_millis ( 2 ) ) ;
130134
131135 let mut channel_closed = 0usize ;
132136 let mut timed_out = 0usize ;
@@ -256,38 +260,32 @@ fn wait_for_socket(pid: u32) -> anyhow::Result<()> {
256260fn run_lsp_churn ( stdin : & mut ChildStdin , iterations : usize , pause : Duration ) -> anyhow:: Result < ( ) > {
257261 let uri = "file:///tmp/pgls-leak-check.sql" ;
258262
259- send_lsp_message (
263+ send_lsp_json (
260264 stdin,
261- r#" {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},"rootUri":null}}"# ,
265+ serde_json :: json! ( { "jsonrpc" : "2.0" , "id" : 1 , "method" : "initialize" , "params" : { "capabilities" : { } , "rootUri" : null} } ) ,
262266 ) ?;
263- send_lsp_message (
267+ send_lsp_json (
264268 stdin,
265- r#" {"jsonrpc":"2.0","method":"initialized","params":{}}"# ,
269+ serde_json :: json! ( { "jsonrpc" : "2.0" , "method" : "initialized" , "params" : { } } ) ,
266270 ) ?;
267271
268272 for i in 0 ..iterations {
269- let open_text = json_escape ( & format ! ( "select {i} as value;" ) ) ;
270- let changed_text = json_escape ( & format ! ( "select {i} as value, {} as extra;" , i + 1 ) ) ;
273+ let open_text = format ! ( "select {i} as value;" ) ;
274+ let changed_text = format ! ( "select {i} as value, {} as extra;" , i + 1 ) ;
271275
272- send_lsp_message (
276+ send_lsp_json (
273277 stdin,
274- & format ! (
275- r#"{{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{{"textDocument":{{"uri":"{uri}","languageId":"sql","version":1,"text":"{open_text}"}}}}}}"#
276- ) ,
278+ serde_json:: json!( { "jsonrpc" : "2.0" , "method" : "textDocument/didOpen" , "params" : { "textDocument" : { "uri" : uri, "languageId" : "sql" , "version" : 1 , "text" : open_text} } } ) ,
277279 ) ?;
278280
279- send_lsp_message (
281+ send_lsp_json (
280282 stdin,
281- & format ! (
282- r#"{{"jsonrpc":"2.0","method":"textDocument/didChange","params":{{"textDocument":{{"uri":"{uri}","version":2}},"contentChanges":[{{"text":"{changed_text}"}}]}}}}"#
283- ) ,
283+ serde_json:: json!( { "jsonrpc" : "2.0" , "method" : "textDocument/didChange" , "params" : { "textDocument" : { "uri" : uri, "version" : 2 } , "contentChanges" : [ { "text" : changed_text} ] } } ) ,
284284 ) ?;
285285
286- send_lsp_message (
286+ send_lsp_json (
287287 stdin,
288- & format ! (
289- r#"{{"jsonrpc":"2.0","method":"textDocument/didClose","params":{{"textDocument":{{"uri":"{uri}"}}}}}}"#
290- ) ,
288+ serde_json:: json!( { "jsonrpc" : "2.0" , "method" : "textDocument/didClose" , "params" : { "textDocument" : { "uri" : uri} } } ) ,
291289 ) ?;
292290
293291 thread:: sleep ( pause) ;
@@ -297,15 +295,19 @@ fn run_lsp_churn(stdin: &mut ChildStdin, iterations: usize, pause: Duration) ->
297295}
298296
299297fn send_shutdown_and_exit ( stdin : & mut ChildStdin ) -> anyhow:: Result < ( ) > {
300- send_lsp_message (
298+ send_lsp_json (
299+ stdin,
300+ serde_json:: json!( { "jsonrpc" : "2.0" , "id" : 2 , "method" : "shutdown" , "params" : null} ) ,
301+ ) ?;
302+ send_lsp_json (
301303 stdin,
302- r#" {"jsonrpc":"2.0","id":2," method":"shutdown ","params":null}"# ,
304+ serde_json :: json! ( { "jsonrpc" : "2.0" , "method" : "exit " , "params" : null} ) ,
303305 ) ?;
304- send_lsp_message ( stdin, r#"{"jsonrpc":"2.0","method":"exit","params":null}"# ) ?;
305306 Ok ( ( ) )
306307}
307308
308- fn send_lsp_message ( stdin : & mut ChildStdin , payload : & str ) -> anyhow:: Result < ( ) > {
309+ fn send_lsp_json ( stdin : & mut ChildStdin , value : Value ) -> anyhow:: Result < ( ) > {
310+ let payload = serde_json:: to_string ( & value) . context ( "failed to serialize LSP message" ) ?;
309311 let header = format ! ( "Content-Length: {}\r \n \r \n " , payload. len( ) ) ;
310312 stdin
311313 . write_all ( header. as_bytes ( ) )
@@ -316,21 +318,6 @@ fn send_lsp_message(stdin: &mut ChildStdin, payload: &str) -> anyhow::Result<()>
316318 stdin. flush ( ) . context ( "failed to flush LSP payload" )
317319}
318320
319- fn json_escape ( s : & str ) -> String {
320- let mut out = String :: with_capacity ( s. len ( ) ) ;
321- for c in s. chars ( ) {
322- match c {
323- '"' => out. push_str ( "\\ \" " ) ,
324- '\\' => out. push_str ( "\\ \\ " ) ,
325- '\n' => out. push_str ( "\\ n" ) ,
326- '\r' => out. push_str ( "\\ r" ) ,
327- '\t' => out. push_str ( "\\ t" ) ,
328- _ => out. push ( c) ,
329- }
330- }
331- out
332- }
333-
334321fn run_leaks ( pid : u32 ) -> anyhow:: Result < String > {
335322 let output = Command :: new ( "leaks" )
336323 . arg ( pid. to_string ( ) )
0 commit comments