@@ -98,6 +98,10 @@ function makeRunId(): string {
9898 return `admin-smoke-${ Date . now ( ) } -${ crypto . randomUUID ( ) . slice ( 0 , 8 ) } `
9999}
100100
101+ function sleep ( ms : number ) : Promise < void > {
102+ return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) )
103+ }
104+
101105async function fetchSandboxState (
102106 baseUrl : string ,
103107 apiKey : string ,
@@ -142,6 +146,24 @@ function formatSandboxState(snapshot: SandboxStateSnapshot | null): string {
142146 return `sandbox state ${ snapshot . status } `
143147}
144148
149+ async function waitForArtifact (
150+ sandbox : Sandbox ,
151+ expectedName : string ,
152+ timeoutMs : number ,
153+ ) : Promise < boolean > {
154+ const deadline = Date . now ( ) + timeoutMs
155+
156+ while ( Date . now ( ) < deadline ) {
157+ const artifacts = await sandbox . artifacts . list ( )
158+ if ( artifacts . some ( ( artifact : { name : string } ) => artifact . name === expectedName ) ) {
159+ return true
160+ }
161+ await sleep ( 250 )
162+ }
163+
164+ return false
165+ }
166+
145167async function measureCheck (
146168 name : string ,
147169 logger : SandboxSmokeLogger | undefined ,
@@ -376,29 +398,41 @@ export async function runSandboxSmokeTest(
376398 let forkSandbox : Sandbox | undefined
377399 checks . push (
378400 await measureCheck ( 'fork sandbox' , logger , async ( ) => {
379- forkSandbox = await sandbox . fork ( {
380- ttlSeconds : resolved . ttlSeconds ,
381- } )
382- tracker . trackSandbox ( 'fork' , forkSandbox )
383- forkSandboxId = forkSandbox . id
401+ try {
402+ forkSandbox = await sandbox . fork ( {
403+ ttlSeconds : resolved . ttlSeconds ,
404+ } )
405+ tracker . trackSandbox ( 'fork' , forkSandbox )
406+ forkSandboxId = forkSandbox . id
384407
385- const forkReadback = await forkSandbox . fs . read ( sharedPath )
386- assert ( forkReadback === fileContents , 'Fork did not inherit parent filesystem state' )
408+ const forkReadback = await forkSandbox . fs . read ( sharedPath )
409+ assert ( forkReadback === fileContents , 'Fork did not inherit parent filesystem state' )
387410
388- const forkExec = await forkSandbox . exec (
389- [ 'sh' , '-lc' , 'test "$SMOKE_FORK" = "$EXPECTED" && printf fork-ok' ] ,
390- { env : { SMOKE_FORK : runId , EXPECTED : runId } , timeout : 60 } ,
391- )
392- assertExecSuccess ( forkExec , 'fork exec' )
393- assert ( forkExec . stdout === 'fork-ok' , `Unexpected fork stdout: ${ JSON . stringify ( forkExec . stdout ) } ` )
411+ const forkExec = await forkSandbox . exec (
412+ [ 'sh' , '-lc' , 'test "$SMOKE_FORK" = "$EXPECTED" && printf fork-ok' ] ,
413+ { env : { SMOKE_FORK : runId , EXPECTED : runId } , timeout : 60 } ,
414+ )
415+ assertExecSuccess ( forkExec , 'fork exec' )
416+ assert ( forkExec . stdout === 'fork-ok' , `Unexpected fork stdout: ${ JSON . stringify ( forkExec . stdout ) } ` )
394417
395- const forkTree = await sandbox . forks ( )
396- assert (
397- forkTree . tree . some (
398- ( node : { sandbox_id : string } ) => node . sandbox_id === forkSandbox ! . id ,
399- ) ,
400- `Fork tree did not include ${ forkSandbox . id } ` ,
401- )
418+ const forkTree = await sandbox . forks ( )
419+ assert (
420+ forkTree . tree . some (
421+ ( node : { sandbox_id : string } ) => node . sandbox_id === forkSandbox ! . id ,
422+ ) ,
423+ `Fork tree did not include ${ forkSandbox . id } ` ,
424+ )
425+ } catch ( error ) {
426+ const sourceSnapshot = await fetchSandboxState ( resolved . baseUrl , resolved . apiKey , sandbox . id )
427+ const forkSnapshot = forkSandboxId
428+ ? await fetchSandboxState ( resolved . baseUrl , resolved . apiKey , forkSandboxId )
429+ : null
430+ const normalized = toError ( error )
431+ throw new Error (
432+ `${ normalized . message } ; source ${ formatSandboxState ( sourceSnapshot ) } ; fork ${ formatSandboxState ( forkSnapshot ) } ` ,
433+ { cause : normalized } ,
434+ )
435+ }
402436 } ) ,
403437 )
404438
@@ -423,6 +457,13 @@ export async function runSandboxSmokeTest(
423457
424458 checks . push (
425459 await measureCheck ( 'collect artifacts on stop' , logger , async ( ) => {
460+ await sandbox . fs . write ( sharedPath , fileContents )
461+ const artifactReadback = await sandbox . fs . read ( sharedPath )
462+ assert (
463+ artifactReadback === fileContents ,
464+ 'artifact file could not be refreshed before stop' ,
465+ )
466+
426467 await sandbox . stop ( )
427468 assert (
428469 sandbox . status === 'stopping' || sandbox . status === 'stopped' ,
@@ -435,11 +476,9 @@ export async function runSandboxSmokeTest(
435476 `Expected fetched root sandbox to be stopping or stopped, got ${ fetched . status } ` ,
436477 )
437478
438- const artifacts = await sandbox . artifacts . list ( )
439- assert (
440- artifacts . some ( ( artifact : { name : string } ) => artifact . name === sharedPath ) ,
441- `artifact list did not include ${ sharedPath } ` ,
442- )
479+ const expectedArtifactName = sharedPath . split ( '/' ) . filter ( Boolean ) . pop ( ) ?? sharedPath
480+ const foundArtifact = await waitForArtifact ( sandbox , expectedArtifactName , 5_000 )
481+ assert ( foundArtifact , `artifact list did not include ${ expectedArtifactName } ` )
443482 } ) ,
444483 )
445484 } catch ( error ) {
0 commit comments