Files
2026-07-13 12:47:58 +08:00

204 lines
6.3 KiB
TypeScript

import { assert, describe } from 'vitest'
import { sandboxTest, isDebug } from '../setup.js'
sandboxTest.skipIf(isDebug)(
'pause and resume a sandbox',
async ({ sandbox }) => {
assert.isTrue(await sandbox.isRunning())
await sandbox.pause()
assert.isFalse(await sandbox.isRunning())
const resumedSandbox = await sandbox.connect()
assert.equal(resumedSandbox.sandboxId, sandbox.sandboxId)
assert.isTrue(await sandbox.isRunning())
}
)
describe('pause and resume with env vars', () => {
sandboxTest.scoped({
sandboxOpts: {
envs: { TEST_VAR: 'sfisback' },
},
})
sandboxTest.skipIf(isDebug)(
'pause and resume a sandbox with env vars',
async ({ sandbox }) => {
// Environment variables of a process exist at runtime, and are not stored in some file or so.
// They are stored in the process's own memory
const cmd = await sandbox.commands.run('echo "$TEST_VAR"')
assert.equal(cmd.exitCode, 0)
assert.equal(cmd.stdout.trim(), 'sfisback')
await sandbox.pause()
assert.isFalse(await sandbox.isRunning())
const resumedSandbox = await sandbox.connect()
assert.isTrue(await sandbox.isRunning())
assert.isTrue(await resumedSandbox.isRunning())
assert.equal(resumedSandbox.sandboxId, sandbox.sandboxId)
const cmd2 = await sandbox.commands.run('echo "$TEST_VAR"')
assert.equal(cmd2.exitCode, 0)
assert.equal(cmd2.stdout.trim(), 'sfisback')
}
)
})
sandboxTest.skipIf(isDebug)(
'pause and resume a sandbox with file',
async ({ sandbox }) => {
const filename = 'test_snapshot.txt'
const content = 'This is a snapshot test file.'
const info = await sandbox.files.write(filename, content)
assert.equal(info.name, filename)
assert.equal(info.type, 'file')
assert.equal(info.path, `/home/user/${filename}`)
const exists = await sandbox.files.exists(filename)
assert.isTrue(exists)
const readContent = await sandbox.files.read(filename)
assert.equal(readContent, content)
await sandbox.pause()
assert.isFalse(await sandbox.isRunning())
await sandbox.connect()
assert.isTrue(await sandbox.isRunning())
const exists2 = await sandbox.files.exists(filename)
assert.isTrue(exists2)
const readContent2 = await sandbox.files.read(filename)
assert.equal(readContent2, content)
}
)
sandboxTest.skipIf(isDebug)(
'pause and resume a sandbox with ongoing long running process',
async ({ sandbox }) => {
const cmd = await sandbox.commands.run('sleep 3600', { background: true })
const expectedPid = cmd.pid
await sandbox.pause()
assert.isFalse(await sandbox.isRunning())
await sandbox.connect()
assert.isTrue(await sandbox.isRunning())
// First check that the command is in list
const list = await sandbox.commands.list()
assert.isTrue(list.some((c) => c.pid === expectedPid))
// Make sure we can connect to it
const processInfo = await sandbox.commands.connect(expectedPid)
assert.isObject(processInfo)
assert.equal(processInfo.pid, expectedPid)
}
)
sandboxTest.skipIf(isDebug)(
'pause and resume a sandbox with completed long running process',
async ({ sandbox }) => {
const filename = 'test_long_running.txt'
await sandbox.commands.run(
`sleep 2 && echo "done" > /home/user/${filename}`,
{
background: true,
}
)
// the file should not exist before 2 seconds have elapsed
const exists = await sandbox.files.exists(filename)
assert.isFalse(exists)
await sandbox.pause()
assert.isFalse(await sandbox.isRunning())
await sandbox.connect()
assert.isTrue(await sandbox.isRunning())
// the file should be created after more than 2 seconds have elapsed
await new Promise((resolve) => setTimeout(resolve, 2000))
const exists2 = await sandbox.files.exists(filename)
assert.isTrue(exists2)
const readContent2 = await sandbox.files.read(filename)
assert.equal(readContent2.trim(), 'done')
}
)
sandboxTest.skipIf(isDebug)(
'pause and resume a sandbox with http server',
async ({ sandbox }) => {
await sandbox.commands.run('python3 -m http.server 8000', {
background: true,
})
let url = await sandbox.getHost(8000)
await new Promise((resolve) => setTimeout(resolve, 5000))
const response1 = await fetch(`https://${url}`)
assert.equal(response1.status, 200)
await sandbox.pause()
assert.isFalse(await sandbox.isRunning())
await sandbox.connect()
assert.isTrue(await sandbox.isRunning())
url = await sandbox.getHost(8000)
const response2 = await fetch(`https://${url}`)
assert.equal(response2.status, 200)
}
)
sandboxTest.skipIf(isDebug)(
'filesystem-only pause reboots on resume but keeps the filesystem',
async ({ sandbox }) => {
// Absolute path: a cold boot may not restore the template's default
// user/cwd, so a relative path could resolve differently after resume.
const filename = '/home/user/fs_only_snapshot.txt'
const content = 'This is a filesystem-only snapshot test file.'
await sandbox.files.write(filename, content)
// Kernel boot id before the pause; it changes only across a real (cold) boot.
const bootBefore = (
await sandbox.files.read('/proc/sys/kernel/random/boot_id')
).trim()
// Filesystem-only snapshot: no memory is captured, so resuming cold-boots.
await sandbox.pause({ keepMemory: false })
assert.isFalse(await sandbox.isRunning())
// Resume the paused sandbox; a filesystem-only pause keeps no memory, so
// connect() cold-boots (reboots) it. connect() returns the same handle, and
// its credentials stay valid across the resume (the backend re-binds the
// same envd access token on the cold boot).
const resumedSandbox = await sandbox.connect()
assert.equal(resumedSandbox.sandboxId, sandbox.sandboxId)
assert.isTrue(await resumedSandbox.isRunning())
// The filesystem survives the cold boot.
assert.isTrue(await resumedSandbox.files.exists(filename))
assert.equal(await resumedSandbox.files.read(filename), content)
// A fresh boot id proves the guest rebooted rather than restoring memory.
const bootAfter = (
await resumedSandbox.files.read('/proc/sys/kernel/random/boot_id')
).trim()
assert.notEqual(bootAfter, bootBefore)
}
)