/** * 资源限制测试 * * 覆盖: * - 内存限制(RSS 轮询监控) * - CPU 密集型超时(JS / Python) * - 运行时长限制(wall-clock timeout 验证) * - 网络请求限制(次数、请求体大小、响应大小) */ import { describe, it, expect, afterEach, beforeAll } from 'vitest'; import { ProcessPool } from '../../src/pool/process-pool'; import { PythonProcessPool } from '../../src/pool/python-process-pool'; import { config } from '../../src/config'; beforeAll(async () => { console.log(`\n=== Memory Limit Test Status ===`); console.log(`Method: RSS polling (cross-platform)`); console.log(`User configured memory: ${config.maxMemoryMB}MB`); console.log( `Actual process limit: ${config.maxMemoryMB + config.RUNTIME_MEMORY_OVERHEAD_MB}MB (${config.maxMemoryMB}MB user + ${config.RUNTIME_MEMORY_OVERHEAD_MB}MB runtime)` ); console.log(`=============================\n`); }); // ============================================================ // 1. 内存限制(RSS 轮询监控,跨平台) // ============================================================ describe('内存限制', () => { let pool: ProcessPool; afterEach(async () => { try { await pool?.shutdown(); } catch {} }); it('JS 分配超大内存被 RSS 监控终止后自动 respawn', async () => { pool = new ProcessPool(1); await pool.init(); expect(pool.stats.total).toBe(1); // 实际限制 = 用户配置 + 运行时开销(50MB) const actualLimitMB = config.maxMemoryMB + config.RUNTIME_MEMORY_OVERHEAD_MB; const result = await pool.execute({ code: `async function main() { const arr = []; // 逐步分配内存(使用随机字符串填充,防止 OS 内存压缩优化) // 每次 10MB,每轮等待 200ms 让 RSS 监控有机会检测 for (let i = 0; i < 40; i++) { arr.push(Buffer.alloc(10 * 1024 * 1024, String(Date.now() + i))); await new Promise(r => setTimeout(r, 200)); } return { allocated: arr.length }; }`, variables: {} }); expect(result.success).toBe(false); expect(result.message).toMatch(/memory|Memory|crash|Worker|timed out/i); // 等 respawn await new Promise((r) => setTimeout(r, 2000)); // 新 worker 应该可用 const result2 = await pool.execute({ code: `async function main() { return { recovered: true }; }`, variables: {} }); expect(result2.success).toBe(true); expect(result2.data?.codeReturn.recovered).toBe(true); }, 30000); it('JS 分配配置范围内的内存正常工作', async () => { pool = new ProcessPool(1); await pool.init(); expect(pool.stats.total).toBe(1); // 分配少量内存(远小于限制),确保不会被误杀 const allocMB = 10; const result = await pool.execute({ code: `async function main() { const arr = []; for (let i = 0; i < ${allocMB}; i++) { arr.push(Buffer.alloc(1024 * 1024)); } return { allocated: arr.length, totalMB: arr.length }; }`, variables: {} }); expect(result.success).toBe(true); expect(result.data?.codeReturn.allocated).toBe(allocMB); }, 30000); }); describe('Python 内存限制', () => { let pool: PythonProcessPool; afterEach(async () => { try { await pool?.shutdown(); } catch {} }); it('Python 分配超大内存被 RSS 监控终止后自动 respawn', async () => { pool = new PythonProcessPool(1); await pool.init(); expect(pool.stats.total).toBe(1); const result = await pool.execute({ code: `import time\nimport random\ndef main():\n chunks = []\n for i in range(40):\n chunk = bytearray(10 * 1024 * 1024)\n chunk[0] = i % 256\n chunks.append(chunk)\n time.sleep(0.2)\n return {'size': len(chunks)}`, variables: {} }); expect(result.success).toBe(false); expect(result.message).toMatch(/memory|Memory|crash|Worker|timed out/i); // 等 respawn await new Promise((r) => setTimeout(r, 2000)); const result2 = await pool.execute({ code: `def main():\n return {'recovered': True}`, variables: {} }); expect(result2.success).toBe(true); expect(result2.data?.codeReturn.recovered).toBe(true); }, 30000); it('Python 分配配置范围内的内存正常工作', async () => { pool = new PythonProcessPool(1); await pool.init(); expect(pool.stats.total).toBe(1); const allocMB = 10; const result = await pool.execute({ code: `def main():\n data = bytearray(${allocMB} * 1024 * 1024)\n return {'allocated': len(data), 'totalMB': len(data) // (1024 * 1024)}`, variables: {} }); expect(result.success).toBe(true); expect(result.data?.codeReturn.totalMB).toBe(allocMB); }, 30000); }); // ============================================================ // 2. CPU 限制 // ============================================================ describe('JS CPU 密集型超时', () => { let pool: ProcessPool; afterEach(async () => { try { await pool?.shutdown(); } catch {} }); it('纯计算死循环被超时终止', async () => { pool = new ProcessPool(1); await pool.init(); const start = Date.now(); const result = await pool.execute({ code: `async function main() { while(true) { Math.random(); } }`, variables: {} }); const elapsed = Date.now() - start; expect(result.success).toBe(false); expect(result.message).toMatch(/timed out|timeout/i); // 应该在合理时间内被终止(超时 + 一些余量) expect(elapsed).toBeLessThan(30000); }); it('CPU 密集型计算(大量数学运算)被超时终止', async () => { pool = new ProcessPool(1); await pool.init(); const result = await pool.execute({ code: `async function main() { let x = 0; while(true) { x += Math.sin(x) * Math.cos(x); } }`, variables: {} }); expect(result.success).toBe(false); expect(result.message).toMatch(/timed out|timeout/i); }); it('CPU 超时后 worker 恢复正常', async () => { pool = new ProcessPool(1); await pool.init(); await pool.execute({ code: `async function main() { while(true) {} }`, variables: {} }); await new Promise((r) => setTimeout(r, 1500)); const r2 = await pool.execute({ code: `async function main() { return { ok: true }; }`, variables: {} }); expect(r2.success).toBe(true); }); }); describe('Python CPU 密集型超时', () => { let pool: PythonProcessPool; afterEach(async () => { try { await pool?.shutdown(); } catch {} }); it('纯计算死循环被超时终止', async () => { pool = new PythonProcessPool(1); await pool.init(); const start = Date.now(); const result = await pool.execute({ code: `import math\ndef main():\n x = 0\n while True:\n x += math.sin(x) * math.cos(x)`, variables: {} }); const elapsed = Date.now() - start; expect(result.success).toBe(false); expect(result.message).toMatch(/timed out|timeout/i); expect(elapsed).toBeLessThan(30000); }); it('CPU 超时后 worker 恢复正常', async () => { pool = new PythonProcessPool(1); await pool.init(); await pool.execute({ code: `def main():\n while True:\n pass`, variables: {} }); await new Promise((r) => setTimeout(r, 2000)); const r2 = await pool.execute({ code: `def main():\n return {'ok': True}`, variables: {} }); expect(r2.success).toBe(true); }); }); // ============================================================ // 3. 运行时长限制(wall-clock timeout) // ============================================================ describe('JS 运行时长限制', () => { let pool: ProcessPool; afterEach(async () => { try { await pool?.shutdown(); } catch {} }); it('sleep 超过 maxTimeoutMs 被终止', async () => { pool = new ProcessPool(1); await pool.init(); const start = Date.now(); const result = await pool.execute({ code: `async function main() { await new Promise(r => setTimeout(r, ${config.maxTimeoutMs + 30000})); return { done: true }; }`, variables: {} }); const elapsed = Date.now() - start; expect(result.success).toBe(false); expect(result.message).toMatch(/timed out|timeout/i); // 实际耗时应在 maxTimeoutMs 附近(加上 2s 余量),不会等到 sleep 结束 expect(elapsed).toBeLessThan(config.maxTimeoutMs + 10000); }); it('在超时范围内完成的代码正常返回', async () => { pool = new ProcessPool(1); await pool.init(); const result = await pool.execute({ code: `async function main() { await new Promise(r => setTimeout(r, 100)); return { elapsed: true }; }`, variables: {} }); expect(result.success).toBe(true); expect(result.data?.codeReturn.elapsed).toBe(true); }); it('delay() 超过 10s 上限被拒绝', async () => { pool = new ProcessPool(1); await pool.init(); const result = await pool.execute({ code: `async function main() { await delay(15000); return { done: true }; }`, variables: {} }); expect(result.success).toBe(false); expect(result.message).toContain('10000'); }); }); describe('Python 运行时长限制', () => { let pool: PythonProcessPool; afterEach(async () => { try { await pool?.shutdown(); } catch {} }); it('sleep 超过超时限制被终止', async () => { pool = new PythonProcessPool(1); await pool.init(); const start = Date.now(); const result = await pool.execute({ code: `import time\ndef main():\n time.sleep(${Math.ceil(config.maxTimeoutMs / 1000) + 30})\n return {'done': True}`, variables: {} }); const elapsed = Date.now() - start; expect(result.success).toBe(false); expect(result.message).toMatch(/timed out|timeout/i); expect(elapsed).toBeLessThan(config.maxTimeoutMs + 10000); }); it('在超时范围内完成的代码正常返回', async () => { pool = new PythonProcessPool(1); await pool.init(); const result = await pool.execute({ code: `import time\ndef main():\n time.sleep(0.1)\n return {'elapsed': True}`, variables: {} }); expect(result.success).toBe(true); expect(result.data?.codeReturn.elapsed).toBe(true); }); it('delay() 超过 10s 上限被拒绝', async () => { pool = new PythonProcessPool(1); await pool.init(); const result = await pool.execute({ code: `def main():\n delay(15000)\n return {'done': True}`, variables: {} }); expect(result.success).toBe(false); }); }); // ============================================================ // 4. 网络请求次数限制 // ============================================================ describe('JS 网络请求次数限制', () => { let pool: ProcessPool; afterEach(async () => { try { await pool?.shutdown(); } catch {} }); it(`第 maxRequests+1 次请求被拒绝(计数器验证)`, async () => { pool = new ProcessPool(1); await pool.init(); // 快速消耗计数器:每次 httpRequest 调用会先 ++requestCount 再发起网络请求 // 即使网络请求失败(DNS/连接),计数器也已递增 // 为避免超时,用循环快速调用并 catch 所有错误,只关注 limit 错误 const result = await pool.execute({ code: `async function main() { let limitError = null; for (let i = 0; i < ${config.maxRequests + 1}; i++) { try { await httpRequest('http://0.0.0.0:1'); } catch(e) { if (e.message.includes('limit') || e.message.includes('Limit')) { limitError = { idx: i, msg: e.message }; break; } } } return { limitError }; }`, variables: {} }); expect(result.success).toBe(true); const le = result.data?.codeReturn.limitError; expect(le).not.toBeNull(); expect(le.idx).toBe(config.maxRequests); expect(le.msg).toMatch(/limit/i); }); it('请求计数每次执行重置', async () => { pool = new ProcessPool(1); await pool.init(); // 第一次执行:消耗一些计数 await pool.execute({ code: `async function main() { for (let i = 0; i < 3; i++) { try { await httpRequest('http://0.0.0.0:1'); } catch(e) {} } return {}; }`, variables: {} }); // 第二次执行:计数应该重置,第一次请求不会触发 limit const r2 = await pool.execute({ code: `async function main() { let limitHit = false; try { await httpRequest('http://0.0.0.0:1'); } catch(e) { if (e.message.includes('limit') || e.message.includes('Limit')) limitHit = true; } return { limitHit }; }`, variables: {} }); expect(r2.success).toBe(true); expect(r2.data?.codeReturn.limitHit).toBe(false); }); }); describe('Python 网络请求次数限制', () => { let pool: PythonProcessPool; afterEach(async () => { try { await pool?.shutdown(); } catch {} }); it(`第 maxRequests+1 次请求被拒绝(计数器验证)`, async () => { pool = new PythonProcessPool(1); await pool.init(); const result = await pool.execute({ code: `def main():\n limit_error = None\n for i in range(${config.maxRequests + 1}):\n try:\n http_request('http://0.0.0.0:1')\n except Exception as e:\n if 'limit' in str(e).lower():\n limit_error = {'idx': i, 'msg': str(e)}\n break\n return {'limit_error': limit_error}`, variables: {} }); expect(result.success).toBe(true); const le = result.data?.codeReturn.limit_error; expect(le).not.toBeNull(); expect(le.idx).toBe(config.maxRequests); expect(le.msg.toLowerCase()).toContain('limit'); }); it('请求计数每次执行重置', async () => { pool = new PythonProcessPool(1); await pool.init(); await pool.execute({ code: `def main():\n for i in range(3):\n try:\n http_request('http://0.0.0.0:1')\n except:\n pass\n return {}`, variables: {} }); const r2 = await pool.execute({ code: `def main():\n limit_hit = False\n try:\n http_request('http://0.0.0.0:1')\n except Exception as e:\n if 'limit' in str(e).lower():\n limit_hit = True\n return {'limit_hit': limit_hit}`, variables: {} }); expect(r2.success).toBe(true); expect(r2.data?.codeReturn.limit_hit).toBe(false); }); }); // ============================================================ // 5. 网络请求大小限制 // ============================================================ describe('JS 请求体大小限制', () => { let pool: ProcessPool; afterEach(async () => { try { await pool?.shutdown(); } catch {} }); it('请求体超过 maxRequestBodySize 被拒绝', async () => { pool = new ProcessPool(1); await pool.init(); // maxRequestBodySize 单位是 MB,生成超过限制的 body const sizeMB = config.maxRequestBodySize; const result = await pool.execute({ code: `async function main() { const bigBody = 'x'.repeat(${sizeMB} * 1024 * 1024 + 1); try { await httpRequest('https://example.com', { method: 'POST', body: bigBody }); return { blocked: false }; } catch(e) { return { blocked: true, msg: e.message }; } }`, variables: {} }); expect(result.success).toBe(true); expect(result.data?.codeReturn.blocked).toBe(true); expect(result.data?.codeReturn.msg).toMatch(/body.*large|too large/i); }); it('请求体在限制内正常发送(不因大小被拒)', async () => { pool = new ProcessPool(1); await pool.init(); const result = await pool.execute({ code: `async function main() { const smallBody = JSON.stringify({ data: 'hello' }); try { await httpRequest('https://example.com', { method: 'POST', body: smallBody }); return { sizeOk: true }; } catch(e) { // 网络错误可以接受,但不应该是 body too large return { sizeOk: !e.message.includes('too large'), msg: e.message }; } }`, variables: {} }); expect(result.success).toBe(true); expect(result.data?.codeReturn.sizeOk).toBe(true); }); }); describe('Python 请求体大小限制', () => { let pool: PythonProcessPool; afterEach(async () => { try { await pool?.shutdown(); } catch {} }); it('请求体超过 maxRequestBodySize 被拒绝', async () => { pool = new PythonProcessPool(1); await pool.init(); const sizeMB = config.maxRequestBodySize; const result = await pool.execute({ code: `def main():\n big_body = 'x' * (${sizeMB} * 1024 * 1024 + 1)\n try:\n http_request('https://example.com', method='POST', body=big_body)\n return {'blocked': False}\n except Exception as e:\n return {'blocked': True, 'msg': str(e)}`, variables: {} }); expect(result.success).toBe(true); expect(result.data?.codeReturn.blocked).toBe(true); expect(result.data?.codeReturn.msg).toMatch(/body.*large|too large/i); }); it('请求体在限制内正常发送(不因大小被拒)', async () => { pool = new PythonProcessPool(1); await pool.init(); const result = await pool.execute({ code: `def main():\n try:\n http_request('https://example.com', method='POST', body='hello')\n return {'size_ok': True}\n except Exception as e:\n return {'size_ok': 'too large' not in str(e).lower(), 'msg': str(e)}`, variables: {} }); expect(result.success).toBe(true); expect(result.data?.codeReturn.size_ok).toBe(true); }); }); // ============================================================ // 6. 网络协议限制 // ============================================================ describe('JS 网络协议限制', () => { let pool: ProcessPool; afterEach(async () => { try { await pool?.shutdown(); } catch {} }); it('ftp:// 协议被拒绝', async () => { pool = new ProcessPool(1); await pool.init(); const result = await pool.execute({ code: `async function main() { try { await httpRequest('ftp://example.com/file'); return { blocked: false }; } catch(e) { return { blocked: true, msg: e.message }; } }`, variables: {} }); expect(result.success).toBe(true); expect(result.data?.codeReturn.blocked).toBe(true); expect(result.data?.codeReturn.msg).toMatch(/protocol/i); }); it('file:// 协议被拒绝', async () => { pool = new ProcessPool(1); await pool.init(); const result = await pool.execute({ code: `async function main() { try { await httpRequest('file:///etc/passwd'); return { blocked: false }; } catch(e) { return { blocked: true, msg: e.message }; } }`, variables: {} }); expect(result.success).toBe(true); expect(result.data?.codeReturn.blocked).toBe(true); }); }); describe('Python 网络协议限制', () => { let pool: PythonProcessPool; afterEach(async () => { try { await pool?.shutdown(); } catch {} }); it('ftp:// 协议被拒绝', async () => { pool = new PythonProcessPool(1); await pool.init(); const result = await pool.execute({ code: `def main():\n try:\n http_request('ftp://example.com/file')\n return {'blocked': False}\n except Exception as e:\n return {'blocked': True, 'msg': str(e)}`, variables: {} }); expect(result.success).toBe(true); expect(result.data?.codeReturn.blocked).toBe(true); expect(result.data?.codeReturn.msg.toLowerCase()).toContain('protocol'); }); it('file:// 协议被拒绝', async () => { pool = new PythonProcessPool(1); await pool.init(); const result = await pool.execute({ code: `def main():\n try:\n http_request('file:///etc/passwd')\n return {'blocked': False}\n except Exception as e:\n return {'blocked': True}`, variables: {} }); expect(result.success).toBe(true); expect(result.data?.codeReturn.blocked).toBe(true); }); });