this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add environment isolation tests

Tests for the multiple environments feature:
- Creating and destroying environments
- Environment isolation (values don't leak between environments)
- Listing environments
- Reusing environment names after destruction

All 21 tests verify that environments are properly isolated using
Toploop.toplevel_env save/restore mechanism.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+440
+59
test/node/dune
··· 178 178 (deps _opam) 179 179 (action 180 180 (diff node_ppx_test.expected node_ppx_test.out))) 181 + 182 + ; Environment test executable 183 + (executable 184 + (name node_env_test) 185 + (modes byte) 186 + (modules node_env_test) 187 + (link_flags (-linkall)) 188 + (libraries 189 + str 190 + fpath 191 + js_of_ocaml 192 + js_top_worker-web 193 + js_of_ocaml-toplevel 194 + js_top_worker 195 + logs 196 + logs.fmt 197 + rpclib.core 198 + rpclib.json 199 + findlib.top 200 + js_of_ocaml-lwt 201 + zarith_stubs_js)) 202 + 203 + (rule 204 + (targets node_env_test.js) 205 + (action 206 + (run 207 + %{bin:js_of_ocaml} 208 + --toplevel 209 + --pretty 210 + --no-cmis 211 + --effects=cps 212 + --debuginfo 213 + --target-env=nodejs 214 + +toplevel.js 215 + +dynlink.js 216 + +bigstringaf/runtime.js 217 + +zarith_stubs_js/runtime.js 218 + %{lib:js_top_worker:stubs.js} 219 + %{dep:node_env_test.bc} 220 + -o 221 + %{targets}))) 222 + 223 + (rule 224 + (deps _opam) 225 + (action 226 + (with-outputs-to 227 + node_env_test.out 228 + (run 229 + node 230 + --stack-size=2000 231 + -r 232 + ./%{dep:import_scripts.js} 233 + %{dep:node_env_test.js})))) 234 + 235 + (rule 236 + (alias runtest) 237 + (deps _opam) 238 + (action 239 + (diff node_env_test.expected node_env_test.out)))
+116
test/node/node_env_test.expected
··· 1 + === Node.js Environment Tests === 2 + 3 + node_env_test.js: [INFO] init() 4 + Initializing findlib 5 + Parsed uri: lib/sexplib0/META 6 + Reading library: sexplib0 7 + Number of children: 0 8 + Parsed uri: lib/ocaml_intrinsics_kernel/META 9 + Reading library: ocaml_intrinsics_kernel 10 + Number of children: 0 11 + Parsed uri: lib/ocaml/stdlib/META 12 + Reading library: stdlib 13 + Number of children: 0 14 + Parsed uri: lib/base/META 15 + Reading library: base 16 + Number of children: 3 17 + Found child: base_internalhash_types 18 + Reading library: base.base_internalhash_types 19 + Number of children: 0 20 + Found child: md5 21 + Reading library: base.md5 22 + Number of children: 0 23 + Found child: shadow_stdlib 24 + Reading library: base.shadow_stdlib 25 + Number of children: 0 26 + node_env_test.js: [INFO] Adding toplevel modules for dynamic cmis from lib/ocaml/ 27 + node_env_test.js: [INFO] toplevel modules: CamlinternalFormat, CamlinternalLazy, CamlinternalFormatBasics, CamlinternalMod, Std_exit, Stdlib, CamlinternalOO 28 + node_env_test.js: [INFO] init() finished 29 + --- Section 1: Default Environment --- 30 + node_env_test.js: [INFO] setup() for env default... 31 + node_env_test.js: [INFO] Fetching stdlib__Format.cmi 32 + 33 + node_env_test.js: [INFO] Fetching stdlib__Sys.cmi 34 + 35 + error while evaluating #enable "pretty";; 36 + error while evaluating #disable "shortvar";; 37 + node_env_test.js: [INFO] Setup complete 38 + node_env_test.js: [INFO] setup() finished for env default 39 + [PASS] default_setup: Default environment setup 40 + [PASS] default_define: # let default_val = 42;; 41 + val default_val : int = 42 42 + 43 + --- Section 2: Creating New Environments --- 44 + node_env_test.js: [INFO] create_env(env1) 45 + [PASS] create_env1: Created environment env1 46 + node_env_test.js: [INFO] setup() for env env1... 47 + error while evaluating #enable "pretty";; 48 + error while evaluating #disable "shortvar";; 49 + node_env_test.js: [INFO] Setup complete 50 + node_env_test.js: [INFO] setup() finished for env env1 51 + [PASS] setup_env1: Setup environment env1 52 + [PASS] env1_define: # let env1_val = 100;; 53 + val env1_val : int = 100 54 + 55 + --- Section 3: Environment Isolation --- 56 + Line 1, characters 0-11: 57 + Error: Unbound value default_val 58 + [PASS] isolation_default_from_env1: No leakage: # default_val;; 59 + 60 + Line 1, characters 0-8: 61 + Error: Unbound value env1_val 62 + [PASS] isolation_env1_from_default: No leakage: # env1_val;; 63 + [PASS] default_still_works: # default_val;; 64 + - : int = 42 65 + 66 + --- Section 4: Multiple Environments --- 67 + node_env_test.js: [INFO] create_env(env2) 68 + node_env_test.js: [INFO] setup() for env env2... 69 + error while evaluating #enable "pretty";; 70 + error while evaluating #disable "shortvar";; 71 + node_env_test.js: [INFO] Setup complete 72 + node_env_test.js: [INFO] setup() finished for env env2 73 + [PASS] create_and_setup_env2: Created and setup env2 74 + [PASS] env2_define: # let env2_val = 200;; 75 + val env2_val : int = 200 76 + 77 + Line 1, characters 0-8: 78 + Error: Unbound value env1_val 79 + Hint: Did you mean env2_val? 80 + [PASS] isolation_env1_from_env2: No leakage: # env1_val;; 81 + 82 + Line 1, characters 0-8: 83 + Error: Unbound value env2_val 84 + Hint: Did you mean env1_val? 85 + [PASS] isolation_env2_from_env1: No leakage: # env2_val;; 86 + 87 + --- Section 5: List Environments --- 88 + node_env_test.js: [INFO] list_envs() -> [env2, default, env1] 89 + [PASS] list_envs_count: Found 3 environments 90 + [PASS] list_envs_has_default: env2, default, env1 91 + [PASS] list_envs_has_env1: env2, default, env1 92 + [PASS] list_envs_has_env2: env2, default, env1 93 + 94 + --- Section 6: Destroy Environment --- 95 + node_env_test.js: [INFO] destroy_env(env2) 96 + [PASS] destroy_env2: Destroyed env2 97 + node_env_test.js: [INFO] list_envs() -> [default, env1] 98 + [PASS] env2_destroyed: default, env1 99 + [PASS] env1_still_exists: default, env1 100 + 101 + --- Section 7: Reuse Environment Name --- 102 + node_env_test.js: [INFO] create_env(env2) 103 + node_env_test.js: [INFO] setup() for env env2... 104 + error while evaluating #enable "pretty";; 105 + error while evaluating #disable "shortvar";; 106 + node_env_test.js: [INFO] Setup complete 107 + node_env_test.js: [INFO] setup() finished for env env2 108 + 109 + Line 1, characters 0-8: 110 + Error: Unbound value env2_val 111 + [PASS] new_env2_clean: Old value gone: # env2_val;; 112 + [PASS] new_env2_define: # let new_env2_val = 999;; 113 + val new_env2_val : int = 999 114 + 115 + === Results: 21/21 tests passed === 116 + SUCCESS: All environment tests passed!
+265
test/node/node_env_test.ml
··· 1 + (** Node.js test for multiple environment support. 2 + 3 + This tests that multiple isolated execution environments work correctly, 4 + including: 5 + - Creating and destroying environments 6 + - Isolation between environments (values defined in one don't leak to another) 7 + - Using the default environment 8 + - Listing environments 9 + *) 10 + 11 + open Js_top_worker 12 + open Js_top_worker_rpc.Toplevel_api_gen 13 + open Impl 14 + 15 + (* Flusher that writes to process.stdout in Node.js *) 16 + let console_flusher (s : string) : unit = 17 + let open Js_of_ocaml in 18 + let process = Js.Unsafe.get Js.Unsafe.global (Js.string "process") in 19 + let stdout = Js.Unsafe.get process (Js.string "stdout") in 20 + let write = Js.Unsafe.get stdout (Js.string "write") in 21 + ignore (Js.Unsafe.call write stdout [| Js.Unsafe.inject (Js.string s) |]) 22 + 23 + let capture : (unit -> 'a) -> unit -> Impl.captured * 'a = 24 + fun f () -> 25 + let stdout_buff = Buffer.create 1024 in 26 + let stderr_buff = Buffer.create 1024 in 27 + Js_of_ocaml.Sys_js.set_channel_flusher stdout (Buffer.add_string stdout_buff); 28 + let x = f () in 29 + let captured = 30 + { 31 + Impl.stdout = Buffer.contents stdout_buff; 32 + stderr = Buffer.contents stderr_buff; 33 + } 34 + in 35 + Js_of_ocaml.Sys_js.set_channel_flusher stdout console_flusher; 36 + (captured, x) 37 + 38 + module Server = Js_top_worker_rpc.Toplevel_api_gen.Make (Impl.IdlM.GenServer ()) 39 + 40 + module S : Impl.S = struct 41 + type findlib_t = Js_top_worker_web.Findlibish.t 42 + 43 + let capture = capture 44 + 45 + let sync_get f = 46 + let f = Fpath.v ("_opam/" ^ f) in 47 + try Some (In_channel.with_open_bin (Fpath.to_string f) In_channel.input_all) 48 + with _ -> None 49 + 50 + let async_get f = 51 + let f = Fpath.v ("_opam/" ^ f) in 52 + try 53 + let content = 54 + In_channel.with_open_bin (Fpath.to_string f) In_channel.input_all 55 + in 56 + Lwt.return (Ok content) 57 + with e -> Lwt.return (Error (`Msg (Printexc.to_string e))) 58 + 59 + let create_file = Js_of_ocaml.Sys_js.create_file 60 + 61 + let import_scripts urls = 62 + let open Js_of_ocaml.Js in 63 + let import_scripts_fn = Unsafe.get Unsafe.global (string "importScripts") in 64 + List.iter 65 + (fun url -> 66 + let (_ : 'a) = 67 + Unsafe.fun_call import_scripts_fn [| Unsafe.inject (string url) |] 68 + in 69 + ()) 70 + urls 71 + 72 + let init_function _ () = failwith "Not implemented" 73 + let findlib_init = Js_top_worker_web.Findlibish.init async_get 74 + 75 + let get_stdlib_dcs uri = 76 + Js_top_worker_web.Findlibish.fetch_dynamic_cmis sync_get uri 77 + |> Result.to_list 78 + 79 + let require b v = function 80 + | [] -> [] 81 + | packages -> 82 + Js_top_worker_web.Findlibish.require ~import_scripts sync_get b v 83 + packages 84 + 85 + let path = "/static/cmis" 86 + end 87 + 88 + module U = Impl.Make (S) 89 + 90 + let start_server () = 91 + let open U in 92 + Logs.set_reporter (Logs_fmt.reporter ()); 93 + Logs.set_level (Some Logs.Info); 94 + Server.init (IdlM.T.lift init); 95 + Server.create_env (IdlM.T.lift create_env); 96 + Server.destroy_env (IdlM.T.lift destroy_env); 97 + Server.list_envs (IdlM.T.lift list_envs); 98 + Server.setup (IdlM.T.lift setup); 99 + Server.exec execute; 100 + Server.typecheck typecheck_phrase; 101 + Server.complete_prefix complete_prefix; 102 + Server.query_errors query_errors; 103 + Server.type_enclosing type_enclosing; 104 + Server.exec_toplevel exec_toplevel; 105 + IdlM.server Server.implementation 106 + 107 + module Client = Js_top_worker_rpc.Toplevel_api_gen.Make (Impl.IdlM.GenClient ()) 108 + 109 + (* Test result tracking *) 110 + let total_tests = ref 0 111 + let passed_tests = ref 0 112 + 113 + let test name check message = 114 + incr total_tests; 115 + let passed = check in 116 + if passed then incr passed_tests; 117 + let status = if passed then "PASS" else "FAIL" in 118 + Printf.printf "[%s] %s: %s\n%!" status name message 119 + 120 + let contains s substr = 121 + try 122 + let _ = Str.search_forward (Str.regexp_string substr) s 0 in 123 + true 124 + with Not_found -> false 125 + 126 + let run_toplevel rpc env_id code = 127 + let ( let* ) = IdlM.ErrM.bind in 128 + let* result = Client.exec_toplevel rpc env_id ("# " ^ code) in 129 + IdlM.ErrM.return result.script 130 + 131 + let _ = 132 + Printf.printf "=== Node.js Environment Tests ===\n\n%!"; 133 + 134 + let rpc = start_server () in 135 + let ( let* ) = IdlM.ErrM.bind in 136 + 137 + let init_config = 138 + { stdlib_dcs = None; findlib_requires = []; execute = true } 139 + in 140 + 141 + let test_sequence = 142 + (* Initialize *) 143 + let* _ = Client.init rpc init_config in 144 + 145 + Printf.printf "--- Section 1: Default Environment ---\n%!"; 146 + 147 + (* Setup default environment *) 148 + let* _ = Client.setup rpc "" in 149 + test "default_setup" true "Default environment setup"; 150 + 151 + (* Define a value in default environment *) 152 + let* r = run_toplevel rpc "" "let default_val = 42;;" in 153 + test "default_define" (contains r "val default_val : int = 42") r; 154 + 155 + Printf.printf "\n--- Section 2: Creating New Environments ---\n%!"; 156 + 157 + (* Create a new environment "env1" *) 158 + let* _ = Client.create_env rpc "env1" in 159 + test "create_env1" true "Created environment env1"; 160 + 161 + (* Setup env1 *) 162 + let* _ = Client.setup rpc "env1" in 163 + test "setup_env1" true "Setup environment env1"; 164 + 165 + (* Define a different value in env1 *) 166 + let* r = run_toplevel rpc "env1" "let env1_val = 100;;" in 167 + test "env1_define" (contains r "val env1_val : int = 100") r; 168 + 169 + Printf.printf "\n--- Section 3: Environment Isolation ---\n%!"; 170 + 171 + (* Check that default_val is NOT visible in env1 - the script output 172 + should NOT contain "val default_val" if there was an error *) 173 + let* r = run_toplevel rpc "env1" "default_val;;" in 174 + test "isolation_default_from_env1" (not (contains r "val default_val")) 175 + ("No leakage: " ^ String.sub r 0 (min 40 (String.length r))); 176 + 177 + (* Check that env1_val is NOT visible in default env *) 178 + let* r = run_toplevel rpc "" "env1_val;;" in 179 + test "isolation_env1_from_default" (not (contains r "val env1_val")) 180 + ("No leakage: " ^ String.sub r 0 (min 40 (String.length r))); 181 + 182 + (* Check that default_val IS still visible in default env *) 183 + let* r = run_toplevel rpc "" "default_val;;" in 184 + test "default_still_works" (contains r "- : int = 42") r; 185 + 186 + Printf.printf "\n--- Section 4: Multiple Environments ---\n%!"; 187 + 188 + (* Create a second environment *) 189 + let* _ = Client.create_env rpc "env2" in 190 + let* _ = Client.setup rpc "env2" in 191 + test "create_and_setup_env2" true "Created and setup env2"; 192 + 193 + (* Define value in env2 *) 194 + let* r = run_toplevel rpc "env2" "let env2_val = 200;;" in 195 + test "env2_define" (contains r "val env2_val : int = 200") r; 196 + 197 + (* Verify isolation between all three environments *) 198 + let* r = run_toplevel rpc "env2" "env1_val;;" in 199 + test "isolation_env1_from_env2" (not (contains r "val env1_val")) 200 + ("No leakage: " ^ String.sub r 0 (min 40 (String.length r))); 201 + 202 + let* r = run_toplevel rpc "env1" "env2_val;;" in 203 + test "isolation_env2_from_env1" (not (contains r "val env2_val")) 204 + ("No leakage: " ^ String.sub r 0 (min 40 (String.length r))); 205 + 206 + Printf.printf "\n--- Section 5: List Environments ---\n%!"; 207 + 208 + (* List all environments *) 209 + let* envs = Client.list_envs rpc () in 210 + test "list_envs_count" (List.length envs >= 3) 211 + (Printf.sprintf "Found %d environments" (List.length envs)); 212 + test "list_envs_has_default" (List.mem "default" envs) 213 + (String.concat ", " envs); 214 + test "list_envs_has_env1" (List.mem "env1" envs) 215 + (String.concat ", " envs); 216 + test "list_envs_has_env2" (List.mem "env2" envs) 217 + (String.concat ", " envs); 218 + 219 + Printf.printf "\n--- Section 6: Destroy Environment ---\n%!"; 220 + 221 + (* Destroy env2 *) 222 + let* _ = Client.destroy_env rpc "env2" in 223 + test "destroy_env2" true "Destroyed env2"; 224 + 225 + (* Verify env2 is gone from list *) 226 + let* envs = Client.list_envs rpc () in 227 + test "env2_destroyed" (not (List.mem "env2" envs)) 228 + (String.concat ", " envs); 229 + 230 + (* env1 should still exist *) 231 + test "env1_still_exists" (List.mem "env1" envs) 232 + (String.concat ", " envs); 233 + 234 + Printf.printf "\n--- Section 7: Reuse Environment Name ---\n%!"; 235 + 236 + (* Re-create env2 *) 237 + let* _ = Client.create_env rpc "env2" in 238 + let* _ = Client.setup rpc "env2" in 239 + 240 + (* Old values should not exist - checking that it doesn't find the old value *) 241 + let* r = run_toplevel rpc "env2" "env2_val;;" in 242 + test "new_env2_clean" (not (contains r "- : int = 200")) 243 + ("Old value gone: " ^ String.sub r 0 (min 40 (String.length r))); 244 + 245 + (* Define new value *) 246 + let* r = run_toplevel rpc "env2" "let new_env2_val = 999;;" in 247 + test "new_env2_define" (contains r "val new_env2_val : int = 999") r; 248 + 249 + IdlM.ErrM.return () 250 + in 251 + 252 + let promise = test_sequence |> IdlM.T.get in 253 + (match Lwt.state promise with 254 + | Lwt.Return (Ok ()) -> () 255 + | Lwt.Return (Error (InternalError s)) -> 256 + Printf.printf "\n[ERROR] Test failed with: %s\n%!" s 257 + | Lwt.Fail e -> 258 + Printf.printf "\n[ERROR] Exception: %s\n%!" (Printexc.to_string e) 259 + | Lwt.Sleep -> Printf.printf "\n[ERROR] Promise still pending\n%!"); 260 + 261 + Printf.printf "\n=== Results: %d/%d tests passed ===\n%!" !passed_tests 262 + !total_tests; 263 + if !passed_tests = !total_tests then 264 + Printf.printf "SUCCESS: All environment tests passed!\n%!" 265 + else Printf.printf "FAILURE: Some tests failed.\n%!"