Files
ECC/tests/ci/bengali-language-surface.test.js
T

245 lines
8.1 KiB
JavaScript

#!/usr/bin/env node
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const ROOT = path.join(__dirname, '..', '..');
const { resolveInstallPlan } = require(path.join(ROOT, 'scripts', 'lib', 'install-manifests.js'));
// Every file that carries a language selector linking to the Bengali README.
// Root-level files link via `docs/bn/README.md`; translated docs link via `../bn/README.md`.
const SELECTOR_FILES = [
'README.md',
'README.zh-CN.md',
'docs/de-DE/README.md',
'docs/es/README.md',
'docs/ja-JP/README.md',
'docs/ko-KR/README.md',
'docs/pt-BR/README.md',
'docs/ru/README.md',
'docs/th/README.md',
'docs/tr/README.md',
'docs/uk-UA/README.md',
'docs/ur/README.md',
'docs/vi-VN/README.md',
'docs/zh-CN/README.md',
'docs/zh-TW/README.md',
];
function read(relativePath) {
return fs.readFileSync(path.join(ROOT, relativePath), 'utf8');
}
function readJson(relativePath) {
return JSON.parse(read(relativePath));
}
let passed = 0;
let failed = 0;
let skipped = 0;
function test(name, fn) {
try {
fn();
console.log(` ✓ ${name}`);
passed++;
} catch (error) {
if (error instanceof SkipError) {
console.log(` ○ ${name} (SKIPPED: ${error.message})`);
skipped++;
return;
}
console.log(` ✗ ${name}`);
console.error(` ${error.message}`);
failed++;
}
}
class SkipError extends Error {}
function findPython() {
for (const command of process.platform === 'win32' ? ['python', 'py'] : ['python3', 'python']) {
const args = command === 'py' ? ['-3', '--version'] : ['--version'];
const result = spawnSync(command, args, { encoding: 'utf8' });
if (result.status === 0) {
return { command, prefix: command === 'py' ? ['-3'] : [] };
}
}
return null;
}
// Walks every fence in the document so an unclosed ```python block fails loudly
// instead of being silently dropped by a non-greedy regex.
function extractFencedBlocks(markdown) {
const lines = markdown.split('\n');
const blocks = [];
let open = null;
let buffer = [];
lines.forEach((line, index) => {
const fence = /^\s*```(.*)$/.exec(line);
if (!fence) {
if (open) buffer.push(line);
return;
}
const info = fence[1].trim();
if (!open) {
open = { language: info.split(/\s+/)[0] || '', line: index + 1 };
buffer = [];
return;
}
if (info !== '') {
throw new Error(
`Fence opened at line ${open.line} is closed by an info-string fence at line ${index + 1}`,
);
}
blocks.push({ language: open.language, code: buffer.join('\n'), line: open.line });
open = null;
buffer = [];
});
if (open) {
throw new Error(`Unclosed \`\`\`${open.language} fence opened at line ${open.line}`);
}
return blocks;
}
function run() {
console.log('\n=== Testing Bengali language surfaces ===\n');
test('Bengali Python examples are syntactically valid', () => {
const skill = read('skills/bengali-nlp/SKILL.md');
const blocks = extractFencedBlocks(skill);
const snippets = blocks.filter(block => block.language === 'python');
assert.ok(snippets.length > 0, 'Expected at least one Python example');
const python = findPython();
if (!python) {
throw new SkipError('no Python 3 interpreter on PATH');
}
snippets.forEach(snippet => {
const encoded = Buffer.from(snippet.code, 'utf8').toString('base64');
const result = spawnSync(
python.command,
[
...python.prefix,
'-c',
"import base64, sys; compile(base64.b64decode(sys.stdin.buffer.read()).decode('utf-8'), '<bengali-nlp>', 'exec')",
],
{ input: encoded, encoding: 'utf8' },
);
assert.strictEqual(
result.status,
0,
`Python example at line ${snippet.line} does not compile:\n${result.stderr || result.stdout}`,
);
});
});
test('Bengali tokenizer preserves decimal quantities', () => {
const skill = read('skills/bengali-nlp/SKILL.md');
const tokenizer = extractFencedBlocks(skill)
.filter(block => block.language === 'python')
.find(block => block.code.includes('def tokenize_bangla'));
assert.ok(tokenizer, 'Expected the Bengali tokenizer example');
const python = findPython();
if (!python) {
throw new SkipError('no Python 3 interpreter on PATH');
}
const check = [
tokenizer.code,
"assert tokenize_bangla('ওজন ৩.৫ কেজি। মূল্য 12.75 টাকা.') == ['ওজন', '৩.৫', 'কেজি', 'মূল্য', '12.75', 'টাকা']",
].join('\n');
const result = spawnSync(python.command, [...python.prefix, '-c', check], {
encoding: 'utf8',
});
assert.strictEqual(
result.status,
0,
`Bengali tokenizer changed decimal quantities:\n${result.stderr || result.stdout}`,
);
});
test('Bengali skill is exported and installable', () => {
const agentYaml = read('agent.yaml');
const modules = readJson('manifests/install-modules.json').modules;
const packageFiles = readJson('package.json').files;
const frameworkModule = modules.find(module => module.id === 'framework-language');
assert.match(agentYaml, /^\s+- bengali-nlp\s*$/m);
assert.ok(frameworkModule?.paths.includes('skills/bengali-nlp'));
assert.ok(packageFiles.includes('skills/bengali-nlp/'));
});
test('Selecting the Bengali skill installs only the Bengali skill', () => {
const plan = resolveInstallPlan({ includeComponentIds: ['skill:bengali-nlp'] });
const paths = plan.selectedModules.flatMap(module => module.paths || []);
assert.deepStrictEqual(plan.selectedModuleIds, ['skill-bengali-nlp']);
assert.deepStrictEqual(paths, ['skills/bengali-nlp']);
});
test('Bengali reviewer is discoverable through the shared agent surface', () => {
const agentsGuide = read('AGENTS.md');
const modules = readJson('manifests/install-modules.json').modules;
const agentsModule = modules.find(module => module.id === 'agents-core');
assert.ok(fs.existsSync(path.join(ROOT, 'agents', 'bengali-reviewer.md')));
assert.match(agentsGuide, /^\| bengali-reviewer \|/m);
assert.ok(agentsModule?.paths.includes('agents'));
});
test('Bengali documentation has an installable locale route', () => {
const modules = readJson('manifests/install-modules.json').modules;
const components = readJson('manifests/install-components.json').components;
const packageFiles = readJson('package.json').files;
const docsModule = modules.find(module => module.id === 'docs-bn');
const localeComponent = components.find(component => component.id === 'locale:bn');
assert.ok(fs.existsSync(path.join(ROOT, 'docs', 'bn', 'README.md')));
assert.deepStrictEqual(docsModule?.paths, ['docs/bn']);
assert.ok(localeComponent?.modules.includes('docs-bn'));
assert.ok(packageFiles.includes('docs/bn/'));
});
test('Every language selector links to the Bengali README', () => {
SELECTOR_FILES.forEach(relativePath => {
const contents = read(relativePath);
const route = relativePath.includes('/') ? '../bn/README.md' : 'docs/bn/README.md';
assert.ok(
contents.includes(`](${route})`) || contents.includes(`href="${route}"`),
`${relativePath} is missing the Bengali language link (${route})`,
);
assert.ok(contents.includes('বাংলা'), `${relativePath} is missing the বাংলা label`);
});
});
test('Bengali README offers the same languages as the root README', () => {
const rootLabels = [...read('README.md')
.matchAll(/<a href="(?:docs\/[\w-]+\/)?README(?:\.[\w-]+)?\.md">([^<]+)<\/a>/g)]
.map(match => match[1].trim());
assert.ok(rootLabels.length > 5, 'Expected the root README language selector to be parsed');
const bengali = read('docs/bn/README.md');
rootLabels.forEach(label => {
assert.ok(bengali.includes(label), `docs/bn/README.md is missing the ${label} link`);
});
});
console.log(`\nPassed: ${passed}`);
if (skipped > 0) console.log(`Skipped: ${skipped}`);
console.log(`Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
}
run();