@@ -178,3 +178,118 @@ func TestPersistentSandboxE2E(t *testing.T) {
178178 t .Fatalf ("expected run after terminate to fail with unknown sandbox, got %v" , err )
179179 }
180180}
181+
182+ func TestPersistentSandboxE2EExecStreamingDoesNotHang (t * testing.T ) {
183+ if strings .TrimSpace (os .Getenv (darwinVZE2EEnvEnabled )) == "" {
184+ t .Skipf ("set %s=1 to run real darwin-vz persistence e2e" , darwinVZE2EEnvEnabled )
185+ }
186+ if testing .Short () {
187+ t .Skip ("skipping darwin-vz e2e in short mode" )
188+ }
189+
190+ helperPath , err := resolveHelperBinaryPath ()
191+ if err != nil {
192+ t .Fatalf ("resolve helper binary: %v" , err )
193+ }
194+ hasEntitlement , err := helperHasVirtualizationEntitlement (helperPath )
195+ if err != nil {
196+ t .Fatalf ("verify helper entitlement: %v" , err )
197+ }
198+ if ! hasEntitlement {
199+ t .Fatalf ("helper %q is missing com.apple.security.virtualization entitlement" , helperPath )
200+ }
201+ if _ , _ , err := New ().getGuestAgentBinary (); err != nil {
202+ t .Fatalf ("resolve guest agent binary: %v" , err )
203+ }
204+
205+ rootFSOverride := strings .TrimSpace (os .Getenv (darwinVZE2EEnvRootFS ))
206+ if rootFSOverride == "" {
207+ if _ , err := hosttools .ResolveE2FSProgsBinary ("mkfs.ext4" ); err != nil {
208+ t .Fatalf ("resolve mkfs.ext4: %v" , err )
209+ }
210+ if _ , err := hosttools .ResolveE2FSProgsBinary ("debugfs" ); err != nil {
211+ t .Fatalf ("resolve debugfs: %v" , err )
212+ }
213+ }
214+
215+ imageRef := strings .TrimSpace (os .Getenv (darwinVZE2EEnvImageRef ))
216+ if imageRef == "" {
217+ imageRef = defaultDarwinVZE2EImageRef ()
218+ }
219+
220+ cfg := backend.FirecrackerConfig {
221+ KernelImagePath : strings .TrimSpace (os .Getenv (darwinVZE2EEnvKernelImage )),
222+ RootFSPath : rootFSOverride ,
223+ VCPUs : 1 ,
224+ MemoryMiB : 1024 ,
225+ LaunchSeconds : 90 ,
226+ }
227+ compiled := & policy.CompiledPolicy {
228+ Version : 1 ,
229+ ImageRef : imageRef ,
230+ NetworkDefault : "deny" ,
231+ }
232+ sandboxID := fmt .Sprintf ("cr-e2e-streaming-%d" , time .Now ().UnixNano ())
233+ ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Minute )
234+ defer cancel ()
235+
236+ adapter := New ()
237+ if err := adapter .ProvisionSandbox (ctx , backend.ProvisionRequest {
238+ SandboxID : sandboxID ,
239+ Policy : compiled ,
240+ FirecrackerConfig : cfg ,
241+ }); err != nil {
242+ t .Fatalf ("ProvisionSandbox returned error: %v" , err )
243+ }
244+
245+ defer func () {
246+ if err := adapter .TerminateSandbox (context .Background (), sandboxID ); err != nil {
247+ t .Fatalf ("deferred TerminateSandbox returned error: %v" , err )
248+ }
249+ }()
250+
251+ // A quick warm-up run that exercises the readiness/proxy path before we
252+ // assert repeated non-interactive executions complete promptly.
253+ warmupCtx , warmupCancel := context .WithTimeout (ctx , 60 * time .Second )
254+ warmup , err := adapter .RunInSandbox (warmupCtx , backend.RunRequest {
255+ SandboxID : sandboxID ,
256+ RunID : "run-warmup" ,
257+ Command : []string {"sh" , "-lc" , "echo warmup" },
258+ Policy : compiled ,
259+ FirecrackerConfig : backend.FirecrackerConfig {
260+ LaunchSeconds : cfg .LaunchSeconds ,
261+ },
262+ }, backend.OutputStream {})
263+ warmupCancel ()
264+ if err != nil {
265+ t .Fatalf ("warm-up RunInSandbox returned error: %v" , err )
266+ }
267+ if warmup .ExitCode != 0 {
268+ t .Fatalf ("expected warm-up exit code 0, got %d (stderr=%q)" , warmup .ExitCode , warmup .Stderr )
269+ }
270+
271+ for i := 0 ; i < 8 ; i ++ {
272+ runID := fmt .Sprintf ("run-stream-%d" , i )
273+ want := fmt .Sprintf ("stream-%d" , i )
274+ runCtx , runCancel := context .WithTimeout (ctx , 30 * time .Second )
275+ res , err := adapter .RunInSandbox (runCtx , backend.RunRequest {
276+ SandboxID : sandboxID ,
277+ RunID : runID ,
278+ Command : []string {"sh" , "-lc" , "echo " + want },
279+ Policy : compiled ,
280+ FirecrackerConfig : backend.FirecrackerConfig {
281+ LaunchSeconds : cfg .LaunchSeconds ,
282+ },
283+ }, backend.OutputStream {})
284+ runCancel ()
285+ if err != nil {
286+ t .Fatalf ("RunInSandbox %q returned error: %v" , runID , err )
287+ }
288+ if res .ExitCode != 0 {
289+ t .Fatalf ("expected %q exit code 0, got %d (stderr=%q)" , runID , res .ExitCode , res .Stderr )
290+ }
291+ if got := strings .TrimSpace (res .Stdout ); got != want {
292+ t .Fatalf ("unexpected stdout for %q: got %q want %q" , runID , got , want )
293+ }
294+ }
295+ }
0 commit comments