csa / test /unit-openai-compat.mjs
ricebug's picture
Upload 76 files
ca8ab2d verified
/**
* test/unit-openai-compat.mjs
*
* ๅ•ๅ…ƒๆต‹่ฏ•๏ผšOpenAI ๅค„็†ๅ™จๅ…ผๅฎนๆ€งๅŠŸ่ƒฝ
* - responsesToChatCompletions ่ฝฌๆข
* - Cursor ๆ‰ๅนณๆ ผๅผๅทฅๅ…ทๅ…ผๅฎน
* - ๆถˆๆฏ่ง’่‰ฒๅˆๅนถ
*
* ่ฟ่กŒๆ–นๅผ๏ผšnode test/unit-openai-compat.mjs
*/
// โ”€โ”€โ”€ ๆต‹่ฏ•ๆก†ๆžถ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(` โœ… ${name}`);
passed++;
} catch (e) {
console.error(` โŒ ${name}`);
console.error(` ${e.message}`);
failed++;
}
}
function assert(condition, msg) {
if (!condition) throw new Error(msg || 'Assertion failed');
}
function assertEqual(a, b, msg) {
const as = JSON.stringify(a), bs = JSON.stringify(b);
if (as !== bs) throw new Error(msg || `Expected ${bs}, got ${as}`);
}
function stringifyUnknownContent(value) {
if (value === null || value === undefined) return '';
if (typeof value === 'string') return value;
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
return String(value);
}
try {
return JSON.stringify(value);
} catch {
return String(value);
}
}
function extractOpenAIContentBlocks(msg) {
if (msg.content === null || msg.content === undefined) return '';
if (typeof msg.content === 'string') return msg.content;
if (Array.isArray(msg.content)) {
const blocks = [];
for (const p of msg.content) {
if ((p.type === 'text' || p.type === 'input_text') && p.text) {
blocks.push({ type: 'text', text: p.text });
} else if (p.type === 'image_url' && p.image_url?.url) {
blocks.push({
type: 'image',
source: { type: 'url', media_type: 'image/jpeg', data: p.image_url.url },
});
} else if (p.type === 'input_image' && p.image_url?.url) {
blocks.push({
type: 'image',
source: { type: 'url', media_type: 'image/jpeg', data: p.image_url.url },
});
}
}
return blocks.length > 0 ? blocks : '';
}
return stringifyUnknownContent(msg.content);
}
function extractOpenAIContent(msg) {
const blocks = extractOpenAIContentBlocks(msg);
if (typeof blocks === 'string') return blocks;
return blocks.filter(b => b.type === 'text').map(b => b.text).join('\n');
}
// โ”€โ”€โ”€ ๅ†…่” mergeConsecutiveRoles๏ผˆไธŽ src/openai-handler.ts ไฟๆŒๅŒๆญฅ๏ผ‰โ”€โ”€โ”€โ”€
function toBlocks(content) {
if (typeof content === 'string') {
return content ? [{ type: 'text', text: content }] : [];
}
return content || [];
}
function mergeConsecutiveRoles(messages) {
if (messages.length <= 1) return messages;
const merged = [];
for (const msg of messages) {
const last = merged[merged.length - 1];
if (last && last.role === msg.role) {
const lastBlocks = toBlocks(last.content);
const newBlocks = toBlocks(msg.content);
last.content = [...lastBlocks, ...newBlocks];
} else {
merged.push({ ...msg });
}
}
return merged;
}
// โ”€โ”€โ”€ ๅ†…่” responsesToChatCompletions๏ผˆไธŽ src/openai-handler.ts ไฟๆŒๅŒๆญฅ๏ผ‰
function responsesToChatCompletions(body) {
const messages = [];
if (body.instructions && typeof body.instructions === 'string') {
messages.push({ role: 'system', content: body.instructions });
}
const input = body.input;
if (typeof input === 'string') {
messages.push({ role: 'user', content: input });
} else if (Array.isArray(input)) {
for (const item of input) {
// function_call_output has type but no role โ€” check first
if (item.type === 'function_call_output') {
messages.push({
role: 'tool',
content: stringifyUnknownContent(item.output),
tool_call_id: item.call_id || '',
});
continue;
}
const role = item.role || 'user';
if (role === 'system' || role === 'developer') {
const text = extractOpenAIContent({
role: 'system',
content: item.content ?? null,
});
messages.push({ role: 'system', content: text });
} else if (role === 'user') {
const rawContent = item.content ?? null;
const normalizedContent = typeof rawContent === 'string'
? rawContent
: Array.isArray(rawContent) && rawContent.every(b => b.type === 'input_text')
? rawContent.map(b => b.text || '').join('\n')
: rawContent;
messages.push({
role: 'user',
content: normalizedContent,
});
} else if (role === 'assistant') {
const blocks = Array.isArray(item.content) ? item.content : [];
const text = blocks.filter(b => b.type === 'output_text').map(b => b.text).join('\n');
const toolCallBlocks = blocks.filter(b => b.type === 'function_call');
const toolCalls = toolCallBlocks.map(b => ({
id: b.call_id || `call_${Math.random().toString(36).slice(2)}`,
type: 'function',
function: {
name: b.name || '',
arguments: b.arguments || '{}',
},
}));
messages.push({
role: 'assistant',
content: text || null,
...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
});
}
}
}
const tools = Array.isArray(body.tools)
? body.tools.map(t => ({
type: 'function',
function: {
name: t.name || '',
description: t.description,
parameters: t.parameters,
},
}))
: undefined;
return {
model: body.model || 'gpt-4',
messages,
stream: body.stream ?? true,
temperature: body.temperature,
max_tokens: body.max_output_tokens || 8192,
tools,
};
}
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// 1. responsesToChatCompletions โ€” ๅŸบๆœฌ่ฝฌๆข
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
console.log('\n๐Ÿ“ฆ [1] responsesToChatCompletions โ€” ๅŸบๆœฌ่ฝฌๆข\n');
test('็ฎ€ๅ•ๅญ—็ฌฆไธฒ input โ†’ user ๆถˆๆฏ', () => {
const result = responsesToChatCompletions({
model: 'gpt-4',
input: 'Hello, how are you?',
});
assertEqual(result.model, 'gpt-4');
assertEqual(result.messages.length, 1);
assertEqual(result.messages[0].role, 'user');
assertEqual(result.messages[0].content, 'Hello, how are you?');
});
test('ๅธฆ instructions โ†’ system ๆถˆๆฏ', () => {
const result = responsesToChatCompletions({
model: 'gpt-4',
instructions: 'You are a helpful assistant.',
input: 'Hello',
});
assertEqual(result.messages.length, 2);
assertEqual(result.messages[0].role, 'system');
assertEqual(result.messages[0].content, 'You are a helpful assistant.');
assertEqual(result.messages[1].role, 'user');
});
test('ๅคš่ฝฎๅฏน่ฏ input ๆ•ฐ็ป„', () => {
const result = responsesToChatCompletions({
model: 'gpt-4',
input: [
{ role: 'user', content: 'What is 2+2?' },
{ role: 'assistant', content: [{ type: 'output_text', text: '4' }] },
{ role: 'user', content: 'And 3+3?' },
],
});
assertEqual(result.messages.length, 3);
assertEqual(result.messages[0].role, 'user');
assertEqual(result.messages[1].role, 'assistant');
assertEqual(result.messages[1].content, '4');
assertEqual(result.messages[2].role, 'user');
});
test('developer ่ง’่‰ฒ โ†’ system', () => {
const result = responsesToChatCompletions({
model: 'gpt-4',
input: [
{ role: 'developer', content: 'You are a coding assistant.' },
{ role: 'user', content: 'Write hello world' },
],
});
assertEqual(result.messages[0].role, 'system');
assertEqual(result.messages[0].content, 'You are a coding assistant.');
});
test('function_call_output โ†’ tool ๆถˆๆฏ', () => {
const result = responsesToChatCompletions({
model: 'gpt-4',
input: [
{ role: 'user', content: 'List files' },
{
role: 'assistant',
content: [{
type: 'function_call',
call_id: 'call_123',
name: 'list_dir',
arguments: '{"path":"."}'
}]
},
{
type: 'function_call_output',
call_id: 'call_123',
output: 'file1.ts\nfile2.ts'
},
],
});
assertEqual(result.messages.length, 3);
assertEqual(result.messages[2].role, 'tool');
assertEqual(result.messages[2].content, 'file1.ts\nfile2.ts');
assertEqual(result.messages[2].tool_call_id, 'call_123');
});
test('function_call_output ๅฏน่ฑก โ†’ JSON ๅญ—็ฌฆไธฒ', () => {
const result = responsesToChatCompletions({
model: 'gpt-4',
input: [
{ role: 'user', content: 'Summarize tool output' },
{
type: 'function_call_output',
call_id: 'call_obj',
output: { files: ['a.ts', 'b.ts'], count: 2 }
},
],
});
assertEqual(result.messages.length, 2);
assertEqual(result.messages[1].role, 'tool');
assertEqual(result.messages[1].content, '{"files":["a.ts","b.ts"],"count":2}');
assertEqual(result.messages[1].tool_call_id, 'call_obj');
});
test('ๅŠฉๆ‰‹ๆถˆๆฏๅธฆ function_call โ†’ tool_calls', () => {
const result = responsesToChatCompletions({
model: 'gpt-4',
input: [
{ role: 'user', content: 'Read file' },
{
role: 'assistant',
content: [{
type: 'function_call',
call_id: 'call_abc',
name: 'read_file',
arguments: '{"path":"index.ts"}'
}]
},
],
});
assertEqual(result.messages[1].role, 'assistant');
assert(result.messages[1].tool_calls, 'should have tool_calls');
assertEqual(result.messages[1].tool_calls.length, 1);
assertEqual(result.messages[1].tool_calls[0].function.name, 'read_file');
assertEqual(result.messages[1].tool_calls[0].function.arguments, '{"path":"index.ts"}');
});
test('ๅทฅๅ…ทๅฎšไน‰่ฝฌๆข', () => {
const result = responsesToChatCompletions({
model: 'gpt-4',
input: 'hello',
tools: [
{
type: 'function',
name: 'read_file',
description: 'Read a file',
parameters: { type: 'object', properties: { path: { type: 'string' } } },
}
],
});
assert(result.tools, 'should have tools');
assertEqual(result.tools.length, 1);
assertEqual(result.tools[0].function.name, 'read_file');
});
test('input_text content ๆ•ฐ็ป„', () => {
const result = responsesToChatCompletions({
model: 'gpt-4',
input: [
{
role: 'user',
content: [
{ type: 'input_text', text: 'Part 1' },
{ type: 'input_text', text: 'Part 2' },
]
},
],
});
assertEqual(result.messages[0].content, 'Part 1\nPart 2');
});
test('Responses user input_image ไธๅบ”ไธขๅคฑ', () => {
const result = responsesToChatCompletions({
model: 'gpt-4',
input: [
{
role: 'user',
content: [
{ type: 'input_text', text: '่ฏทๆ่ฟฐ่ฟ™ๅผ ๅ›พ' },
{ type: 'input_image', image_url: { url: 'https://example.com/image.jpg' } },
]
},
],
});
assertEqual(result.messages.length, 1);
assert(Array.isArray(result.messages[0].content), 'content should remain multimodal blocks');
assertEqual(result.messages[0].content[0], { type: 'input_text', text: '่ฏทๆ่ฟฐ่ฟ™ๅผ ๅ›พ' });
assertEqual(result.messages[0].content[1], { type: 'input_image', image_url: { url: 'https://example.com/image.jpg' } });
});
test('stream ้ป˜่ฎคไธบ true', () => {
const result = responsesToChatCompletions({ model: 'gpt-4', input: 'hi' });
assertEqual(result.stream, true);
});
test('stream ๆ˜พๅผ่ฎพไธบ false', () => {
const result = responsesToChatCompletions({ model: 'gpt-4', input: 'hi', stream: false });
assertEqual(result.stream, false);
});
test('max_output_tokens ่ฝฌๆข', () => {
const result = responsesToChatCompletions({ model: 'gpt-4', input: 'hi', max_output_tokens: 4096 });
assertEqual(result.max_tokens, 4096);
});
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// 2. mergeConsecutiveRoles โ€” ๆถˆๆฏๅˆๅนถ
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
console.log('\n๐Ÿ“ฆ [2] mergeConsecutiveRoles โ€” ๆถˆๆฏๅˆๅนถ\n');
test('ไบคๆ›ฟ่ง’่‰ฒไธๅˆๅนถ', () => {
const msgs = [
{ role: 'user', content: 'Hello' },
{ role: 'assistant', content: 'Hi' },
{ role: 'user', content: 'Bye' },
];
const result = mergeConsecutiveRoles(msgs);
assertEqual(result.length, 3);
});
test('่ฟž็ปญ user ๆถˆๆฏๅˆๅนถ', () => {
const msgs = [
{ role: 'user', content: 'Message 1' },
{ role: 'user', content: 'Message 2' },
{ role: 'assistant', content: 'Response' },
];
const result = mergeConsecutiveRoles(msgs);
assertEqual(result.length, 2);
assertEqual(result[0].role, 'user');
// ๅˆๅนถๅŽๅบ”ไธบ block ๆ•ฐ็ป„
assert(Array.isArray(result[0].content), 'merged content should be array');
assertEqual(result[0].content.length, 2);
assertEqual(result[0].content[0].text, 'Message 1');
assertEqual(result[0].content[1].text, 'Message 2');
});
test('่ฟž็ปญ assistant ๆถˆๆฏๅˆๅนถ', () => {
const msgs = [
{ role: 'user', content: 'Hello' },
{ role: 'assistant', content: 'Part 1' },
{ role: 'assistant', content: 'Part 2' },
];
const result = mergeConsecutiveRoles(msgs);
assertEqual(result.length, 2);
assertEqual(result[1].role, 'assistant');
assert(Array.isArray(result[1].content));
assertEqual(result[1].content.length, 2);
});
test('tool result + text user ๆถˆๆฏๅˆๅนถ', () => {
const msgs = [
{ role: 'user', content: [{ type: 'tool_result', tool_use_id: 'id1', content: 'output' }] },
{ role: 'user', content: 'Follow up question' },
];
const result = mergeConsecutiveRoles(msgs);
assertEqual(result.length, 1);
assert(Array.isArray(result[0].content));
assertEqual(result[0].content.length, 2); // tool_result + text
});
test('็ฉบๆถˆๆฏๅˆ—่กจ', () => {
assertEqual(mergeConsecutiveRoles([]).length, 0);
});
test('ๅ•ๆกๆถˆๆฏไธๅˆๅนถ', () => {
const result = mergeConsecutiveRoles([{ role: 'user', content: 'solo' }]);
assertEqual(result.length, 1);
});
test('ไธ‰ๆก่ฟž็ปญ user ๅ…จ้ƒจๅˆๅนถ', () => {
const msgs = [
{ role: 'user', content: 'A' },
{ role: 'user', content: 'B' },
{ role: 'user', content: 'C' },
];
const result = mergeConsecutiveRoles(msgs);
assertEqual(result.length, 1);
assert(Array.isArray(result[0].content));
assertEqual(result[0].content.length, 3);
});
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// 3. Cursor ๆ‰ๅนณๆ ผๅผๅทฅๅ…ทๅ…ผๅฎน
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
console.log('\n๐Ÿ“ฆ [3] Cursor ๆ‰ๅนณๆ ผๅผๅทฅๅ…ทๅ…ผๅฎน\n');
function convertTools(tools) {
return tools.map(t => {
if ('function' in t && t.function) {
return {
name: t.function.name,
description: t.function.description,
input_schema: t.function.parameters || { type: 'object', properties: {} },
};
}
return {
name: t.name || '',
description: t.description,
input_schema: t.input_schema || { type: 'object', properties: {} },
};
});
}
test('ๆ ‡ๅ‡† OpenAI ๆ ผๅผๅทฅๅ…ท', () => {
const tools = convertTools([{
type: 'function',
function: {
name: 'read_file',
description: 'Read file contents',
parameters: { type: 'object', properties: { path: { type: 'string' } } },
},
}]);
assertEqual(tools[0].name, 'read_file');
assertEqual(tools[0].description, 'Read file contents');
assert(tools[0].input_schema.properties.path);
});
test('Cursor ๆ‰ๅนณๆ ผๅผๅทฅๅ…ท', () => {
const tools = convertTools([{
name: 'write_file',
description: 'Write file',
input_schema: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' } } },
}]);
assertEqual(tools[0].name, 'write_file');
assertEqual(tools[0].description, 'Write file');
assert(tools[0].input_schema.properties.path);
assert(tools[0].input_schema.properties.content);
});
test('ๆททๅˆๆ ผๅผๅทฅๅ…ทๅˆ—่กจ', () => {
const tools = convertTools([
{
type: 'function',
function: { name: 'tool_a', description: 'A', parameters: {} },
},
{
name: 'tool_b',
description: 'B',
input_schema: {},
},
]);
assertEqual(tools.length, 2);
assertEqual(tools[0].name, 'tool_a');
assertEqual(tools[1].name, 'tool_b');
});
test('็ผบๅฐ‘ input_schema ็š„ๆ‰ๅนณๆ ผๅผ', () => {
const tools = convertTools([{ name: 'simple_tool' }]);
assertEqual(tools[0].name, 'simple_tool');
assert(tools[0].input_schema, 'should have default input_schema');
assertEqual(tools[0].input_schema.type, 'object');
});
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// 4. ๅขž้‡ๆตๅผๅทฅๅ…ท่ฐƒ็”จ้ชŒ่ฏ
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
console.log('\n๐Ÿ“ฆ [4] ๅขž้‡ๆตๅผๅทฅๅ…ท่ฐƒ็”จ้ชŒ่ฏ\n');
test('128 ๅญ—่Š‚ๅˆ†ๅ—๏ผšshort arguments', () => {
const args = '{"path":"src/index.ts"}';
const CHUNK_SIZE = 128;
const chunks = [];
for (let j = 0; j < args.length; j += CHUNK_SIZE) {
chunks.push(args.slice(j, j + CHUNK_SIZE));
}
// ็Ÿญๅ‚ๆ•ฐๅบ”ไธ€ๅธงๅ‘ๅฎŒ
assertEqual(chunks.length, 1);
assertEqual(chunks[0], args);
});
test('128 ๅญ—่Š‚ๅˆ†ๅ—๏ผšlong arguments', () => {
const longContent = 'A'.repeat(400);
const args = JSON.stringify({ path: 'test.ts', content: longContent });
const CHUNK_SIZE = 128;
const chunks = [];
for (let j = 0; j < args.length; j += CHUNK_SIZE) {
chunks.push(args.slice(j, j + CHUNK_SIZE));
}
// ๆ‹ผๆŽฅๅŽๅบ”็ญ‰ไบŽๅŽŸๅง‹ๆ•ฐๆฎ
assertEqual(chunks.join(''), args);
// ๅบ”ๆœ‰ๅคšๅธง
assert(chunks.length > 1, `Expected multiple chunks, got ${chunks.length}`);
// ๆฏๅธงๆœ€ๅคš 128 ๅญ—่Š‚
for (const c of chunks) {
assert(c.length <= CHUNK_SIZE, `Chunk too long: ${c.length}`);
}
});
test('็ฉบ arguments ้›ถๅธง', () => {
const args = '';
const CHUNK_SIZE = 128;
const chunks = [];
for (let j = 0; j < args.length; j += CHUNK_SIZE) {
chunks.push(args.slice(j, j + CHUNK_SIZE));
}
assertEqual(chunks.length, 0);
});
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ๆฑ‡ๆ€ป
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
console.log('\n' + 'โ•'.repeat(55));
console.log(` ็ป“ๆžœ: ${passed} ้€š่ฟ‡ / ${failed} ๅคฑ่ดฅ / ${passed + failed} ๆ€ป่ฎก`);
console.log('โ•'.repeat(55) + '\n');
if (failed > 0) process.exit(1);