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