-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathpython_state_persistence.rs
More file actions
114 lines (100 loc) · 3.8 KB
/
python_state_persistence.rs
File metadata and controls
114 lines (100 loc) · 3.8 KB
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
//! Integration test: Python guest module globals persist across `run()`.
//!
//! This test pins the documented contract that the Python `Executor`
//! reuses one module-level namespace for every call to `run()` on the
//! same sandbox instance. The previous implementation built a fresh
//! `globals` dict on every call (`exec(code, {...})`), which silently
//! discarded any `def`, `class`, or top-level assignment between runs.
//! That contradicted:
//!
//! * the `WasmSandbox` `snapshot`/`restore` contract — the documented
//! mechanism for rewinding guest state — which only makes sense
//! if state otherwise survives a `run()` boundary;
//! * the `python_basics` example's "state was rolled back" narrative;
//! * the JavaScript guest, which preserves `globalThis` across runs.
//!
//! The tests below would have failed on the prior implementation; they
//! pass once `Executor` stores its globals on the instance and reuses
//! them across `run()` calls.
use std::path::Path;
use hyperlight_sandbox::SandboxBuilder;
use hyperlight_wasm_sandbox::Wasm;
fn python_guest_path() -> String {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("guests/python/python-sandbox.aot")
.display()
.to_string()
}
/// A `def` at module top level in `run()` #1 must be callable in `run()` #2.
#[tokio::test]
async fn python_function_definition_persists_across_runs() {
let result = tokio::task::spawn_blocking(|| {
let mut sandbox = SandboxBuilder::new()
.guest(Wasm)
.module_path(python_guest_path())
.build()
.expect("failed to create sandbox");
sandbox
.run("def word_count(text): return len(text.split())")
.expect("first run failed");
sandbox
.run("print(word_count('hello world from hyperlight'))")
.expect("second run failed")
})
.await
.unwrap();
assert_eq!(result.exit_code, 0, "stderr: {}", result.stderr);
assert_eq!(result.stdout.trim(), "4");
}
/// A bare module-level assignment in `run()` #1 must be readable in `run()` #2.
#[tokio::test]
async fn python_top_level_assignment_persists_across_runs() {
let result = tokio::task::spawn_blocking(|| {
let mut sandbox = SandboxBuilder::new()
.guest(Wasm)
.module_path(python_guest_path())
.build()
.expect("failed to create sandbox");
sandbox.run("counter = 100").expect("first run failed");
sandbox
.run("print(f'counter = {counter}')")
.expect("second run failed")
})
.await
.unwrap();
assert_eq!(result.exit_code, 0, "stderr: {}", result.stderr);
assert_eq!(result.stdout.trim(), "counter = 100");
}
/// `snapshot` + `restore` must continue to rewind the persistent
/// namespace, undoing any names defined since the snapshot. This is
/// the contract documented on `WasmSandbox`; the persistence fix must
/// not regress it.
#[tokio::test]
async fn python_restore_rewinds_module_globals() {
let result = tokio::task::spawn_blocking(|| {
let mut sandbox = SandboxBuilder::new()
.guest(Wasm)
.module_path(python_guest_path())
.build()
.expect("failed to create sandbox");
let snap = sandbox.snapshot().expect("snapshot failed");
sandbox
.run("rolled_back = 'still here'")
.expect("set failed");
sandbox.restore(&snap).expect("restore failed");
sandbox
.run(
r#"
try:
print(rolled_back)
except NameError:
print("rolled_back is undefined")
"#,
)
.expect("post-restore run failed")
})
.await
.unwrap();
assert_eq!(result.exit_code, 0, "stderr: {}", result.stderr);
assert_eq!(result.stdout.trim(), "rolled_back is undefined");
}