From eee2c1262a310dd533d7df8e104f7e319775cfd9 Mon Sep 17 00:00:00 2001 From: terasum Date: Wed, 11 Mar 2026 16:46:12 +0800 Subject: [PATCH] feat: support mount with polarfs Signed-off-by: terasum --- .gitignore | 4 +- package.json | 6 +- tests/e2e/sandbox/sandbox-create-run.test.ts | 556 ++++++++++++++++++ .../sandbox-create-with-polarfs.test.ts | 434 ++++++++++++++ 4 files changed, 997 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/sandbox/sandbox-create-run.test.ts create mode 100644 tests/e2e/sandbox/sandbox-create-with-polarfs.test.ts diff --git a/.gitignore b/.gitignore index 51ae6f3..4496a8c 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,6 @@ pnpm-lock.yaml .coverage bun.lock -package-lock.json \ No newline at end of file +package-lock.json +pnpm-workspace.yaml +libs/ \ No newline at end of file diff --git a/package.json b/package.json index 37723be..78f47d9 100644 --- a/package.json +++ b/package.json @@ -63,8 +63,10 @@ "codegen": "npx tsx scripts/codegen.ts", "generate-exports": "node scripts/generate-exports.mjs", "inject-version-check": "node scripts/inject-version-check.mjs", + "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", + "test:e2e": "jest --config jest.config.e2e.ts --testPathPattern='tests/e2e'", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", "format": "prettier --write .", @@ -100,7 +102,7 @@ "dependencies": { "@ai-sdk/openai": "^3.0.0", "@ai-sdk/openai-compatible": "^2.0.13", - "@alicloud/agentrun20250910": "^5.0.0", + "@alicloud/agentrun20250910": "^5.4.2", "@alicloud/devs20230714": "^2.4.1", "@alicloud/openapi-client": "^0.4.12", "@alicloud/tea-util": "^1.4.9", @@ -148,4 +150,4 @@ "typescript": "^5.4.0", "yaml": "^2.7.0" } -} +} \ No newline at end of file diff --git a/tests/e2e/sandbox/sandbox-create-run.test.ts b/tests/e2e/sandbox/sandbox-create-run.test.ts new file mode 100644 index 0000000..070704d --- /dev/null +++ b/tests/e2e/sandbox/sandbox-create-run.test.ts @@ -0,0 +1,556 @@ +/** + * Sandbox 创建和运行测试 + * + * 测试覆盖完整的 Sandbox 创建和运行流程: + * - 创建 Template + * - 创建 Sandbox + * - 等待 Sandbox 就绪 + * - 执行代码 + * - 文件操作 + * - 进程操作 + * - 清理资源 + * + * 运行前请确保设置环境变量: + * - AGENTRUN_ACCESS_KEY_ID + * - AGENTRUN_ACCESS_KEY_SECRET + * - AGENTRUN_ACCOUNT_ID + */ + +import { + CodeInterpreterSandbox, + Template, + TemplateType, + TemplateNetworkMode, + CodeLanguage, + Sandbox, + SandboxState, +} from '../../../src/sandbox'; +import { ResourceNotExistError, ClientError } from '../../../src/utils/exception'; +import { logger } from '../../../src/utils/log'; +import type { TemplateCreateInput } from '../../../src/sandbox'; + +/** + * 生成唯一名称 + */ +function generateUniqueName(prefix: string): string { + const timestamp = Date.now(); + const random = Math.random().toString(36).substring(2, 8); + return `${prefix}-${timestamp}-${random}`; +} + +describe('Sandbox Create and Run E2E Tests', () => { + let templateName: string; + let sandbox: CodeInterpreterSandbox | undefined; + let template: Template | undefined; + + beforeAll(async () => { + templateName = generateUniqueName('test-create-run-template'); + }); + + afterAll(async () => { + // 清理 Sandbox + if (sandbox?.sandboxId) { + try { + await sandbox.delete(); + logger.info('Sandbox 已清理 / Sandbox cleaned up'); + } catch (error) { + if (!(error instanceof ResourceNotExistError)) { + logger.error('清理 Sandbox 失败 / Failed to cleanup sandbox:', error); + } + } + } + + // 清理 Template + try { + await Template.delete({ name: templateName }); + logger.info('Template 已清理 / Template cleaned up'); + } catch (error) { + if (!(error instanceof ResourceNotExistError)) { + logger.error('清理 Template 失败 / Failed to cleanup template:', error); + } + } + }); + + // ========== 模板创建测试 ========== + + describe('Template Creation', () => { + it('should create a Code Interpreter template successfully', async () => { + const templateInput: TemplateCreateInput = { + templateName, + templateType: TemplateType.CODE_INTERPRETER, + description: 'Sandbox Create Run Test - Code Interpreter Template', + cpu: 2.0, + memory: 4096, + diskSize: 512, + sandboxIdleTimeoutInSeconds: 600, + networkConfiguration: { + networkMode: TemplateNetworkMode.PUBLIC, + }, + }; + + template = await Template.create({ input: templateInput }); + + expect(template).toBeDefined(); + expect(template.templateName).toBe(templateName); + expect(template.templateType).toBe(TemplateType.CODE_INTERPRETER); + expect(template.status).toBeDefined(); + }); + + it('should wait for template to be ready', async () => { + expect(template).toBeDefined(); + + await template!.waitUntilReadyOrFailed({ + timeoutSeconds: 180, + intervalSeconds: 5, + callback: t => { + logger.info(`等待模板就绪 / Waiting for template: ${t.status}`); + }, + }); + + expect(template!.status).toBe('READY'); + }); + }); + + // ========== Sandbox 创建测试 ========== + + describe('Sandbox Creation', () => { + it('should create a Code Interpreter sandbox from template', async () => { + expect(template).toBeDefined(); + + sandbox = await CodeInterpreterSandbox.createFromTemplate(templateName, { + sandboxIdleTimeoutSeconds: 600, + }); + + expect(sandbox).toBeDefined(); + expect(sandbox.sandboxId).toBeDefined(); + expect(sandbox.templateName).toBe(templateName); + expect(sandbox.state).toBeDefined(); + }); + + it('should wait for sandbox to be running', async () => { + expect(sandbox).toBeDefined(); + + await sandbox!.waitUntilRunning({ + timeoutSeconds: 120, + intervalSeconds: 5, + beforeCheck: s => { + logger.info(`等待沙箱运行 / Waiting for sandbox: ${s.state}`); + }, + }); + + expect([SandboxState.RUNNING, SandboxState.READY]).toContain(sandbox!.state!); + }); + + it('should pass health check', async () => { + expect(sandbox).toBeDefined(); + + await sandbox!.waitUntilReadyOrFailed({ + timeoutSeconds: 60, + intervalSeconds: 3, + }); + + // 通过健康检查意味着 sandbox 可以正常运行 + expect(sandbox!.sandboxId).toBeDefined(); + }); + }); + + // ========== 代码执行测试 ========== + + describe('Code Execution', () => { + it('should execute simple Python code', async () => { + expect(sandbox).toBeDefined(); + + const ctx = await sandbox!.context.create({ language: CodeLanguage.PYTHON }); + const result = await ctx.execute({ + code: "print('Hello from sandbox!')", + }); + + expect(result).toBeDefined(); + expect(result.exitCode).toBe(0); + + await ctx.delete(); + }); + + it('should execute Python code with variables', async () => { + expect(sandbox).toBeDefined(); + + const ctx = await sandbox!.context.create({ language: CodeLanguage.PYTHON }); + const result = await ctx.execute({ + code: ` +x = 10 +y = 20 +print(f"Result: {x + y}") +`, + }); + + expect(result).toBeDefined(); + expect(result.exitCode).toBe(0); + if (result.stdout) { + expect(result.stdout).toContain('Result: 30'); + } + + await ctx.delete(); + }); + + it('should execute multi-line Python code', async () => { + expect(sandbox).toBeDefined(); + + const ctx = await sandbox!.context.create({ language: CodeLanguage.PYTHON }); + const result = await ctx.execute({ + code: ` +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n-1) + fibonacci(n-2) + +for i in range(10): + print(fibonacci(i), end=",") +`, + }); + + expect(result).toBeDefined(); + expect(result.exitCode).toBe(0); + + await ctx.delete(); + }); + + it('should handle Python errors', async () => { + expect(sandbox).toBeDefined(); + + const ctx = await sandbox!.context.create({ language: CodeLanguage.PYTHON }); + const result = await ctx.execute({ + code: ` +result = 10 / 0 +`, + }); + + expect(result).toBeDefined(); + // 错误应该被捕获 + expect(result.exitCode).not.toBe(0); + + await ctx.delete(); + }); + }); + + // ========== 文件系统测试 ========== + + describe('File System Operations', () => { + it('should list root directory', async () => { + expect(sandbox).toBeDefined(); + + const files = await sandbox!.fileSystem.list({ path: '/' }); + + expect(files).toBeDefined(); + expect(Array.isArray(files)).toBe(true); + }); + + it('should create directory', async () => { + expect(sandbox).toBeDefined(); + + const testDir = '/home/user/test-create-run'; + await sandbox!.fileSystem.mkdir({ path: testDir }); + + // 验证目录创建成功 + const files = await sandbox!.fileSystem.list({ path: '/home/user' }); + const createdDir = files.find((f: any) => f.name === 'test-create-run'); + expect(createdDir).toBeDefined(); + }); + + it('should write and read file', async () => { + expect(sandbox).toBeDefined(); + + const testFile = '/home/user/test-create-run/test.txt'; + const testContent = 'Hello, World! This is a test file.'; + + // 写入文件 + await sandbox!.file.write({ + path: testFile, + content: testContent, + }); + + // 读取文件 + const result = await sandbox!.file.read({ path: testFile }); + expect(result).toBeDefined(); + expect(result.content).toBe(testContent); + }); + + it('should get file stat', async () => { + expect(sandbox).toBeDefined(); + + const testFile = '/home/user/test-create-run/test.txt'; + const stat = await sandbox!.fileSystem.stat({ path: testFile }); + + expect(stat).toBeDefined(); + expect(stat.name).toBe('test.txt'); + expect(stat.size).toBeGreaterThan(0); + }); + + it('should move file', async () => { + expect(sandbox).toBeDefined(); + + const sourceFile = '/home/user/test-create-run/test.txt'; + const destFile = '/home/user/test-create-run/moved_test.txt'; + + // 移动文件 + await sandbox!.fileSystem.move({ + source: sourceFile, + destination: destFile, + }); + + // 验证移动成功 + const files = await sandbox!.fileSystem.list({ path: '/home/user/test-create-run' }); + const movedFile = files.find((f: any) => f.name === 'moved_test.txt'); + expect(movedFile).toBeDefined(); + + // 原文件应该不存在 + try { + await sandbox!.fileSystem.stat({ path: sourceFile }); + throw new Error('Expected error'); + } catch (error) { + // 预期错误 + } + }); + + it('should upload and download file', async () => { + expect(sandbox).toBeDefined(); + + const localFile = '/tmp/test-upload.txt'; + const remoteFile = '/home/user/test-create-run/uploaded.txt'; + const testContent = `Upload test content - ${Date.now()}`; + let downloadPath = '/tmp/test-download.txt'; + + // 创建本地文件 + const fs = await import('fs/promises'); + await fs.writeFile(localFile, testContent); + + try { + // 上传文件 + await sandbox!.fileSystem.upload({ + localFilePath: localFile, + targetFilePath: remoteFile, + }); + + // 验证上传成功 + const stat = await sandbox!.fileSystem.stat({ path: remoteFile }); + expect(stat).toBeDefined(); + expect(stat.size).toBeGreaterThan(0); + + // 下载文件 + await sandbox!.fileSystem.download({ + path: remoteFile, + savePath: downloadPath, + }); + + // 验证下载内容 + const downloadedContent = await fs.readFile(downloadPath, 'utf-8'); + expect(downloadedContent).toBe(testContent); + } finally { + // 清理本地文件 + await fs.unlink(localFile).catch(() => {}); + await fs.unlink(downloadPath).catch(() => {}); + } + }); + + it('should remove directory', async () => { + expect(sandbox).toBeDefined(); + + const testDir = '/home/user/test-create-run'; + + // 删除目录 + await sandbox!.fileSystem.remove({ path: testDir }); + + // 验证删除成功 + try { + await sandbox!.fileSystem.stat({ path: testDir }); + throw new Error('Expected error'); + } catch (error) { + // 预期错误,目录已删除 + } + }); + }); + + // ========== 进程操作测试 ========== + + describe('Process Operations', () => { + it('should list processes', async () => { + expect(sandbox).toBeDefined(); + + const processes = await sandbox!.process.list(); + + expect(processes).toBeDefined(); + expect(Array.isArray(processes)).toBe(true); + expect(processes.length).toBeGreaterThan(0); + }); + + it('should execute shell command', async () => { + expect(sandbox).toBeDefined(); + + const result = await sandbox!.process.cmd({ + command: 'echo "Hello from shell"', + cwd: '/', + }); + + expect(result).toBeDefined(); + expect(result.exitCode).toBe(0); + if (result.stdout) { + expect(result.stdout).toContain('Hello from shell'); + } + }); + + it('should execute ls command', async () => { + expect(sandbox).toBeDefined(); + + const result = await sandbox!.process.cmd({ + command: 'ls -la /home/user', + cwd: '/', + }); + + expect(result).toBeDefined(); + expect(result.exitCode).toBe(0); + }); + + it('should get process details', async () => { + expect(sandbox).toBeDefined(); + + // PID 1 通常是 init 进程 + const process = await sandbox!.process.get({ pid: '1' }); + + expect(process).toBeDefined(); + expect(process.pid).toBe('1'); + }); + }); + + // ========== Sandbox 状态测试 ========== + + describe('Sandbox State', () => { + it('should refresh sandbox state', async () => { + expect(sandbox).toBeDefined(); + + const initialState = sandbox!.state; + await sandbox!.refresh(); + + expect(sandbox!.state).toBeDefined(); + // 刷新后状态应该仍然有效 + expect([SandboxState.RUNNING, SandboxState.READY]).toContain(sandbox!.state!); + }); + + it('should get sandbox details', async () => { + expect(sandbox).toBeDefined(); + + const details = await sandbox!.get(); + + expect(details).toBeDefined(); + expect(details.sandboxId).toBe(sandbox!.sandboxId); + expect(details.templateName).toBe(templateName); + }); + }); + + // ========== 停止和清理测试 ========== + + describe('Sandbox Stop and Cleanup', () => { + it('should stop sandbox', async () => { + expect(sandbox).toBeDefined(); + + await sandbox!.stop(); + + // 停止后 sandbox 状态应该更新 + expect(sandbox!.sandboxId).toBeDefined(); + }); + + it('should restart sandbox', async () => { + expect(sandbox).toBeDefined(); + + // 重新获取 sandbox 以刷新状态 + await sandbox!.refresh(); + + // 等待 sandbox 再次运行 + await sandbox!.waitUntilRunning({ + timeoutSeconds: 60, + intervalSeconds: 3, + }); + + expect([SandboxState.RUNNING, SandboxState.READY]).toContain(sandbox!.state!); + }); + }); +}); + +// ========== 独立测试:使用已存在的 Template ========== + +describe('Sandbox with Existing Template', () => { + let existingTemplateName: string; + let template: Template | undefined; + let sandbox: CodeInterpreterSandbox | undefined; + + beforeAll(async () => { + existingTemplateName = generateUniqueName('existing-template'); + + // 创建一个模板供后续使用 + template = await Template.create({ + input: { + templateName: existingTemplateName, + templateType: TemplateType.CODE_INTERPRETER, + description: 'Test template for existing template test', + cpu: 2.0, + memory: 4096, + diskSize: 512, + sandboxIdleTimeoutInSeconds: 300, + networkConfiguration: { + networkMode: TemplateNetworkMode.PUBLIC, + }, + }, + }); + + await template.waitUntilReadyOrFailed({ + timeoutSeconds: 180, + intervalSeconds: 5, + }); + }); + + afterAll(async () => { + // 清理 Sandbox + if (sandbox?.sandboxId) { + try { + await sandbox.delete(); + } catch { + // Ignore + } + } + + // 清理 Template + try { + await Template.delete({ name: existingTemplateName }); + } catch { + // Ignore + } + }); + + it('should create sandbox from existing template', async () => { + expect(template).toBeDefined(); + expect(template!.status).toBe('READY'); + + sandbox = await CodeInterpreterSandbox.createFromTemplate(existingTemplateName); + + expect(sandbox).toBeDefined(); + expect(sandbox.sandboxId).toBeDefined(); + expect(sandbox.templateName).toBe(existingTemplateName); + }); + + it('should execute code in new sandbox', async () => { + expect(sandbox).toBeDefined(); + + await sandbox!.waitUntilRunning({ + timeoutSeconds: 60, + intervalSeconds: 3, + }); + + const ctx = await sandbox!.context.create({ language: CodeLanguage.PYTHON }); + const result = await ctx.execute({ + code: "print('Test from existing template')", + }); + + expect(result).toBeDefined(); + expect(result.exitCode).toBe(0); + + await ctx.delete(); + }); +}); diff --git a/tests/e2e/sandbox/sandbox-create-with-polarfs.test.ts b/tests/e2e/sandbox/sandbox-create-with-polarfs.test.ts new file mode 100644 index 0000000..460965e --- /dev/null +++ b/tests/e2e/sandbox/sandbox-create-with-polarfs.test.ts @@ -0,0 +1,434 @@ +/** + * Sandbox 创建测试 - 使用 PolarFS 配置 + * + * 测试使用 PolarFS 配置创建 Sandbox 的完整流程: + * - 创建 Template + * - 等待 Template 就绪 + * - 使用 polarFsConfig 创建 Sandbox + * - 验证 Sandbox 状态 + * - 清理资源 + * + * 运行前请确保设置环境变量: + * - AGENTRUN_ACCESS_KEY_ID + * - AGENTRUN_ACCESS_KEY_SECRET= + * - AGENTRUN_ACCOUNT_ID=cq219740@1760720386195983.onaliyun.com + * - AGENTRUN_POLARFS_INSTANCE_ID=pfs-bp1p1t86b15z78s4 + * - AGENTRUN_VPC_ID=vpc-bp13a6qsr26ero2r1zebe + * - AGENTRUN_VSWITCH_IDS=vsw-bp1kusycsu29kdpi9fxbv + * - AGENTRUN_SECURITY_GROUP_ID=sg-bp1gs1qqbxo87qeerorz + */ + +import { + CodeInterpreterSandbox, + Template, + TemplateType, + TemplateNetworkMode, + CodeLanguage, + SandboxState, + type TemplateCreateInput, + type PolarFsConfig, +} from '../../../src/sandbox'; +import { ResourceNotExistError } from '../../../src/utils/exception'; +import { logger } from '../../../src/utils/log'; + +/** + * 生成唯一名称 + */ +function generateUniqueName(prefix: string): string { + const timestamp = Date.now(); + const random = Math.random().toString(36).substring(2, 8); + return `${prefix}-${timestamp}-${random}`; +} + +describe('Sandbox Create with PolarFS Configuration', () => { + let templateName: string; + let sandbox: CodeInterpreterSandbox | undefined; + let template: Template | undefined; + + // 从环境变量获取 PolarFS 实例 ID + const polarfsInstanceId = process.env.AGENTRUN_POLARFS_INSTANCE_ID; + + beforeAll(async () => { + templateName = generateUniqueName('test-polarfs-template'); + }); + + afterAll(async () => { + // 清理 Sandbox + if (sandbox?.sandboxId) { + try { + await sandbox.delete(); + logger.info('Sandbox 已清理 / Sandbox cleaned up'); + } catch (error) { + if (!(error instanceof ResourceNotExistError)) { + logger.error('清理 Sandbox 失败 / Failed to cleanup sandbox:', error); + } + } + } + + // 清理 Template + try { + await Template.delete({ name: templateName }); + logger.info('Template 已清理 / Template cleaned up'); + } catch (error) { + if (!(error instanceof ResourceNotExistError)) { + logger.error('清理 Template 失败 / Failed to cleanup template:', error); + } + } + }); + + // ========== 模板创建测试 ========== + + describe('Template Creation for PolarFS Sandbox', () => { + it('should create a Code Interpreter template', async () => { + // 从环境变量获取 VPC 配置 + const vpcId = process.env.AGENTRUN_VPC_ID; + const vswitchIds = process.env.AGENTRUN_VSWITCH_IDS?.split(','); + const securityGroupId = process.env.AGENTRUN_SECURITY_GROUP_ID; + // 构建网络配置 + const networkConfiguration = polarfsInstanceId + ? { + networkMode: TemplateNetworkMode.PUBLIC_AND_PRIVATE as TemplateNetworkMode, + vpcId, + vswitchIds, + securityGroupId, + } + : { + networkMode: TemplateNetworkMode.PUBLIC as TemplateNetworkMode, + }; + logger.info("securityGroupId", securityGroupId) + + const templateInput: TemplateCreateInput = { + templateName, + templateType: TemplateType.CODE_INTERPRETER, + description: 'Sandbox Create with PolarFS - Test Template', + cpu: 2.0, + memory: 4096, + diskSize: 512, + sandboxIdleTimeoutInSeconds: 600, + networkConfiguration, + }; + console.log(`------------- template create sanbdox input -----------`) + console.log(JSON.stringify(templateInput)) + + template = await Template.create({ input: templateInput }); + + expect(template).toBeDefined(); + expect(template.templateName).toBe(templateName); + expect(template.templateType).toBe(TemplateType.CODE_INTERPRETER); + expect(template.status).toBeDefined(); + }); + + it('should wait for template to be ready', async () => { + expect(template).toBeDefined(); + + await template!.waitUntilReadyOrFailed({ + timeoutSeconds: 180, + intervalSeconds: 5, + callback: t => { + logger.info(`等待模板就绪 / Waiting for template: ${t.status}`); + logger.info(`模板创建状态 ${JSON.stringify(t)}`) + }, + }); + + expect(template!.status).toBe('READY'); + }); + }); + + // ========== Sandbox 创建测试(使用 PolarFS 配置) ========== + + describe('Sandbox Creation with PolarFS Config', () => { + it('should create sandbox with polarFsConfig (skip without instance ID)', async () => { + expect(template).toBeDefined(); + + // 如果没有 PolarFS 实例 ID,跳过此测试 + if (!polarfsInstanceId) { + logger.warn('跳过测试:需要设置 AGENTRUN_POLARFS_INSTANCE_ID 环境变量'); + return; + } + + // 检查 VPC 配置是否存在 + const vpcId = process.env.AGENTRUN_VPC_ID; + if (!vpcId) { + logger.error('使用 PolarFS 时必须配置 VPC。请设置以下环境变量:AGENTRUN_VPC_ID, AGENTRUN_VSWITCH_IDS, AGENTRUN_SECURITY_GROUP_ID'); + throw new Error('缺少 VPC 配置:使用 PolarFS 时,Template 必须配置 VPC 网络模式。请设置 AGENTRUN_VPC_ID 等环境变量。'); + } + + // 构建 PolarFS 配置 + const polarFsConfig: PolarFsConfig = { + userId: 1000, + groupId: 1000, + mountPoints: [ + { + instanceId: polarfsInstanceId, + mountDir: '/mnt/polarfs', + remoteDir: '/qianfeng-test-uid', + }, + ], + }; + + logger.info(`使用 PolarFS 实例 ID: ${polarfsInstanceId}`); + + // 使用 polarFsConfig 创建 Sandbox + sandbox = await CodeInterpreterSandbox.createFromTemplate(templateName, { + sandboxIdleTimeoutSeconds: 600, + polarFsConfig, + }); + + expect(sandbox).toBeDefined(); + expect(sandbox.sandboxId).toBeDefined(); + expect(sandbox.templateName).toBe(templateName); + expect(sandbox.state).toBeDefined(); + }); + + it('should wait for sandbox to be running (skip if no sandbox)', async () => { + if (!polarfsInstanceId || !sandbox) { + logger.warn('跳过测试:sandbox 未创建'); + return; + } + expect(sandbox).toBeDefined(); + + await sandbox!.waitUntilRunning({ + timeoutSeconds: 120, + intervalSeconds: 5, + beforeCheck: s => { + logger.info(`等待沙箱运行 / Waiting for sandbox: ${s.state}`); + }, + }); + + expect([SandboxState.RUNNING, SandboxState.READY]).toContain(sandbox!.state!); + }); + + it('should pass health check (skip if no sandbox)', async () => { + if (!polarfsInstanceId || !sandbox) { + logger.warn('跳过测试:sandbox 未创建'); + return; + } + expect(sandbox).toBeDefined(); + + await sandbox!.waitUntilReadyOrFailed({ + timeoutSeconds: 60, + intervalSeconds: 3, + }); + + // 通过健康检查意味着 sandbox 可以正常运行 + expect(sandbox!.sandboxId).toBeDefined(); + }); + }); + + // ========== 代码执行测试(验证 PolarFS 配置生效) ========== + + describe('Code Execution in PolarFS Sandbox', () => { + it('should execute simple Python code (skip if no sandbox)', async () => { + if (!polarfsInstanceId || !sandbox) { + logger.warn('跳过测试:sandbox 未创建'); + return; + } + expect(sandbox).toBeDefined(); + + const ctx = await sandbox!.context.create({ language: CodeLanguage.PYTHON }); + const result = await ctx.execute({ + code: "print('Hello from PolarFS sandbox!')", + }); + + expect(result).toBeDefined(); + expect(result.exitCode).toBe(0); + + await ctx.delete(); + }); + + it('should check PolarFS mount point exists (skip if no sandbox)', async () => { + if (!polarfsInstanceId || !sandbox) { + logger.warn('跳过测试:sandbox 未创建'); + return; + } + expect(sandbox).toBeDefined(); + + const ctx = await sandbox!.context.create({ language: CodeLanguage.PYTHON }); + + // 检查 PolarFS 挂载点是否存在 + const result = await ctx.execute({ + code: ` +import os +mount_point = '/mnt/polarfs' +exists = os.path.exists(mount_point) +print(f"PolarFS mount point exists: {exists}") +if exists: + print(f"Contents: {os.listdir(mount_point)}") +`, + }); + + expect(result).toBeDefined(); + logger.info(`执行结果:${result.stdout}`); + + await ctx.delete(); + }); + + it('should list sandbox file system (skip if no sandbox)', async () => { + if (!polarfsInstanceId || !sandbox) { + logger.warn('跳过测试:sandbox 未创建'); + return; + } + expect(sandbox).toBeDefined(); + + const files = await sandbox!.fileSystem.list({ path: '/home/user' }); + + expect(files).toBeDefined(); + expect(Array.isArray(files)).toBe(true); + }); + }); + + // ========== Sandbox 状态测试 ========== + + describe('Sandbox State Verification', () => { + it('should refresh sandbox state (skip if no sandbox)', async () => { + if (!polarfsInstanceId || !sandbox) { + logger.warn('跳过测试:sandbox 未创建'); + return; + } + expect(sandbox).toBeDefined(); + + await sandbox!.refresh(); + + expect(sandbox!.state).toBeDefined(); + expect([SandboxState.RUNNING, SandboxState.READY]).toContain(sandbox!.state!); + }); + + it('should get sandbox details (skip if no sandbox)', async () => { + if (!polarfsInstanceId || !sandbox) { + logger.warn('跳过测试:sandbox 未创建'); + return; + } + expect(sandbox).toBeDefined(); + + const details = await sandbox!.get(); + + expect(details).toBeDefined(); + expect(details.sandboxId).toBe(sandbox!.sandboxId); + expect(details.templateName).toBe(templateName); + }); + }); +}); + +// ========== 独立测试:使用已存在的 Template 创建带 PolarFS 配置的 Sandbox ========== + +describe('Sandbox Create with PolarFS - Existing Template', () => { + let existingTemplateName: string; + let template: Template | undefined; + let sandbox: CodeInterpreterSandbox | undefined; + + const polarfsInstanceId = process.env.AGENTRUN_POLARFS_INSTANCE_ID; + + beforeAll(async () => { + existingTemplateName = generateUniqueName('existing-polarfs-template'); + + // 从环境变量获取 VPC 配置 + const vpcId = process.env.AGENTRUN_VPC_ID; + const vswitchIds = process.env.AGENTRUN_VSWITCH_IDS?.split(','); + const securityGroupId = process.env.AGENTRUN_SECURITY_GROUP_ID; + + // 构建网络配置 + const networkConfiguration = polarfsInstanceId + ? { + networkMode: TemplateNetworkMode.PUBLIC_AND_PRIVATE as TemplateNetworkMode, + vpcId, + vswitchIds, + securityGroupId, + } + : { + networkMode: TemplateNetworkMode.PUBLIC as TemplateNetworkMode, + }; + + // 创建一个模板供后续使用 + template = await Template.create({ + input: { + templateName: existingTemplateName, + templateType: TemplateType.CODE_INTERPRETER, + description: 'Test template for PolarFS sandbox test', + cpu: 2.0, + memory: 4096, + diskSize: 512, + sandboxIdleTimeoutInSeconds: 300, + networkConfiguration, + }, + }); + + await template.waitUntilReadyOrFailed({ + timeoutSeconds: 180, + intervalSeconds: 5, + }); + }); + + afterAll(async () => { + // 清理 Sandbox + if (sandbox?.sandboxId) { + try { + await sandbox.delete(); + } catch { + // Ignore + } + } + + // 清理 Template + try { + await Template.delete({ name: existingTemplateName }); + } catch { + // Ignore + } + }); + + it('should create sandbox with polarFsConfig from existing template (skip without instance ID)', async () => { + expect(template).toBeDefined(); + expect(template!.status).toBe('READY'); + + // 如果没有 PolarFS 实例 ID,跳过此测试 + if (!polarfsInstanceId) { + logger.warn('跳过测试:需要设置 AGENTRUN_POLARFS_INSTANCE_ID 环境变量'); + return; + } + + // 构建 PolarFS 配置 + const polarFsConfig: PolarFsConfig = { + userId: 1000, + groupId: 1000, + mountPoints: [ + { + instanceId: polarfsInstanceId, + mountDir: '/mnt/polarfs', + remoteDir: '/', + }, + ], + }; + + sandbox = await CodeInterpreterSandbox.createFromTemplate(existingTemplateName, { + sandboxIdleTimeoutSeconds: 300, + polarFsConfig, + }); + + expect(sandbox).toBeDefined(); + expect(sandbox.sandboxId).toBeDefined(); + expect(sandbox.templateName).toBe(existingTemplateName); + }); + + it('should execute code in PolarFS sandbox (skip if no sandbox)', async () => { + if (!polarfsInstanceId || !sandbox) { + logger.warn('跳过测试:sandbox 未创建'); + return; + } + expect(sandbox).toBeDefined(); + + await sandbox!.waitUntilRunning({ + timeoutSeconds: 60, + intervalSeconds: 3, + }); + + const ctx = await sandbox!.context.create({ language: CodeLanguage.PYTHON }); + const result = await ctx.execute({ + code: "print('Test from PolarFS sandbox')", + }); + + expect(result).toBeDefined(); + expect(result.exitCode).toBe(0); + + await ctx.delete(); + }); +});