@@ -95,7 +95,7 @@ let extract_anf = ({cstate_desc}) =>
9595let compile_string_to_final_anf = (name, s) =>
9696 extract_anf(compile_string(~hook= stop_after_optimization, ~name, s));
9797
98- let open_process = args => {
98+ let open_process = (~stdin_input =?, args) => {
9999 // We need to run the tests in powershell on Windows to have the correct environment
100100 let program = Sys . win32 ? "powershell.exe" : "/usr/bin/env" ;
101101
@@ -112,6 +112,13 @@ let open_process = args => {
112112 Unix . environment() ,
113113 );
114114
115+ switch (stdin_input) {
116+ | None => ()
117+ | Some (input ) =>
118+ output_string(stdin, input);
119+ flush(stdin);
120+ };
121+
115122 let current_time = Unix . time() ;
116123
117124 let out_eof = ref (false );
@@ -200,6 +207,14 @@ let doc = (file, arguments) => {
200207 (out ++ err, code);
201208};
202209
210+ let lsp = stdin_input => {
211+ let cmd = [| "grain" , "lsp" |] ;
212+
213+ let (code , out , err ) = open_process(~stdin_input, cmd);
214+
215+ (out ++ err, code);
216+ };
217+
203218let module_header = "module Test; " ;
204219
205220let makeSnapshotRunner =
@@ -534,3 +549,155 @@ let makeGrainDocErrorRunner = (test, name, filename, expected, arguments) => {
534549 },
535550 );
536551};
552+
553+ let lsp_request = json => {
554+ let str = Yojson . Safe . to_string(json);
555+ let request_len = String . length(str);
556+ "Content-Length: " ++ string_of_int(request_len) ++ "\r\n " ++ str ++ "\r\n " ;
557+ };
558+
559+ let lsp_expected_response = json => {
560+ let str = Yojson . Safe . to_string(json);
561+ let request_len = String . length(str);
562+ "Content-Length: " ++ string_of_int(request_len) ++ "\r\n\r\n " ++ str;
563+ };
564+
565+ let lsp_input = (method, params) => {
566+ ` Assoc ([
567+ ("jsonrpc" , ` String ("2.0" )),
568+ ("id" , ` Int (1 )),
569+ ("method" , ` String (method)),
570+ ("params" , params),
571+ ] );
572+ };
573+
574+ let lsp_notification = (method, params) => {
575+ ` Assoc ([
576+ ("jsonrpc" , ` String ("2.0" )),
577+ ("method" , ` String (method)),
578+ ("params" , params),
579+ ] );
580+ };
581+
582+ let lsp_success_response = result => {
583+ ` Assoc ([
584+ ("jsonrpc" , ` String ("2.0" )),
585+ ("id" , ` Int (1 )),
586+ ("result" , result),
587+ ("error" , ` Null ),
588+ ] );
589+ };
590+
591+ let lsp_setup_teardown_requests = (code_uri, code) => {
592+ let init_request =
593+ lsp_input(
594+ "initialize" ,
595+ Yojson . Safe . from_string(
596+ {| {"processId":1,"clientInfo":null,"locale":null,"rootUri":null,"trace":"off"}|} ,
597+ ),
598+ );
599+
600+ let open_request =
601+ lsp_input(
602+ "textDocument/didOpen" ,
603+ ` Assoc ([
604+ (
605+ "textDocument" ,
606+ ` Assoc ([
607+ ("uri" , ` String (code_uri)),
608+ ("languageId" , ` String ("grain" )),
609+ ("version" , ` Int (1 )),
610+ ("text" , ` String (code)),
611+ ] ),
612+ ),
613+ ] ),
614+ );
615+
616+ let shutdown_request =
617+ Yojson . Safe . from_string({| {"jsonrpc":"2.0","id":1,"method":"shutdown"}|} );
618+
619+ let exit_request =
620+ Yojson . Safe . from_string({| {"jsonrpc":"2.0","id":1,"method":"exit"}|} );
621+
622+ (
623+ lsp_request(init_request) ++ lsp_request(open_request),
624+ lsp_request(shutdown_request) ++ lsp_request(exit_request),
625+ );
626+ };
627+
628+ let assert_lsp_responses =
629+ (expect, expected_open_diagnostics, ~expected_output=?, result) => {
630+ let expected_init_response =
631+ lsp_expected_response(
632+ lsp_success_response(
633+ Yojson . Safe . from_string(
634+ {| {"capabilities":{"documentFormattingProvider":true,"textDocumentSync":1,"hoverProvider":true,"definitionProvider":{"linkSupport":true},"typeDefinitionProvider":true,"referencesProvider":false,"documentSymbolProvider":false,"codeActionProvider":true,"codeLensProvider":{"resolveProvider":true},"documentHighlightProvider":false,"documentRangeFormattingProvider":false,"renameProvider":false,"inlayHintProvider":{"resolveProvider":false}}}|} ,
635+ ),
636+ ),
637+ );
638+ let expected_open_response =
639+ lsp_expected_response(
640+ lsp_notification(
641+ "textDocument/publishDiagnostics" ,
642+ expected_open_diagnostics,
643+ ),
644+ );
645+ let expected_begin_response =
646+ expected_init_response ++ expected_open_response;
647+
648+ let expected_exit_response =
649+ lsp_expected_response(lsp_success_response(` Null ));
650+
651+ let expected =
652+ switch (expected_output) {
653+ | None => expected_begin_response ++ expected_exit_response
654+ | Some (expected_output ) =>
655+ let expected_response =
656+ lsp_expected_response(lsp_success_response(expected_output));
657+ expected_begin_response ++ expected_response ++ expected_exit_response;
658+ };
659+
660+ expect. string(result).toEqual(expected);
661+ };
662+
663+ let makeLspRunner =
664+ (test, name, code_uri, code, request_params, expected_output) => {
665+ test(
666+ name,
667+ ({expect}) => {
668+ let (setup_request , teardown_request ) =
669+ lsp_setup_teardown_requests(code_uri, code);
670+
671+ let (result , code ) =
672+ lsp(
673+ setup_request ++ lsp_request(request_params) ++ teardown_request,
674+ );
675+
676+ assert_lsp_responses(
677+ expect,
678+ ` Assoc ([ ("uri" , ` String (code_uri)), ("diagnostics" , ` List ([] ))] ),
679+ ~expected_output,
680+ result,
681+ );
682+
683+ expect. int(code).toBe(0 );
684+ },
685+ );
686+ };
687+
688+ let makeLspDiagnosticsRunner =
689+ (test, name, code_uri, code, expected_diagnostics) => {
690+ test(
691+ name,
692+ ({expect}) => {
693+ let (setup_request , teardown_request ) =
694+ lsp_setup_teardown_requests(code_uri, code);
695+
696+ let (result , code ) = lsp(setup_request ++ teardown_request);
697+
698+ assert_lsp_responses(expect, expected_diagnostics, result);
699+
700+ expect. int(code).toBe(0 );
701+ },
702+ );
703+ };
0 commit comments