@@ -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