Skip to content

Commit 1cd84cf

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

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
@@ -2916,4 +2916,244 @@ mod tests {
29162916
"Expected overlap error for scratch region, got: {err:?}"
29172917
);
29182918
}
2919+
2920+
/// Tests for [`MultiUseSandbox::from_snapshot`] in-memory.
2921+
mod from_snapshot {
2922+
use std::sync::Arc;
2923+
2924+
use hyperlight_testing::simple_guest_as_string;
2925+
2926+
use crate::func::Registerable;
2927+
use crate::sandbox::SandboxConfiguration;
2928+
use crate::sandbox::snapshot::Snapshot;
2929+
use crate::{GuestBinary, HostFunctions, MultiUseSandbox, UninitializedSandbox};
2930+
2931+
fn make_sandbox() -> MultiUseSandbox {
2932+
let path = simple_guest_as_string().unwrap();
2933+
UninitializedSandbox::new(GuestBinary::FilePath(path), None)
2934+
.unwrap()
2935+
.evolve()
2936+
.unwrap()
2937+
}
2938+
2939+
/// Sandbox with an extra `Add(i32, i32) -> i32` host function.
2940+
fn make_sandbox_with_add() -> MultiUseSandbox {
2941+
let path = simple_guest_as_string().unwrap();
2942+
let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2943+
u.register_host_function("Add", |a: i32, b: i32| Ok(a + b))
2944+
.unwrap();
2945+
u.evolve().unwrap()
2946+
}
2947+
2948+
fn host_funcs_with_matching_add() -> HostFunctions {
2949+
let mut hf = HostFunctions::default();
2950+
hf.register_host_function("Add", |a: i32, b: i32| Ok(a + b))
2951+
.unwrap();
2952+
hf
2953+
}
2954+
2955+
#[test]
2956+
fn round_trip_running_sandbox() {
2957+
let mut sbox = make_sandbox();
2958+
sbox.call::<i32>("AddToStatic", 11i32).unwrap();
2959+
let snapshot = sbox.snapshot().unwrap();
2960+
let mut sbox2 =
2961+
MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), None).unwrap();
2962+
assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 11);
2963+
let echoed: String = sbox2.call("Echo", "hi".to_string()).unwrap();
2964+
assert_eq!(echoed, "hi");
2965+
}
2966+
2967+
#[test]
2968+
fn round_trip_pre_init_snapshot() {
2969+
let path = simple_guest_as_string().unwrap();
2970+
let snap =
2971+
Snapshot::from_env(GuestBinary::FilePath(path), SandboxConfiguration::default())
2972+
.unwrap();
2973+
let mut sbox =
2974+
MultiUseSandbox::from_snapshot(Arc::new(snap), HostFunctions::default(), None)
2975+
.unwrap();
2976+
assert_eq!(sbox.call::<i32>("GetStatic", ()).unwrap(), 0);
2977+
}
2978+
2979+
/// Sandboxes built from clones of one `Arc<Snapshot>` share
2980+
/// `sandbox_id` (so both can `restore` to it) but are
2981+
/// memory-isolated from each other.
2982+
#[test]
2983+
fn arc_clone_isolation_and_restore_compat() {
2984+
let mut sbox = make_sandbox();
2985+
sbox.call::<i32>("AddToStatic", 3i32).unwrap();
2986+
let snapshot = sbox.snapshot().unwrap();
2987+
2988+
let mut a =
2989+
MultiUseSandbox::from_snapshot(snapshot.clone(), HostFunctions::default(), None)
2990+
.unwrap();
2991+
let mut b =
2992+
MultiUseSandbox::from_snapshot(snapshot.clone(), HostFunctions::default(), None)
2993+
.unwrap();
2994+
assert_eq!(a.call::<i32>("GetStatic", ()).unwrap(), 3);
2995+
assert_eq!(b.call::<i32>("GetStatic", ()).unwrap(), 3);
2996+
2997+
a.call::<i32>("AddToStatic", 7i32).unwrap();
2998+
assert_eq!(a.call::<i32>("GetStatic", ()).unwrap(), 10);
2999+
assert_eq!(b.call::<i32>("GetStatic", ()).unwrap(), 3);
3000+
3001+
a.restore(snapshot.clone()).unwrap();
3002+
b.restore(snapshot).unwrap();
3003+
assert_eq!(a.call::<i32>("GetStatic", ()).unwrap(), 3);
3004+
assert_eq!(b.call::<i32>("GetStatic", ()).unwrap(), 3);
3005+
}
3006+
3007+
#[test]
3008+
fn accepts_matching_host_functions() {
3009+
let mut sbox = make_sandbox_with_add();
3010+
sbox.call::<i32>("AddToStatic", 5i32).unwrap();
3011+
let snap = sbox.snapshot().unwrap();
3012+
let mut sbox2 =
3013+
MultiUseSandbox::from_snapshot(snap, host_funcs_with_matching_add(), None).unwrap();
3014+
assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 5);
3015+
}
3016+
3017+
#[test]
3018+
fn rejects_missing_host_function() {
3019+
let mut sbox = make_sandbox_with_add();
3020+
let snap = sbox.snapshot().unwrap();
3021+
let err = MultiUseSandbox::from_snapshot(snap, HostFunctions::default(), None)
3022+
.expect_err("missing `Add` must be rejected");
3023+
let msg = format!("{}", err);
3024+
assert!(msg.contains("Add"), "got: {}", msg);
3025+
}
3026+
3027+
#[test]
3028+
fn rejects_signature_mismatch() {
3029+
let mut sbox = make_sandbox_with_add();
3030+
let snap = sbox.snapshot().unwrap();
3031+
let mut hf = HostFunctions::default();
3032+
hf.register_host_function("Add", |a: String, b: String| Ok(format!("{a}{b}")))
3033+
.unwrap();
3034+
let err = MultiUseSandbox::from_snapshot(snap, hf, None)
3035+
.expect_err("signature mismatch on `Add` must be rejected");
3036+
let msg = format!("{}", err);
3037+
assert!(msg.contains("Add"), "got: {}", msg);
3038+
}
3039+
3040+
/// Supplied host-function set may be a strict superset of the
3041+
/// snapshot's required set.
3042+
#[test]
3043+
fn accepts_extra_host_functions() {
3044+
let mut sbox = make_sandbox_with_add();
3045+
sbox.call::<i32>("AddToStatic", 9i32).unwrap();
3046+
let snap = sbox.snapshot().unwrap();
3047+
let mut hf = host_funcs_with_matching_add();
3048+
hf.register_host_function("Mul", |a: i32, b: i32| Ok(a * b))
3049+
.unwrap();
3050+
let mut sbox2 = MultiUseSandbox::from_snapshot(snap, hf, None).unwrap();
3051+
assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 9);
3052+
}
3053+
3054+
/// A sandbox built via `from_snapshot` can itself be snapshotted
3055+
/// and restored, and its snapshots are restore-compatible with it.
3056+
#[test]
3057+
fn re_snapshot_after_from_snapshot() {
3058+
let mut sbox = make_sandbox();
3059+
sbox.call::<i32>("AddToStatic", 4i32).unwrap();
3060+
let snap1 = sbox.snapshot().unwrap();
3061+
3062+
let mut sbox2 =
3063+
MultiUseSandbox::from_snapshot(snap1, HostFunctions::default(), None).unwrap();
3064+
sbox2.call::<i32>("AddToStatic", 6i32).unwrap();
3065+
let snap2 = sbox2.snapshot().unwrap();
3066+
3067+
sbox2.call::<i32>("AddToStatic", 100i32).unwrap();
3068+
assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 110);
3069+
3070+
sbox2.restore(snap2.clone()).unwrap();
3071+
assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 10);
3072+
3073+
let mut sbox3 =
3074+
MultiUseSandbox::from_snapshot(snap2, HostFunctions::default(), None).unwrap();
3075+
assert_eq!(sbox3.call::<i32>("GetStatic", ()).unwrap(), 10);
3076+
}
3077+
3078+
/// The host function closure supplied to `from_snapshot` (not the
3079+
/// original sandbox's closure) is the one invoked at runtime.
3080+
#[test]
3081+
fn supplied_host_function_is_callable() {
3082+
let path = simple_guest_as_string().unwrap();
3083+
let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
3084+
u.register_host_function("Echo42", || Ok(1i64)).unwrap();
3085+
let mut sbox = u.evolve().unwrap();
3086+
let snap = sbox.snapshot().unwrap();
3087+
3088+
let mut hf = HostFunctions::default();
3089+
hf.register_host_function("Echo42", || Ok(42i64)).unwrap();
3090+
let mut sbox2 = MultiUseSandbox::from_snapshot(snap, hf, None).unwrap();
3091+
3092+
let got: i64 = sbox2
3093+
.call(
3094+
"CallGivenParamlessHostFuncThatReturnsI64",
3095+
"Echo42".to_string(),
3096+
)
3097+
.unwrap();
3098+
assert_eq!(got, 42);
3099+
}
3100+
3101+
/// Pre-init snapshots record no required host functions, so any
3102+
/// `HostFunctions` set is accepted.
3103+
#[test]
3104+
fn pre_init_snapshot_accepts_arbitrary_host_functions() {
3105+
let path = simple_guest_as_string().unwrap();
3106+
let snap =
3107+
Snapshot::from_env(GuestBinary::FilePath(path), SandboxConfiguration::default())
3108+
.unwrap();
3109+
let mut hf = HostFunctions::default();
3110+
hf.register_host_function("Unrelated", |a: i32| Ok(a + 1))
3111+
.unwrap();
3112+
let mut sbox = MultiUseSandbox::from_snapshot(Arc::new(snap), hf, None).unwrap();
3113+
assert_eq!(sbox.call::<i32>("GetStatic", ()).unwrap(), 0);
3114+
}
3115+
3116+
/// Snapshots taken from a sandbox built via `from_snapshot`
3117+
/// must continue the generation counter of the snapshot they
3118+
/// were constructed from, matching `restore`.
3119+
#[test]
3120+
fn snapshot_generation_propagates() {
3121+
let mut sbox = make_sandbox();
3122+
sbox.call::<i32>("AddToStatic", 1i32).unwrap();
3123+
let snap1 = sbox.snapshot().unwrap();
3124+
let gen1 = snap1.snapshot_generation();
3125+
sbox.call::<i32>("AddToStatic", 1i32).unwrap();
3126+
let snap2 = sbox.snapshot().unwrap();
3127+
let gen2 = snap2.snapshot_generation();
3128+
assert_eq!(gen2, gen1 + 1);
3129+
3130+
let mut sbox2 =
3131+
MultiUseSandbox::from_snapshot(snap2, HostFunctions::default(), None).unwrap();
3132+
sbox2.call::<i32>("AddToStatic", 1i32).unwrap();
3133+
let snap3 = sbox2.snapshot().unwrap();
3134+
assert_eq!(snap3.snapshot_generation(), gen2 + 1);
3135+
}
3136+
3137+
/// Registering a host function on an already-evolved
3138+
/// `MultiUseSandbox` must invalidate its cached snapshot, so
3139+
/// that the next `snapshot()` reflects the new required
3140+
/// host-function set.
3141+
#[test]
3142+
fn late_register_invalidates_snapshot_cache() {
3143+
let mut sbox = make_sandbox();
3144+
// Force a cached snapshot to exist.
3145+
let _ = sbox.snapshot().unwrap();
3146+
3147+
sbox.register_host_function("Echo42", || Ok(42i64)).unwrap();
3148+
3149+
// The next snapshot must include `Echo42` as a required
3150+
// host function, so building a sandbox from it without
3151+
// `Echo42` must fail.
3152+
let snap = sbox.snapshot().unwrap();
3153+
let err = MultiUseSandbox::from_snapshot(snap, HostFunctions::default(), None)
3154+
.expect_err("late-registered `Echo42` must be required by the new snapshot");
3155+
let msg = format!("{}", err);
3156+
assert!(msg.contains("Echo42"), "got: {}", msg);
3157+
}
3158+
}
29193159
}

0 commit comments

Comments
 (0)