Skip to content

Commit 2de6145

Browse files
committed
Improve sandbox smoke test reliability
1 parent fe4816e commit 2de6145

1 file changed

Lines changed: 64 additions & 25 deletions

File tree

packages/admin-cli/src/sandbox-smoke.ts

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
101105
async 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+
145167
async 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

Comments
 (0)