Skip to content

Commit 6152b7e

Browse files
committed
Add tests for MultiUseSandbox from_snapshot
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent 61f0625 commit 6152b7e

1 file changed

Lines changed: 240 additions & 0 deletions

File tree

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2810,4 +2810,244 @@ mod tests {
28102810
}
28112811
let _ = std::fs::remove_file(&path);
28122812
}
2813+
2814+
/// Tests for [`MultiUseSandbox::from_snapshot`] in-memory.
2815+
mod from_snapshot {
2816+
use std::sync::Arc;
2817+
2818+
use hyperlight_testing::simple_guest_as_string;
2819+
2820+
use crate::func::Registerable;
2821+
use crate::sandbox::SandboxConfiguration;
2822+
use crate::sandbox::snapshot::Snapshot;
2823+
use crate::{GuestBinary, HostFunctions, MultiUseSandbox, UninitializedSandbox};
2824+
2825+
fn make_sandbox() -> MultiUseSandbox {
2826+
let path = simple_guest_as_string().unwrap();
2827+
UninitializedSandbox::new(GuestBinary::FilePath(path), None)
2828+
.unwrap()
2829+
.evolve()
2830+
.unwrap()
2831+
}
2832+
2833+
/// Sandbox with an extra `Add(i32, i32) -> i32` host function.
2834+
fn make_sandbox_with_add() -> MultiUseSandbox {
2835+
let path = simple_guest_as_string().unwrap();
2836+
let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2837+
u.register_host_function("Add", |a: i32, b: i32| Ok(a + b))
2838+
.unwrap();
2839+
u.evolve().unwrap()
2840+
}
2841+
2842+
fn host_funcs_with_matching_add() -> HostFunctions {
2843+
let mut hf = HostFunctions::default();
2844+
hf.register_host_function("Add", |a: i32, b: i32| Ok(a + b))
2845+
.unwrap();
2846+
hf
2847+
}
2848+
2849+
#[test]
2850+
fn round_trip_running_sandbox() {
2851+
let mut sbox = make_sandbox();
2852+
sbox.call::<i32>("AddToStatic", 11i32).unwrap();
2853+
let snapshot = sbox.snapshot().unwrap();
2854+
let mut sbox2 =
2855+
MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), None).unwrap();
2856+
assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 11);
2857+
let echoed: String = sbox2.call("Echo", "hi".to_string()).unwrap();
2858+
assert_eq!(echoed, "hi");
2859+
}
2860+
2861+
#[test]
2862+
fn round_trip_pre_init_snapshot() {
2863+
let path = simple_guest_as_string().unwrap();
2864+
let snap =
2865+
Snapshot::from_env(GuestBinary::FilePath(path), SandboxConfiguration::default())
2866+
.unwrap();
2867+
let mut sbox =
2868+
MultiUseSandbox::from_snapshot(Arc::new(snap), HostFunctions::default(), None)
2869+
.unwrap();
2870+
assert_eq!(sbox.call::<i32>("GetStatic", ()).unwrap(), 0);
2871+
}
2872+
2873+
/// Sandboxes built from clones of one `Arc<Snapshot>` share
2874+
/// `sandbox_id` (so both can `restore` to it) but are
2875+
/// memory-isolated from each other.
2876+
#[test]
2877+
fn arc_clone_isolation_and_restore_compat() {
2878+
let mut sbox = make_sandbox();
2879+
sbox.call::<i32>("AddToStatic", 3i32).unwrap();
2880+
let snapshot = sbox.snapshot().unwrap();
2881+
2882+
let mut a =
2883+
MultiUseSandbox::from_snapshot(snapshot.clone(), HostFunctions::default(), None)
2884+
.unwrap();
2885+
let mut b =
2886+
MultiUseSandbox::from_snapshot(snapshot.clone(), HostFunctions::default(), None)
2887+
.unwrap();
2888+
assert_eq!(a.call::<i32>("GetStatic", ()).unwrap(), 3);
2889+
assert_eq!(b.call::<i32>("GetStatic", ()).unwrap(), 3);
2890+
2891+
a.call::<i32>("AddToStatic", 7i32).unwrap();
2892+
assert_eq!(a.call::<i32>("GetStatic", ()).unwrap(), 10);
2893+
assert_eq!(b.call::<i32>("GetStatic", ()).unwrap(), 3);
2894+
2895+
a.restore(snapshot.clone()).unwrap();
2896+
b.restore(snapshot).unwrap();
2897+
assert_eq!(a.call::<i32>("GetStatic", ()).unwrap(), 3);
2898+
assert_eq!(b.call::<i32>("GetStatic", ()).unwrap(), 3);
2899+
}
2900+
2901+
#[test]
2902+
fn accepts_matching_host_functions() {
2903+
let mut sbox = make_sandbox_with_add();
2904+
sbox.call::<i32>("AddToStatic", 5i32).unwrap();
2905+
let snap = sbox.snapshot().unwrap();
2906+
let mut sbox2 =
2907+
MultiUseSandbox::from_snapshot(snap, host_funcs_with_matching_add(), None).unwrap();
2908+
assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 5);
2909+
}
2910+
2911+
#[test]
2912+
fn rejects_missing_host_function() {
2913+
let mut sbox = make_sandbox_with_add();
2914+
let snap = sbox.snapshot().unwrap();
2915+
let err = MultiUseSandbox::from_snapshot(snap, HostFunctions::default(), None)
2916+
.expect_err("missing `Add` must be rejected");
2917+
let msg = format!("{}", err);
2918+
assert!(msg.contains("Add"), "got: {}", msg);
2919+
}
2920+
2921+
#[test]
2922+
fn rejects_signature_mismatch() {
2923+
let mut sbox = make_sandbox_with_add();
2924+
let snap = sbox.snapshot().unwrap();
2925+
let mut hf = HostFunctions::default();
2926+
hf.register_host_function("Add", |a: String, b: String| Ok(format!("{a}{b}")))
2927+
.unwrap();
2928+
let err = MultiUseSandbox::from_snapshot(snap, hf, None)
2929+
.expect_err("signature mismatch on `Add` must be rejected");
2930+
let msg = format!("{}", err);
2931+
assert!(msg.contains("Add"), "got: {}", msg);
2932+
}
2933+
2934+
/// Supplied host-function set may be a strict superset of the
2935+
/// snapshot's required set.
2936+
#[test]
2937+
fn accepts_extra_host_functions() {
2938+
let mut sbox = make_sandbox_with_add();
2939+
sbox.call::<i32>("AddToStatic", 9i32).unwrap();
2940+
let snap = sbox.snapshot().unwrap();
2941+
let mut hf = host_funcs_with_matching_add();
2942+
hf.register_host_function("Mul", |a: i32, b: i32| Ok(a * b))
2943+
.unwrap();
2944+
let mut sbox2 = MultiUseSandbox::from_snapshot(snap, hf, None).unwrap();
2945+
assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 9);
2946+
}
2947+
2948+
/// A sandbox built via `from_snapshot` can itself be snapshotted
2949+
/// and restored, and its snapshots are restore-compatible with it.
2950+
#[test]
2951+
fn re_snapshot_after_from_snapshot() {
2952+
let mut sbox = make_sandbox();
2953+
sbox.call::<i32>("AddToStatic", 4i32).unwrap();
2954+
let snap1 = sbox.snapshot().unwrap();
2955+
2956+
let mut sbox2 =
2957+
MultiUseSandbox::from_snapshot(snap1, HostFunctions::default(), None).unwrap();
2958+
sbox2.call::<i32>("AddToStatic", 6i32).unwrap();
2959+
let snap2 = sbox2.snapshot().unwrap();
2960+
2961+
sbox2.call::<i32>("AddToStatic", 100i32).unwrap();
2962+
assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 110);
2963+
2964+
sbox2.restore(snap2.clone()).unwrap();
2965+
assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 10);
2966+
2967+
let mut sbox3 =
2968+
MultiUseSandbox::from_snapshot(snap2, HostFunctions::default(), None).unwrap();
2969+
assert_eq!(sbox3.call::<i32>("GetStatic", ()).unwrap(), 10);
2970+
}
2971+
2972+
/// The host function closure supplied to `from_snapshot` (not the
2973+
/// original sandbox's closure) is the one invoked at runtime.
2974+
#[test]
2975+
fn supplied_host_function_is_callable() {
2976+
let path = simple_guest_as_string().unwrap();
2977+
let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2978+
u.register_host_function("Echo42", || Ok(1i64)).unwrap();
2979+
let mut sbox = u.evolve().unwrap();
2980+
let snap = sbox.snapshot().unwrap();
2981+
2982+
let mut hf = HostFunctions::default();
2983+
hf.register_host_function("Echo42", || Ok(42i64)).unwrap();
2984+
let mut sbox2 = MultiUseSandbox::from_snapshot(snap, hf, None).unwrap();
2985+
2986+
let got: i64 = sbox2
2987+
.call(
2988+
"CallGivenParamlessHostFuncThatReturnsI64",
2989+
"Echo42".to_string(),
2990+
)
2991+
.unwrap();
2992+
assert_eq!(got, 42);
2993+
}
2994+
2995+
/// Pre-init snapshots record no required host functions, so any
2996+
/// `HostFunctions` set is accepted.
2997+
#[test]
2998+
fn pre_init_snapshot_accepts_arbitrary_host_functions() {
2999+
let path = simple_guest_as_string().unwrap();
3000+
let snap =
3001+
Snapshot::from_env(GuestBinary::FilePath(path), SandboxConfiguration::default())
3002+
.unwrap();
3003+
let mut hf = HostFunctions::default();
3004+
hf.register_host_function("Unrelated", |a: i32| Ok(a + 1))
3005+
.unwrap();
3006+
let mut sbox = MultiUseSandbox::from_snapshot(Arc::new(snap), hf, None).unwrap();
3007+
assert_eq!(sbox.call::<i32>("GetStatic", ()).unwrap(), 0);
3008+
}
3009+
3010+
/// Snapshots taken from a sandbox built via `from_snapshot`
3011+
/// must continue the generation counter of the snapshot they
3012+
/// were constructed from, matching `restore`.
3013+
#[test]
3014+
fn snapshot_generation_propagates() {
3015+
let mut sbox = make_sandbox();
3016+
sbox.call::<i32>("AddToStatic", 1i32).unwrap();
3017+
let snap1 = sbox.snapshot().unwrap();
3018+
let gen1 = snap1.snapshot_generation();
3019+
sbox.call::<i32>("AddToStatic", 1i32).unwrap();
3020+
let snap2 = sbox.snapshot().unwrap();
3021+
let gen2 = snap2.snapshot_generation();
3022+
assert_eq!(gen2, gen1 + 1);
3023+
3024+
let mut sbox2 =
3025+
MultiUseSandbox::from_snapshot(snap2, HostFunctions::default(), None).unwrap();
3026+
sbox2.call::<i32>("AddToStatic", 1i32).unwrap();
3027+
let snap3 = sbox2.snapshot().unwrap();
3028+
assert_eq!(snap3.snapshot_generation(), gen2 + 1);
3029+
}
3030+
3031+
/// Registering a host function on an already-evolved
3032+
/// `MultiUseSandbox` must invalidate its cached snapshot, so
3033+
/// that the next `snapshot()` reflects the new required
3034+
/// host-function set.
3035+
#[test]
3036+
fn late_register_invalidates_snapshot_cache() {
3037+
let mut sbox = make_sandbox();
3038+
// Force a cached snapshot to exist.
3039+
let _ = sbox.snapshot().unwrap();
3040+
3041+
sbox.register_host_function("Echo42", || Ok(42i64)).unwrap();
3042+
3043+
// The next snapshot must include `Echo42` as a required
3044+
// host function, so building a sandbox from it without
3045+
// `Echo42` must fail.
3046+
let snap = sbox.snapshot().unwrap();
3047+
let err = MultiUseSandbox::from_snapshot(snap, HostFunctions::default(), None)
3048+
.expect_err("late-registered `Echo42` must be required by the new snapshot");
3049+
let msg = format!("{}", err);
3050+
assert!(msg.contains("Echo42"), "got: {}", msg);
3051+
}
3052+
}
28133053
}

0 commit comments

Comments
 (0)