22//!
33//! This test pins the documented contract that the Python `Executor`
44//! reuses one module-level namespace for every call to `run()` on the
5- //! same sandbox instance. The previous implementation built a fresh
6- //! `globals` dict on every call (`exec(code, {...})`), which silently
7- //! discarded any `def`, `class`, or top-level assignment between runs.
8- //! That contradicted:
5+ //! same sandbox instance. It tests:
96//!
10- //! * the `WasmSandbox` `snapshot`/`restore` contract — the documented
11- //! mechanism for rewinding guest state — which only makes sense
12- //! if state otherwise survives a `run()` boundary;
13- //! * the `python_basics` example's "state was rolled back" narrative;
14- //! * the JavaScript guest, which preserves `globalThis` across runs.
7+ //! * the `WasmSandbox` `snapshot`/`restore` resets state — the documented
8+ //! mechanism for rewinding guest state
9+ //! * if state otherwise survives a `run()` boundary
10+ //! * behaves like the JavaScript guest, which preserves `globalThis` across runs
1511//!
16- //! The tests below would have failed on the prior implementation; they
17- //! pass once `Executor` stores its globals on the instance and reuses
18- //! them across `run()` calls.
1912
2013use std:: path:: Path ;
2114
@@ -29,85 +22,111 @@ fn python_guest_path() -> String {
2922 . to_string ( )
3023}
3124
32- /// A `def` at module top level in `run()` #1 must be callable in `run()` #2.
33- #[ tokio:: test]
34- async fn python_function_definition_persists_across_runs ( ) {
35- let result = tokio:: task:: spawn_blocking ( || {
36- let mut sandbox = SandboxBuilder :: new ( )
37- . guest ( Wasm )
38- . module_path ( python_guest_path ( ) )
39- . build ( )
40- . expect ( "failed to create sandbox" ) ;
41-
42- sandbox
43- . run ( "def word_count(text): return len(text.split())" )
44- . expect ( "first run failed" ) ;
45-
46- sandbox
47- . run ( "print(word_count('hello world from hyperlight'))" )
48- . expect ( "second run failed" )
49- } )
50- . await
51- . unwrap ( ) ;
25+ /// A `def` at module top level in `run()` must be callable in the second `run()`
26+ #[ test]
27+ fn python_function_definition_persists_across_runs ( ) {
28+ let mut sandbox = SandboxBuilder :: new ( )
29+ . guest ( Wasm )
30+ . module_path ( python_guest_path ( ) )
31+ . build ( )
32+ . expect ( "failed to create sandbox" ) ;
5233
53- assert_eq ! ( result. exit_code, 0 , "stderr: {}" , result. stderr) ;
54- assert_eq ! ( result. stdout. trim( ) , "4" ) ;
34+ let snap = sandbox. snapshot ( ) . expect ( "snapshot failed" ) ;
35+ sandbox
36+ . run ( "def word_count(text): return len(text.split())" )
37+ . expect ( "first run failed" ) ;
38+
39+ let persist_result = sandbox
40+ . run ( "print(word_count('hello world from hyperlight'))" )
41+ . expect ( "second run failed" ) ;
42+
43+ sandbox. restore ( & snap) . expect ( "restore failed" ) ;
44+ let reset_result = sandbox
45+ . run (
46+ r#"
47+ try:
48+ print(word_count('hello world from hyperlight'))
49+ except NameError:
50+ print("word_count is undefined")
51+ "# ,
52+ )
53+ . expect ( "post-restore run failed" ) ;
54+
55+ assert_eq ! (
56+ persist_result. exit_code, 0 ,
57+ "stderr: {}" ,
58+ persist_result. stderr
59+ ) ;
60+ assert_eq ! ( persist_result. stdout. trim( ) , "4" ) ;
61+ assert_eq ! ( reset_result. exit_code, 0 , "stderr: {}" , reset_result. stderr) ;
62+ assert_eq ! ( reset_result. stdout. trim( ) , "word_count is undefined" ) ;
5563}
5664
57- /// A bare module-level assignment in `run()` #1 must be readable in `run()` #2.
58- #[ tokio:: test]
59- async fn python_top_level_assignment_persists_across_runs ( ) {
60- let result = tokio:: task:: spawn_blocking ( || {
61- let mut sandbox = SandboxBuilder :: new ( )
62- . guest ( Wasm )
63- . module_path ( python_guest_path ( ) )
64- . build ( )
65- . expect ( "failed to create sandbox" ) ;
66-
67- sandbox. run ( "counter = 100" ) . expect ( "first run failed" ) ;
68- sandbox
69- . run ( "print(f'counter = {counter}')" )
70- . expect ( "second run failed" )
71- } )
72- . await
73- . unwrap ( ) ;
65+ /// A bare module-level assignment in `run()` must be readable in the second `run()`
66+ #[ test]
67+ fn python_top_level_assignment_persists_across_runs ( ) {
68+ let mut sandbox = SandboxBuilder :: new ( )
69+ . guest ( Wasm )
70+ . module_path ( python_guest_path ( ) )
71+ . build ( )
72+ . expect ( "failed to create sandbox" ) ;
7473
75- assert_eq ! ( result. exit_code, 0 , "stderr: {}" , result. stderr) ;
76- assert_eq ! ( result. stdout. trim( ) , "counter = 100" ) ;
74+ let snap = sandbox. snapshot ( ) . expect ( "snapshot failed" ) ;
75+ sandbox. run ( "counter = 100" ) . expect ( "first run failed" ) ;
76+ let persist_result = sandbox
77+ . run ( "print(f'counter = {counter}')" )
78+ . expect ( "second run failed" ) ;
79+
80+ sandbox. restore ( & snap) . expect ( "restore failed" ) ;
81+ let reset_result = sandbox
82+ . run (
83+ r#"
84+ try:
85+ print(f'counter = {counter}')
86+ except NameError:
87+ print("counter is undefined")
88+ "# ,
89+ )
90+ . expect ( "post-restore run failed" ) ;
91+
92+ assert_eq ! (
93+ persist_result. exit_code, 0 ,
94+ "stderr: {}" ,
95+ persist_result. stderr
96+ ) ;
97+ assert_eq ! ( persist_result. stdout. trim( ) , "counter = 100" ) ;
98+ assert_eq ! ( reset_result. exit_code, 0 , "stderr: {}" , reset_result. stderr) ;
99+ assert_eq ! ( reset_result. stdout. trim( ) , "counter is undefined" ) ;
77100}
78101
79102/// `snapshot` + `restore` must continue to rewind the persistent
80103/// namespace, undoing any names defined since the snapshot. This is
81104/// the contract documented on `WasmSandbox`; the persistence fix must
82105/// not regress it.
83- #[ tokio:: test]
84- async fn python_restore_rewinds_module_globals ( ) {
85- let result = tokio:: task:: spawn_blocking ( || {
86- let mut sandbox = SandboxBuilder :: new ( )
87- . guest ( Wasm )
88- . module_path ( python_guest_path ( ) )
89- . build ( )
90- . expect ( "failed to create sandbox" ) ;
91-
92- let snap = sandbox. snapshot ( ) . expect ( "snapshot failed" ) ;
93- sandbox
94- . run ( "rolled_back = 'still here'" )
95- . expect ( "set failed" ) ;
96- sandbox. restore ( & snap) . expect ( "restore failed" ) ;
97-
98- sandbox
99- . run (
100- r#"
106+ #[ test]
107+ fn python_restore_rewinds_module_globals ( ) {
108+ let mut sandbox = SandboxBuilder :: new ( )
109+ . guest ( Wasm )
110+ . module_path ( python_guest_path ( ) )
111+ . build ( )
112+ . expect ( "failed to create sandbox" ) ;
113+
114+ let snap = sandbox. snapshot ( ) . expect ( "snapshot failed" ) ;
115+ sandbox
116+ . run ( "rolled_back = 'still here'" )
117+ . expect ( "set failed" ) ;
118+ sandbox. restore ( & snap) . expect ( "restore failed" ) ;
119+
120+ let result = sandbox
121+ . run (
122+ r#"
101123try:
102124 print(rolled_back)
103125except NameError:
104126 print("rolled_back is undefined")
105127"# ,
106- )
107- . expect ( "post-restore run failed" )
108- } )
109- . await
110- . unwrap ( ) ;
128+ )
129+ . expect ( "post-restore run failed" ) ;
111130
112131 assert_eq ! ( result. exit_code, 0 , "stderr: {}" , result. stderr) ;
113132 assert_eq ! ( result. stdout. trim( ) , "rolled_back is undefined" ) ;
0 commit comments