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