mirror of
https://github.com/labring/FastGPT.git
synced 2026-04-28 02:00:47 +08:00
cc3a91d009
* Opensandbox (#6651) * volumn manager * feat: opensandbox volumn * perf: action (#6654) * perf: action * doc * doc * deploy tml * update template
760 lines
27 KiB
TypeScript
760 lines
27 KiB
TypeScript
/**
|
|
* 集成测试套件 - 黑盒功能测试
|
|
*
|
|
* 测试矩阵:输入代码 + 变量 → 预期输出
|
|
* 覆盖真实使用场景,不关心内部实现
|
|
*/
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import { ProcessPool } from '../../src/pool/process-pool';
|
|
import { PythonProcessPool } from '../../src/pool/python-process-pool';
|
|
|
|
// ============================================================
|
|
// 测试用例矩阵类型
|
|
// ============================================================
|
|
interface TestCase {
|
|
name: string;
|
|
code: string;
|
|
variables?: Record<string, any>;
|
|
expect: {
|
|
success: boolean;
|
|
codeReturn?: any; // 精确匹配
|
|
codeReturnMatch?: Record<string, any>; // 部分匹配
|
|
errorMatch?: RegExp; // 错误信息匹配
|
|
hasLog?: boolean; // 是否有日志输出
|
|
logMatch?: RegExp; // 日志内容匹配
|
|
};
|
|
}
|
|
|
|
function runMatrix(getPool: () => ProcessPool | PythonProcessPool, cases: TestCase[]) {
|
|
for (const tc of cases) {
|
|
it(tc.name, async () => {
|
|
const result = await getPool().execute({
|
|
code: tc.code,
|
|
variables: tc.variables || {}
|
|
});
|
|
|
|
expect(result.success).toBe(tc.expect.success);
|
|
|
|
if (tc.expect.codeReturn !== undefined) {
|
|
expect(result.data?.codeReturn).toEqual(tc.expect.codeReturn);
|
|
}
|
|
if (tc.expect.codeReturnMatch) {
|
|
for (const [key, val] of Object.entries(tc.expect.codeReturnMatch)) {
|
|
expect(result.data?.codeReturn?.[key]).toEqual(val);
|
|
}
|
|
}
|
|
if (tc.expect.errorMatch) {
|
|
expect(result.message).toMatch(tc.expect.errorMatch);
|
|
}
|
|
if (tc.expect.hasLog) {
|
|
expect(result.data?.log).toBeTruthy();
|
|
expect(result.data!.log!.length).toBeGreaterThan(0);
|
|
}
|
|
if (tc.expect.logMatch) {
|
|
expect(result.data?.log).toMatch(tc.expect.logMatch);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// JS 功能测试矩阵
|
|
// ============================================================
|
|
describe('JS 功能测试', () => {
|
|
let pool: ProcessPool;
|
|
beforeAll(async () => {
|
|
pool = new ProcessPool(1);
|
|
await pool.init();
|
|
});
|
|
afterAll(async () => {
|
|
await pool.shutdown();
|
|
});
|
|
|
|
// --- 基础运算 ---
|
|
describe('基础运算', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: '返回简单对象',
|
|
code: `async function main() { return { hello: 'world' }; }`,
|
|
expect: { success: true, codeReturn: { hello: 'world' } }
|
|
},
|
|
{
|
|
name: '数学运算',
|
|
code: `async function main() { return { sum: 1 + 2, product: 3 * 4, division: 10 / 3 }; }`,
|
|
expect: { success: true, codeReturnMatch: { sum: 3, product: 12 } }
|
|
},
|
|
{
|
|
name: '字符串操作',
|
|
code: `async function main() {
|
|
const s = 'Hello, World!';
|
|
return { upper: s.toUpperCase(), len: s.length, includes: s.includes('World') };
|
|
}`,
|
|
expect: { success: true, codeReturn: { upper: 'HELLO, WORLD!', len: 13, includes: true } }
|
|
},
|
|
{
|
|
name: '数组操作',
|
|
code: `async function main() {
|
|
const arr = [3, 1, 4, 1, 5, 9];
|
|
return { sorted: [...arr].sort((a,b) => a-b), sum: arr.reduce((a,b) => a+b, 0), len: arr.length };
|
|
}`,
|
|
expect: { success: true, codeReturn: { sorted: [1, 1, 3, 4, 5, 9], sum: 23, len: 6 } }
|
|
},
|
|
{
|
|
name: 'JSON 解析与序列化',
|
|
code: `async function main() {
|
|
const obj = { a: 1, b: [2, 3] };
|
|
const json = JSON.stringify(obj);
|
|
const parsed = JSON.parse(json);
|
|
return { json, equal: JSON.stringify(parsed) === json };
|
|
}`,
|
|
expect: { success: true, codeReturn: { json: '{"a":1,"b":[2,3]}', equal: true } }
|
|
},
|
|
{
|
|
name: '正则表达式',
|
|
code: `async function main() {
|
|
const text = 'Email: test@example.com, Phone: 123-456-7890';
|
|
const email = text.match(/[\\w.]+@[\\w.]+/)?.[0];
|
|
const phone = text.match(/\\d{3}-\\d{3}-\\d{4}/)?.[0];
|
|
return { email, phone };
|
|
}`,
|
|
expect: {
|
|
success: true,
|
|
codeReturn: { email: 'test@example.com', phone: '123-456-7890' }
|
|
}
|
|
},
|
|
{
|
|
name: 'Date 操作',
|
|
code: `async function main() {
|
|
const d = new Date('2024-01-15T12:00:00Z');
|
|
return { year: d.getUTCFullYear(), month: d.getUTCMonth() + 1, day: d.getUTCDate() };
|
|
}`,
|
|
expect: { success: true, codeReturn: { year: 2024, month: 1, day: 15 } }
|
|
},
|
|
{
|
|
name: 'Promise.all 并发',
|
|
code: `async function main() {
|
|
const results = await Promise.all([
|
|
Promise.resolve(1),
|
|
Promise.resolve(2),
|
|
Promise.resolve(3)
|
|
]);
|
|
return { results, sum: results.reduce((a,b) => a+b, 0) };
|
|
}`,
|
|
expect: { success: true, codeReturn: { results: [1, 2, 3], sum: 6 } }
|
|
},
|
|
{
|
|
name: 'Map 和 Set',
|
|
code: `async function main() {
|
|
const m = new Map([['a', 1], ['b', 2]]);
|
|
const s = new Set([1, 2, 2, 3, 3]);
|
|
return { mapSize: m.size, mapGet: m.get('b'), setSize: s.size };
|
|
}`,
|
|
expect: { success: true, codeReturn: { mapSize: 2, mapGet: 2, setSize: 3 } }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
// --- 变量传递 ---
|
|
describe('变量传递', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: '接收字符串变量',
|
|
code: `async function main(v) { return { greeting: 'Hello, ' + v.name + '!' }; }`,
|
|
variables: { name: 'Alice' },
|
|
expect: { success: true, codeReturn: { greeting: 'Hello, Alice!' } }
|
|
},
|
|
{
|
|
name: '接收数字变量',
|
|
code: `async function main(v) { return { doubled: v.num * 2 }; }`,
|
|
variables: { num: 21 },
|
|
expect: { success: true, codeReturn: { doubled: 42 } }
|
|
},
|
|
{
|
|
name: '接收复杂对象变量',
|
|
code: `async function main(v) {
|
|
const items = JSON.parse(v.items);
|
|
return { count: items.length, first: items[0] };
|
|
}`,
|
|
variables: { items: '[{"id":1,"name":"foo"},{"id":2,"name":"bar"}]' },
|
|
expect: { success: true, codeReturn: { count: 2, first: { id: 1, name: 'foo' } } }
|
|
},
|
|
{
|
|
name: '空变量对象',
|
|
code: `async function main(v) { return { keys: Object.keys(v || {}).length }; }`,
|
|
variables: {},
|
|
expect: { success: true, codeReturn: { keys: 0 } }
|
|
},
|
|
{
|
|
name: '多个变量',
|
|
code: `async function main(v) { return { result: v.a + ' ' + v.b + ' ' + v.c }; }`,
|
|
variables: { a: 'hello', b: 'beautiful', c: 'world' },
|
|
expect: { success: true, codeReturn: { result: 'hello beautiful world' } }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
// --- console 日志 ---
|
|
describe('日志输出', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: 'console.log 被捕获',
|
|
code: `async function main() { console.log('debug info'); return { done: true }; }`,
|
|
expect: { success: true, codeReturn: { done: true }, logMatch: /debug info/ }
|
|
},
|
|
{
|
|
name: 'console.error 不报错,内容被捕获',
|
|
code: `async function main() { console.error('err msg'); return { done: true }; }`,
|
|
expect: { success: true, codeReturn: { done: true }, logMatch: /err msg/ }
|
|
},
|
|
{
|
|
name: 'console.warn 不报错,内容被捕获',
|
|
code: `async function main() { console.warn('warn msg'); return { done: true }; }`,
|
|
expect: { success: true, codeReturn: { done: true }, logMatch: /warn msg/ }
|
|
},
|
|
{
|
|
name: 'console.info 不报错,内容被捕获',
|
|
code: `async function main() { console.info('info msg'); return { done: true }; }`,
|
|
expect: { success: true, codeReturn: { done: true }, logMatch: /info msg/ }
|
|
},
|
|
{
|
|
name: 'console.debug 不报错,内容被捕获',
|
|
code: `async function main() { console.debug('debug msg'); return { done: true }; }`,
|
|
expect: { success: true, codeReturn: { done: true }, logMatch: /debug msg/ }
|
|
},
|
|
{
|
|
name: '多次 console 调用均被捕获',
|
|
code: `async function main() { console.log('a'); console.warn('b'); console.error('c'); return { done: true }; }`,
|
|
expect: { success: true, codeReturn: { done: true }, logMatch: /a/ }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
// --- 白名单模块 ---
|
|
describe('白名单模块', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: 'require crypto-js',
|
|
code: `async function main() {
|
|
const CryptoJS = require('crypto-js');
|
|
const hash = CryptoJS.MD5('hello').toString();
|
|
return { hash };
|
|
}`,
|
|
expect: { success: true, codeReturnMatch: { hash: '5d41402abc4b2a76b9719d911017c592' } }
|
|
},
|
|
{
|
|
name: 'require moment',
|
|
code: `async function main() {
|
|
const moment = require('moment');
|
|
const d = moment('2024-01-15');
|
|
return { formatted: d.format('YYYY/MM/DD') };
|
|
}`,
|
|
expect: { success: true, codeReturn: { formatted: '2024/01/15' } }
|
|
},
|
|
{
|
|
name: 'require lodash',
|
|
code: `async function main() {
|
|
const _ = require('lodash');
|
|
return { chunk: _.chunk([1,2,3,4,5,6], 2) };
|
|
}`,
|
|
expect: {
|
|
success: true,
|
|
codeReturn: {
|
|
chunk: [
|
|
[1, 2],
|
|
[3, 4],
|
|
[5, 6]
|
|
]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'require lodash groupBy',
|
|
code: `async function main({ items }) {
|
|
const _ = require('lodash');
|
|
const grouped = _.groupBy(items, 'type');
|
|
const counts = _.mapValues(grouped, arr => arr.length);
|
|
return counts;
|
|
}`,
|
|
variables: {
|
|
items: [
|
|
{ type: 'a', v: 1 },
|
|
{ type: 'b', v: 2 },
|
|
{ type: 'a', v: 3 },
|
|
{ type: 'c', v: 4 }
|
|
]
|
|
},
|
|
expect: { success: true, codeReturn: { a: 2, b: 1, c: 1 } }
|
|
},
|
|
{
|
|
name: 'require dayjs',
|
|
code: `async function main() {
|
|
const dayjs = require('dayjs');
|
|
const d = dayjs('2024-01-15');
|
|
return { formatted: d.format('YYYY/MM/DD'), month: d.month() + 1 };
|
|
}`,
|
|
expect: { success: true, codeReturn: { formatted: '2024/01/15', month: 1 } }
|
|
},
|
|
{
|
|
name: 'require uuid',
|
|
code: `async function main() {
|
|
const { v4 } = require('uuid');
|
|
const id = v4();
|
|
return { valid: /^[0-9a-f-]{36}$/.test(id) };
|
|
}`,
|
|
expect: { success: true, codeReturn: { valid: true } }
|
|
},
|
|
{
|
|
name: 'require crypto-js AES 加解密',
|
|
code: `async function main() {
|
|
const CryptoJS = require('crypto-js');
|
|
const encrypted = CryptoJS.AES.encrypt('hello', 'secret').toString();
|
|
const decrypted = CryptoJS.AES.decrypt(encrypted, 'secret').toString(CryptoJS.enc.Utf8);
|
|
return { match: decrypted === 'hello' };
|
|
}`,
|
|
expect: { success: true, codeReturn: { match: true } }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
// --- 错误处理 ---
|
|
describe('错误处理', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: '语法错误',
|
|
code: `async function main() { return {{{ }`,
|
|
expect: { success: false }
|
|
},
|
|
{
|
|
name: '运行时异常',
|
|
code: `async function main() { throw new Error('boom'); }`,
|
|
expect: { success: false, errorMatch: /boom/ }
|
|
},
|
|
{
|
|
name: '未定义变量',
|
|
code: `async function main() { return { val: undefinedVar }; }`,
|
|
expect: { success: false }
|
|
},
|
|
{
|
|
name: '超时',
|
|
code: `async function main() { while(true) {} return {}; }`,
|
|
expect: { success: false }
|
|
},
|
|
{
|
|
name: '无限递归',
|
|
code: `function recurse() { return recurse(); }
|
|
async function main() { return recurse(); }`,
|
|
expect: { success: false }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
// --- 内置工具函数 ---
|
|
describe('内置工具函数', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: 'delay 正常延迟',
|
|
code: `async function main() {
|
|
const start = Date.now();
|
|
await delay(500);
|
|
const elapsed = Date.now() - start;
|
|
return { elapsed: elapsed >= 400 };
|
|
}`,
|
|
expect: { success: true, codeReturn: { elapsed: true } }
|
|
},
|
|
{
|
|
name: 'strToBase64 编码',
|
|
code: `async function main() {
|
|
const encoded = strToBase64('Hello, World!');
|
|
return { encoded };
|
|
}`,
|
|
expect: { success: true, codeReturn: { encoded: 'SGVsbG8sIFdvcmxkIQ==' } }
|
|
},
|
|
{
|
|
name: 'countToken 计算',
|
|
code: `async function main({ text }) {
|
|
return { tokens: countToken(text) };
|
|
}`,
|
|
variables: { text: 'Hello, this is a test sentence.' },
|
|
expect: { success: true }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
// --- 网络请求 ---
|
|
describe('网络请求', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: 'httpRequest GET',
|
|
code: `async function main() {
|
|
const res = await httpRequest('https://www.baidu.com');
|
|
return { status: res.status, hasData: res.data.length > 0 };
|
|
}`,
|
|
expect: { success: true, codeReturnMatch: { status: 200, hasData: true } }
|
|
},
|
|
{
|
|
name: 'httpRequest POST JSON',
|
|
code: `async function main() {
|
|
const res = await httpRequest('https://www.baidu.com', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: { message: 'hello' }
|
|
});
|
|
return { hasStatus: typeof res.status === 'number' };
|
|
}`,
|
|
expect: { success: true, codeReturnMatch: { hasStatus: true } }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// Python 功能测试矩阵
|
|
// ============================================================
|
|
describe('Python 功能测试', () => {
|
|
let pool: PythonProcessPool;
|
|
beforeAll(async () => {
|
|
pool = new PythonProcessPool(1);
|
|
await pool.init();
|
|
});
|
|
afterAll(async () => {
|
|
await pool.shutdown();
|
|
});
|
|
|
|
// --- 基础运算 ---
|
|
describe('基础运算', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: '返回简单字典',
|
|
code: `def main():\n return {'hello': 'world'}`,
|
|
expect: { success: true, codeReturn: { hello: 'world' } }
|
|
},
|
|
{
|
|
name: '数学运算',
|
|
code: `def main():\n return {'sum': 1 + 2, 'product': 3 * 4, 'power': 2 ** 10}`,
|
|
expect: { success: true, codeReturn: { sum: 3, product: 12, power: 1024 } }
|
|
},
|
|
{
|
|
name: '字符串操作',
|
|
code: `def main():\n s = 'Hello, World!'\n return {'upper': s.upper(), 'len': len(s), 'split': s.split(', ')}`,
|
|
expect: {
|
|
success: true,
|
|
codeReturn: { upper: 'HELLO, WORLD!', len: 13, split: ['Hello', 'World!'] }
|
|
}
|
|
},
|
|
{
|
|
name: '列表操作',
|
|
code: `def main():\n arr = [3, 1, 4, 1, 5, 9]\n return {'sorted': sorted(arr), 'sum': sum(arr), 'len': len(arr)}`,
|
|
expect: { success: true, codeReturn: { sorted: [1, 1, 3, 4, 5, 9], sum: 23, len: 6 } }
|
|
},
|
|
{
|
|
name: '字典推导式',
|
|
code: `def main():\n d = {k: v**2 for k, v in {'a': 1, 'b': 2, 'c': 3}.items()}\n return d`,
|
|
expect: { success: true, codeReturn: { a: 1, b: 4, c: 9 } }
|
|
},
|
|
{
|
|
name: '列表推导式',
|
|
code: `def main():\n evens = [x for x in range(10) if x % 2 == 0]\n return {'evens': evens}`,
|
|
expect: { success: true, codeReturn: { evens: [0, 2, 4, 6, 8] } }
|
|
},
|
|
{
|
|
name: 'try/except 异常处理',
|
|
code: `def main():\n try:\n result = 1 / 0\n except ZeroDivisionError as e:\n return {'caught': True}\n return {'caught': False}`,
|
|
expect: { success: true, codeReturn: { caught: true } }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
// --- 变量传递 ---
|
|
describe('变量传递', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: '接收字符串变量',
|
|
code: `def main(v):\n return {'greeting': f"Hello, {v['name']}!"}`,
|
|
variables: { name: 'Bob' },
|
|
expect: { success: true, codeReturn: { greeting: 'Hello, Bob!' } }
|
|
},
|
|
{
|
|
name: '接收数字变量',
|
|
code: `def main(v):\n return {'doubled': int(v['num']) * 2}`,
|
|
variables: { num: '21' },
|
|
expect: { success: true, codeReturn: { doubled: 42 } }
|
|
},
|
|
{
|
|
name: '多个变量',
|
|
code: `def main(v):\n return {'result': f"{v['a']} {v['b']} {v['c']}"}`,
|
|
variables: { a: 'hello', b: 'beautiful', c: 'world' },
|
|
expect: { success: true, codeReturn: { result: 'hello beautiful world' } }
|
|
},
|
|
{
|
|
name: 'main() 无参数',
|
|
code: `def main():\n return {'ok': True}`,
|
|
variables: { unused: 1 },
|
|
expect: { success: true, codeReturn: { ok: true } }
|
|
},
|
|
{
|
|
name: 'main(a, b) 多参数展开',
|
|
code: `def main(name, age):\n return {'name': name, 'age': age}`,
|
|
variables: { name: 'test', age: 25 },
|
|
expect: { success: true, codeReturn: { name: 'test', age: 25 } }
|
|
},
|
|
{
|
|
name: 'main(content) 单参数按名取字符串值,可调用 split',
|
|
code: `def main(content):\n parts = content.split(',')\n return {'parts': parts, 'count': len(parts)}`,
|
|
variables: { content: 'a,b,c' },
|
|
expect: { success: true, codeReturn: { parts: ['a', 'b', 'c'], count: 3 } }
|
|
},
|
|
{
|
|
name: 'main(items) 单参数按名取列表值',
|
|
code: `def main(items):\n return {'sum': sum(items), 'len': len(items)}`,
|
|
variables: { items: [1, 2, 3, 4, 5] },
|
|
expect: { success: true, codeReturn: { sum: 15, len: 5 } }
|
|
},
|
|
{
|
|
name: 'main(v) 单参数名不在 variables 时回退传整个 dict',
|
|
code: `def main(v):\n return {'a': v['x'], 'b': v['y']}`,
|
|
variables: { x: 10, y: 20 },
|
|
expect: { success: true, codeReturn: { a: 10, b: 20 } }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
// --- 安全模块使用 ---
|
|
describe('安全模块', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: 'import json',
|
|
code: `import json\ndef main():\n data = json.dumps({'key': 'value'}, ensure_ascii=False)\n parsed = json.loads(data)\n return {'data': data, 'key': parsed['key']}`,
|
|
expect: { success: true, codeReturnMatch: { key: 'value' } }
|
|
},
|
|
{
|
|
name: 'import math',
|
|
code: `import math\ndef main():\n return {'pi': round(math.pi, 4), 'sqrt2': round(math.sqrt(2), 4), 'e': round(math.e, 4)}`,
|
|
expect: { success: true, codeReturn: { pi: 3.1416, sqrt2: 1.4142, e: 2.7183 } }
|
|
},
|
|
{
|
|
name: 'import re',
|
|
code: `import re\ndef main():\n text = 'Price: $12.99, Tax: $1.30'\n prices = re.findall(r'\\$(\\d+\\.\\d+)', text)\n return {'prices': prices}`,
|
|
expect: { success: true, codeReturn: { prices: ['12.99', '1.30'] } }
|
|
},
|
|
{
|
|
name: 'from datetime import datetime',
|
|
code: `from datetime import datetime\ndef main():\n d = datetime(2024, 1, 15, 12, 0, 0)\n return {'iso': d.isoformat(), 'year': d.year}`,
|
|
expect: { success: true, codeReturn: { iso: '2024-01-15T12:00:00', year: 2024 } }
|
|
},
|
|
{
|
|
name: 'import hashlib',
|
|
code: `import hashlib\ndef main():\n h = hashlib.md5(b'hello').hexdigest()\n return {'md5': h}`,
|
|
expect: { success: true, codeReturn: { md5: '5d41402abc4b2a76b9719d911017c592' } }
|
|
},
|
|
{
|
|
name: 'import base64',
|
|
code: `import base64\ndef main():\n encoded = base64.b64encode(b'Hello').decode()\n decoded = base64.b64decode(encoded).decode()\n return {'encoded': encoded, 'decoded': decoded}`,
|
|
expect: { success: true, codeReturn: { encoded: 'SGVsbG8=', decoded: 'Hello' } }
|
|
},
|
|
{
|
|
name: 'import hashlib sha256',
|
|
code: `import hashlib\ndef main():\n h = hashlib.sha256(b'hello').hexdigest()\n return {'hash': h}`,
|
|
expect: {
|
|
success: true,
|
|
codeReturn: { hash: '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824' }
|
|
}
|
|
},
|
|
{
|
|
name: 'from collections import Counter',
|
|
code: `from collections import Counter\ndef main(v):\n c = Counter(v['items'])\n return dict(c.most_common())`,
|
|
variables: { items: ['a', 'b', 'a', 'c', 'a', 'b'] },
|
|
expect: { success: true, codeReturnMatch: { a: 3, b: 2 } }
|
|
},
|
|
{
|
|
name: 'datetime + timedelta',
|
|
code: `from datetime import datetime, timedelta\ndef main():\n d = datetime(2024, 1, 15)\n next_week = d + timedelta(days=7)\n return {'formatted': d.strftime('%Y/%m/%d'), 'next_week': next_week.strftime('%Y/%m/%d')}`,
|
|
expect: {
|
|
success: true,
|
|
codeReturn: { formatted: '2024/01/15', next_week: '2024/01/22' }
|
|
}
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
// --- 内置工具函数 ---
|
|
describe('内置工具函数', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: 'str_to_base64 编码',
|
|
code: `def main():\n encoded = str_to_base64('Hello, World!')\n return {'encoded': encoded}`,
|
|
expect: { success: true, codeReturn: { encoded: 'SGVsbG8sIFdvcmxkIQ==' } }
|
|
},
|
|
{
|
|
name: 'delay 正常延迟',
|
|
code: `import time\ndef main():\n start = time.time()\n delay(500)\n elapsed = time.time() - start\n return {'elapsed_ok': elapsed >= 0.4}`,
|
|
expect: { success: true, codeReturn: { elapsed_ok: true } }
|
|
},
|
|
{
|
|
name: 'count_token 计算',
|
|
code: `def main(v):\n return {'tokens': count_token(v['text'])}`,
|
|
variables: { text: 'Hello, this is a test sentence.' },
|
|
expect: { success: true }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
// --- print 日志 ---
|
|
describe('日志输出', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: 'print 被捕获',
|
|
code: `def main():\n print('hello log')\n return {'done': True}`,
|
|
expect: { success: true, codeReturn: { done: true }, logMatch: /hello log/ }
|
|
},
|
|
{
|
|
name: '多次 print 均被捕获',
|
|
code: `def main():\n print('line1')\n print('line2')\n return {'done': True}`,
|
|
expect: { success: true, logMatch: /line1/ }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
// --- 错误处理 ---
|
|
describe('错误处理', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: '语法错误',
|
|
code: `def main():\n return {{{`,
|
|
expect: { success: false }
|
|
},
|
|
{
|
|
name: '运行时异常',
|
|
code: `def main():\n raise ValueError('boom')`,
|
|
expect: { success: false, errorMatch: /boom/ }
|
|
},
|
|
{
|
|
name: '未定义变量',
|
|
code: `def main():\n return {'val': undefined_var}`,
|
|
expect: { success: false }
|
|
},
|
|
{
|
|
name: '超时',
|
|
code: `def main():\n while True:\n pass\n return {}`,
|
|
expect: { success: false }
|
|
},
|
|
{
|
|
name: '除零错误',
|
|
code: `def main():\n return {'value': 1 / 0}`,
|
|
expect: { success: false }
|
|
},
|
|
{
|
|
name: '索引越界',
|
|
code: `def main():\n arr = [1, 2, 3]\n return {'value': arr[10]}`,
|
|
expect: { success: false }
|
|
},
|
|
{
|
|
name: '无限递归',
|
|
code: `def recurse():\n return recurse()\ndef main():\n return recurse()`,
|
|
expect: { success: false }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
// --- 网络请求 ---
|
|
describe('网络请求', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: 'http_request GET',
|
|
code: `import json\ndef main():\n res = http_request('https://www.baidu.com')\n return {'status': res['status'], 'hasData': len(res['data']) > 0}`,
|
|
expect: { success: true, codeReturnMatch: { status: 200, hasData: true } }
|
|
},
|
|
{
|
|
name: 'http_request POST JSON',
|
|
code: `import json\ndef main():\n res = http_request('https://www.baidu.com', method='POST', body={'message': 'hello'})\n return {'hasStatus': type(res['status']) == int}`,
|
|
expect: { success: true, codeReturnMatch: { hasStatus: true } }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
// --- 复杂场景 ---
|
|
describe('复杂场景', () => {
|
|
runMatrix(
|
|
() => pool,
|
|
[
|
|
{
|
|
name: '数据处理管道:解析CSV → 过滤 → 聚合',
|
|
code: `def main(v):
|
|
lines = v['csv'].strip().split('\\n')
|
|
header = lines[0].split(',')
|
|
rows = [dict(zip(header, line.split(','))) for line in lines[1:]]
|
|
adults = [r for r in rows if int(r['age']) >= 18]
|
|
avg_age = sum(int(r['age']) for r in adults) / len(adults)
|
|
return {'total': len(rows), 'adults': len(adults), 'avg_age': avg_age}`,
|
|
variables: { csv: 'name,age\nAlice,25\nBob,17\nCharlie,30\nDiana,15' },
|
|
expect: { success: true, codeReturn: { total: 4, adults: 2, avg_age: 27.5 } }
|
|
},
|
|
{
|
|
name: '递归:斐波那契',
|
|
code: `def main():
|
|
def fib(n):
|
|
if n <= 1: return n
|
|
return fib(n-1) + fib(n-2)
|
|
return {'fib10': fib(10), 'fib20': fib(20)}`,
|
|
expect: { success: true, codeReturn: { fib10: 55, fib20: 6765 } }
|
|
},
|
|
{
|
|
name: '类定义和使用',
|
|
code: `def main():
|
|
class Point:
|
|
def __init__(self, x, y):
|
|
self.x = x
|
|
self.y = y
|
|
def distance(self, other):
|
|
return ((self.x - other.x)**2 + (self.y - other.y)**2)**0.5
|
|
p1 = Point(0, 0)
|
|
p2 = Point(3, 4)
|
|
return {'distance': p1.distance(p2)}`,
|
|
expect: { success: true, codeReturn: { distance: 5.0 } }
|
|
}
|
|
]
|
|
);
|
|
});
|
|
});
|