123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198openCoreopenAsyncopenImportopenVcamlopenVcaml_test_helpers(* The tests in this file demonstrate properties of Neovim's own semantics. VCaml makes
assumptions about how Neovim operates, so if these fail that most likely indicates that
a change to VCaml is necessary. *)lethundred_ms=Time_ns.Span.create~ms:100()let%expect_test"[rpcrequest] blocks other channels"=Expect_test_helpers_async.with_temp_dir(funtmp_dir->letsocket=tmp_dir^/"socket"inlet%bindnvim=(* There is some undocumented internal limit for the socket length (it doesn't
appear in `:h limits`) so to ensure we create a socket we set the working dir
to [tmp_dir] and create the socket with a relative path. *)Process.create_exn()~working_dir:tmp_dir~prog:neovim_path~args:["--headless";"-n";"--clean";"--listen";"./socket"]~env:(`Extend["NVIM_RPLUGIN_MANIFEST","rplugin.vim"])inlet%bind()=with_process_cleanup~name:"nvim"(Process.pidnvim)~f:(fun()->match%bindspin_until_nvim_creates_socket_file(Process.pidnvim)~socketwith|`Nvim_crashedexit_or_signal->return(`Already_reapedexit_or_signal)|`Socket_missing->raise_s[%message"Socket was not created"]|`Socket_created->letblock_nvim~client=letblocking=Ivar.create()inletresult=Ivar.create()inletfunction_name="rpc"inletcall_rpc=(wrap_viml_function~type_:Defun.Vim.(Integer@->String@->Nil@->returnNil)~function_name:"rpcrequest")(Client.channelclient)function_name()inregister_request_blockingclient~name:function_name~type_:Defun.Ocaml.Sync.(Nil@->returnNil)~f:(fun~keyboard_interrupted:_~client:_()->Ivar.fillblocking();Ivar.readresult|>Deferred.ok);letresult_deferred=run_join[%here]clientcall_rpcinlet%map()=Ivar.readblockinginfunresponse->Ivar.fillresultresponse;result_deferredinlet%bindclient1=socket_clientsocket>>|ok_exninlet%bindclient2=socket_clientsocket>>|ok_exninletprint_when_client2_is_unblocked()=don't_wait_for(let%mapresult=run_join[%here]client2(Nvim.eval"'Client 2 is unblocked'"~result_type:String)inprint_s[%sexp(result:stringOr_error.t)])inprint_when_client2_is_unblocked();let%bind()=Clock_ns.afterhundred_msinlet%bind()=Scheduler.yield_until_no_jobs_remain()inprint_s[%message"Blocking nvim (client1)"];let%bindrespond_to_rpc=block_nvim~client:client1inprint_when_client2_is_unblocked();let%bind()=Clock_ns.afterhundred_msinlet%bind()=Scheduler.yield_until_no_jobs_remain()inprint_s[%message"Unblocking nvim (client1)"];let%bind()=respond_to_rpc()>>|ok_exninlet%bind()=Clock_ns.afterhundred_msinlet%bind()=Scheduler.yield_until_no_jobs_remain()inlet%bind()=attempt_to_quit~tmp_dir~client:client2inlet%bind()=Client.closeclient1inlet%bind()=Client.closeclient2inreturn(`Need_to_reap`Patient))in[%expect{|
(Ok "Client 2 is unblocked")
"Blocking nvim (client1)"
"Unblocking nvim (client1)"
(Ok "Client 2 is unblocked")
("nvim exited" (exit_or_signal (Ok ()))) |}];return());;let%expect_test"Plugin dying during [rpcrequest] does not bring down Neovim"=Expect_test_helpers_async.with_temp_dir(funtmp_dir->letsocket=tmp_dir^/"socket"inlet%bindnvim=(* There is some undocumented internal limit for the socket length (it doesn't
appear in `:h limits`) so to ensure we create a socket we set the working dir
to [tmp_dir] and create the socket with a relative path. *)Process.create_exn()~working_dir:tmp_dir~prog:neovim_path~args:["--headless";"-n";"--clean";"--listen";"./socket"]~env:(`Extend["NVIM_RPLUGIN_MANIFEST","rplugin.vim"])inlet%bind()=with_process_cleanup~name:"nvim"(Process.pidnvim)~f:(fun()->match%bindspin_until_nvim_creates_socket_file(Process.pidnvim)~socketwith|`Nvim_crashedexit_or_signal->return(`Already_reapedexit_or_signal)|`Socket_missing->raise_s[%message"Socket was not created"]|`Socket_created->letblock_nvim~client=letblocking=Ivar.create()inletfunction_name="rpc"inletcall_rpc=(wrap_viml_function~type_:Defun.Vim.(Integer@->String@->Nil@->returnNil)~function_name:"rpcrequest")(Client.channelclient)function_name()inregister_request_blockingclient~name:function_name~type_:Defun.Ocaml.Sync.(Nil@->returnNil)~f:(fun~keyboard_interrupted:_~client:_()->Ivar.fillblocking();Deferred.never());don't_wait_for(run_join[%here]clientcall_rpc>>|ok_exn);Ivar.readblockingin(matchCore_unix.fork()with|`In_the_child->Scheduler.reset_in_forked_process();don't_wait_for(let%bindclient=socket_clientsocket>>|ok_exninlet%bind()=block_nvim~clientin(* We don't want to allow the [at_exit] handler that expect test collector
registers to run for this child process. *)Core_unix.exit_immediately0);never_returns(Scheduler.go())|`In_the_parentchild->let%bindexit_or_signal=Unix.waitpidchildinprint_s[%message"child exited"(exit_or_signal:Unix.Exit_or_signal.t)];let%bindclient=socket_clientsocket>>|ok_exninlet%bindresult=run_join[%here]client(Nvim.eval"'nvim is still running'"~result_type:String)inprint_s[%sexp(result:stringOr_error.t)];let%bind()=attempt_to_quit~tmp_dir~clientinlet%bind()=Client.closeclientinreturn(`Need_to_reap`Patient)))in[%expect{|
("child exited" (exit_or_signal (Ok ())))
(Ok "nvim is still running")
("nvim exited" (exit_or_signal (Ok ()))) |}];return());;let%expect_test"Nested [rpcrequest]s are supported"=letresult=with_client(funclient->letopenDeferred.Or_error.Let_syntaxinletfactorial=(wrap_viml_function~type_:Defun.Vim.(Integer@->String@->Integer@->returnInteger)~function_name:"rpcrequest")(Client.channelclient)"factorial"inregister_request_blockingclient~name:"factorial"~type_:Defun.Ocaml.Sync.(Type.Integer@->returnInteger)~f:(fun~keyboard_interrupted:_~clientn->matchnwith|0->return1|_->let%mapresult=run_join[%here]client(factorial(n-1))inn*result);run_join[%here]client(factorial5))inlet%bindresult=with_timeout(Time_float.Span.of_int_sec3)resultinprint_s[%sexp(result:[`Resultofint|`Timeout])];[%expect{| (Result 120) |}];return();;