Skip to content

Commit 8230424

Browse files
committed
Clean up Python state persistence tests
1 parent a5948a9 commit 8230424

1 file changed

Lines changed: 95 additions & 76 deletions

File tree

src/wasm_sandbox/tests/python_state_persistence.rs

Lines changed: 95 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,13 @@
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
2013
use 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#"
101123
try:
102124
print(rolled_back)
103125
except 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

Comments
 (0)