package vcaml

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file test_nvim_semantics.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
open Core
open Async
open Import
open Vcaml
open Vcaml_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. *)

let hundred_ms = Time_ns.Span.create ~ms:100 ()

let%expect_test "[rpcrequest] blocks other channels" =
  Expect_test_helpers_async.with_temp_dir (fun tmp_dir ->
    let socket = tmp_dir ^/ "socket" in
    let%bind nvim =
      (* 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" ])
    in
    let%bind () =
      with_process_cleanup ~name:"nvim" (Process.pid nvim) ~f:(fun () ->
        match%bind spin_until_nvim_creates_socket_file (Process.pid nvim) ~socket with
        | `Nvim_crashed exit_or_signal -> return (`Already_reaped exit_or_signal)
        | `Socket_missing -> raise_s [%message "Socket was not created"]
        | `Socket_created ->
          let block_nvim ~client =
            let blocking = Ivar.create () in
            let result = Ivar.create () in
            let function_name = "rpc" in
            let call_rpc =
              (wrap_viml_function
                 ~type_:Defun.Vim.(Integer @-> String @-> Nil @-> return Nil)
                 ~function_name:"rpcrequest")
                (Client.channel client)
                function_name
                ()
            in
            register_request_blocking
              client
              ~name:function_name
              ~type_:Defun.Ocaml.Sync.(Nil @-> return Nil)
              ~f:(fun ~keyboard_interrupted:_ ~client:_ () ->
                Ivar.fill blocking ();
                Ivar.read result |> Deferred.ok);
            let result_deferred = run_join [%here] client call_rpc in
            let%map () = Ivar.read blocking in
            fun response ->
              Ivar.fill result response;
              result_deferred
          in
          let%bind client1 = socket_client socket >>| ok_exn in
          let%bind client2 = socket_client socket >>| ok_exn in
          let print_when_client2_is_unblocked () =
            don't_wait_for
              (let%map result =
                 run_join
                   [%here]
                   client2
                   (Nvim.eval "'Client 2 is unblocked'" ~result_type:String)
               in
               print_s [%sexp (result : string Or_error.t)])
          in
          print_when_client2_is_unblocked ();
          let%bind () = Clock_ns.after hundred_ms in
          let%bind () = Scheduler.yield_until_no_jobs_remain () in
          print_s [%message "Blocking nvim (client1)"];
          let%bind respond_to_rpc = block_nvim ~client:client1 in
          print_when_client2_is_unblocked ();
          let%bind () = Clock_ns.after hundred_ms in
          let%bind () = Scheduler.yield_until_no_jobs_remain () in
          print_s [%message "Unblocking nvim (client1)"];
          let%bind () = respond_to_rpc () >>| ok_exn in
          let%bind () = Clock_ns.after hundred_ms in
          let%bind () = Scheduler.yield_until_no_jobs_remain () in
          let%bind () = attempt_to_quit ~tmp_dir ~client:client2 in
          let%bind () = Client.close client1 in
          let%bind () = Client.close client2 in
          return (`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 (fun tmp_dir ->
    let socket = tmp_dir ^/ "socket" in
    let%bind nvim =
      (* 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" ])
    in
    let%bind () =
      with_process_cleanup ~name:"nvim" (Process.pid nvim) ~f:(fun () ->
        match%bind spin_until_nvim_creates_socket_file (Process.pid nvim) ~socket with
        | `Nvim_crashed exit_or_signal -> return (`Already_reaped exit_or_signal)
        | `Socket_missing -> raise_s [%message "Socket was not created"]
        | `Socket_created ->
          let block_nvim ~client =
            let blocking = Ivar.create () in
            let function_name = "rpc" in
            let call_rpc =
              (wrap_viml_function
                 ~type_:Defun.Vim.(Integer @-> String @-> Nil @-> return Nil)
                 ~function_name:"rpcrequest")
                (Client.channel client)
                function_name
                ()
            in
            register_request_blocking
              client
              ~name:function_name
              ~type_:Defun.Ocaml.Sync.(Nil @-> return Nil)
              ~f:(fun ~keyboard_interrupted:_ ~client:_ () ->
                Ivar.fill blocking ();
                Deferred.never ());
            don't_wait_for (run_join [%here] client call_rpc >>| ok_exn);
            Ivar.read blocking
          in
          (match Core_unix.fork () with
           | `In_the_child ->
             Scheduler.reset_in_forked_process ();
             don't_wait_for
               (let%bind client = socket_client socket >>| ok_exn in
                let%bind () = block_nvim ~client in
                (* We don't want to allow the [at_exit] handler that expect test collector
                   registers to run for this child process. *)
                Core_unix.exit_immediately 0);
             never_returns (Scheduler.go ())
           | `In_the_parent child ->
             let%bind exit_or_signal = Unix.waitpid child in
             print_s [%message "child exited" (exit_or_signal : Unix.Exit_or_signal.t)];
             let%bind client = socket_client socket >>| ok_exn in
             let%bind result =
               run_join
                 [%here]
                 client
                 (Nvim.eval "'nvim is still running'" ~result_type:String)
             in
             print_s [%sexp (result : string Or_error.t)];
             let%bind () = attempt_to_quit ~tmp_dir ~client in
             let%bind () = Client.close client in
             return (`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" =
  let result =
    with_client (fun client ->
      let open Deferred.Or_error.Let_syntax in
      let factorial =
        (wrap_viml_function
           ~type_:Defun.Vim.(Integer @-> String @-> Integer @-> return Integer)
           ~function_name:"rpcrequest")
          (Client.channel client)
          "factorial"
      in
      register_request_blocking
        client
        ~name:"factorial"
        ~type_:Defun.Ocaml.Sync.(Type.Integer @-> return Integer)
        ~f:(fun ~keyboard_interrupted:_ ~client n ->
          match n with
          | 0 -> return 1
          | _ ->
            let%map result = run_join [%here] client (factorial (n - 1)) in
            n * result);
      run_join [%here] client (factorial 5))
  in
  let%bind result = with_timeout (Time_float.Span.of_int_sec 3) result in
  print_s [%sexp (result : [ `Result of int | `Timeout ])];
  [%expect {| (Result 120) |}];
  return ()
;;
OCaml

Innovation. Community. Security.