@@ -7,22 +7,35 @@ defmodule Hyper.E2e.GrpcContractTest do
77 VM lifecycle are asserted over the real wire, catching proto/codec/server
88 drift a BEAM-side client cannot produce (e.g. unrecognised enum integers).
99
10+ Also carries one BEAM-side test of `ForkVm`: it needs a real booted parent
11+ VM (Firecracker + device-mapper), which the TypeScript suite cannot boot,
12+ so it drives `Hyper.Grpc.V0.Hyper.Stub` directly over the same server
13+ instead.
14+
1015 Runs only under `--only integration` on a provisioned host (CI: the
1116 `integration` job). Requires node/npm on PATH; installs the suite's npm
1217 deps on first run.
1318 """
1419 use ExUnit.Case , async: false
1520
21+ alias Hyper.Grpc.V0 . { ForkVmRequest , ForkVmResponse , StopVmRequest }
22+ alias Hyper.Grpc.V0.Hyper.Stub
23+
1624 @ moduletag :integration
1725 @ moduletag timeout: :timer . minutes ( 25 )
1826
1927 @ port 50_061
2028 @ suite_dir Path . expand ( "../grpc" , __DIR__ )
2129
30+ # public.ecr.aws mirrors library images without Docker Hub's per-IP pull
31+ # limits, which shared GHA egress IPs routinely exhaust.
32+ @ image System . get_env ( "HYPER_E2E_IMAGE" , "public.ecr.aws/docker/library/alpine:3.19" )
33+
2234 setup_all do
2335 config = % Hyper.Cfg.Grpc { enabled: true , port: @ port }
2436 start_supervised! ( { GRPC.Server.Supervisor , Hyper.Cfg.Grpc . server_options ( config ) } )
25- :ok
37+ { :ok , channel } = GRPC.Stub . connect ( "127.0.0.1:#{ @ port } " , adapter: GRPC.Client.Adapters.Gun )
38+ { :ok , channel: channel }
2639 end
2740
2841 test "TypeScript contract suite passes against the live server" do
@@ -39,6 +52,24 @@ defmodule Hyper.E2e.GrpcContractTest do
3952 assert status == 0 , "TypeScript gRPC contract suite failed (exit #{ status } ); see output above"
4053 end
4154
55+ test "ForkVm boots a distinct child from a running parent" , % { channel: channel } do
56+ assert { :ok , img_id } = Hyper.Img.OciLoader . load ( @ image )
57+
58+ assert { :ok , parent } = Hyper . create_vm ( % Hyper.Vm.Spec { img_id: img_id , type: :micro } )
59+ on_exit ( fn -> Hyper.Node . stop_image_vm ( parent ) end )
60+
61+ parent_id = Hyper . id ( parent )
62+ assert parent_id , "Hyper.id/1 returned nil for a freshly-created VM"
63+
64+ assert { :ok , % ForkVmResponse { vm_id: child_id , node: child_node } } =
65+ Stub . fork_vm ( channel , % ForkVmRequest { vm_id: parent_id } )
66+
67+ assert is_binary ( child_id ) and child_id != parent_id
68+ assert child_node != ""
69+
70+ on_exit ( fn -> Stub . stop_vm ( channel , % StopVmRequest { vm_id: child_id } ) end )
71+ end
72+
4273 defp ensure_node_deps! do
4374 if not File . dir? ( Path . join ( @ suite_dir , "node_modules" ) ) do
4475 { out , status } = System . cmd ( "npm" , [ "ci" ] , cd: @ suite_dir , stderr_to_stdout: true )
0 commit comments