Setting the file. One moment. Verify Examples · Batch Script Progress · github/awesome-copilot · Skills Docstests/verify-examples.mjs
tests/verify-examples.mjs
JavaScript·135 lines·6 KB
7
const
windows
=
process.platform
===
'win32'
;
8const runtimes = [
9 { name: 'Python', command: process.env.BATCH_PYTHON ?? (windows ? 'py.exe' : 'python3'), args: [...(windows && !process.env.BATCH_PYTHON ? ['-3'] : []), '-B', join(root, 'examples/batch.py')] },
10 { name: 'PowerShell', command: process.env.BATCH_POWERSHELL ?? (windows ? 'powershell.exe' : 'pwsh'), args: ['-NoProfile', '-NonInteractive', ...(windows ? ['-ExecutionPolicy', 'Bypass'] : []), '-File', join(root, 'examples/batch.ps1')] },
11 { name: 'Node.js', command: process.execPath, args: [join(root, 'examples/batch.mjs')] },
12];
13const cases = {
14 normal: { total: 3, succeeded: 3, failed: 0, skipped: 0, status: 'completed', code: 0 },
15 mixed: { total: 4, succeeded: 2, failed: 1, skipped: 1, status: 'partial', code: 1 },
16 'partial-zero': { total: 3, succeeded: 2, failed: 1, skipped: 0, status: 'partial', code: 0 },
17 empty: { total: 0, succeeded: 0, failed: 0, skipped: 0, status: 'completed', code: 0 },
18 unknown: { total: null, succeeded: 3, failed: 0, skipped: 0, status: 'completed', code: 0 },
19 'fail-fast': { total: 3, succeeded: 1, failed: 1, skipped: 0, status: 'failed', code: 1 },
20 cancelled: { total: 3, succeeded: 1, failed: 0, skipped: 0, status: 'cancelled', code: 130 },
21 live: { total: 3, succeeded: 3, failed: 0, skipped: 0, status: 'completed', code: 0 },
22};
23
24function accounting(record) {
25 for (const key of ['processed', 'succeeded', 'failed', 'skipped']) {
26 assert.ok(Number.isInteger(record[key]) && record[key] >= 0, key);
27 }
28 assert.equal(record.processed, record.succeeded + record.failed + record.skipped);
29}
30
31function verify(stdout, stderr, code, scenario) {
32 const expected = cases[scenario];
33 const events = stderr.trim().split(/\r?\n/).filter(Boolean).map(line => JSON.parse(line));
34 assert.equal(events[0].event, 'start');
35 assert.equal(events.at(-1).event, 'summary');
36 assert.equal(events.filter(record => record.event === 'start').length, 1);
37 assert.equal(events.filter(record => record.event === 'summary').length, 1);
38 let previous = { processed: 0, succeeded: 0, failed: 0, skipped: 0, elapsed_ms: 0 };
39 for (const record of events) {
40 accounting(record);
41 assert.equal(record.total, expected.total);
42 for (const key of ['processed', 'succeeded', 'failed', 'skipped', 'elapsed_ms']) {
43 assert.ok(record[key] >= previous[key], `non-monotonic ${key}`);
44 }
45 if (expected.total === null) {
46 for (const key of ['percentage', 'eta', 'remaining']) assert.ok(!(key in record));
47 }
48 previous = record;
49 }
50 const summary = events.at(-1);
51 for (const key of ['succeeded', 'failed', 'skipped', 'status']) assert.equal(summary[key], expected[key]);
52 assert.equal(summary.exit_code, expected.code);
53 assert.equal(code, expected.code);
54 assert.equal(summary.unprocessed, expected.total === null ? null : expected.total - summary.processed);
55 const failures = events.filter(record => record.event === 'item-failed');
56 assert.equal(failures.length, expected.failed);
57 for (const failure of failures) {
58 assert.equal(failure.item_id, ['fail-fast', 'partial-zero'].includes(scenario) ? 'item-2' : 'item-3');
59 assert.equal(failure.reason, 'fixture-rejected');
60 assert.equal(failure.will_continue, scenario !== 'fail-fast');
61 }
62 if (scenario === 'empty') assert.equal(events.filter(record => record.event === 'progress').length, 0);
63 if (['normal', 'unknown', 'mixed', 'partial-zero', 'live'].includes(scenario)) assert.ok(events.some(record => record.event === 'progress'));
64 if (scenario === 'cancelled') {
65 assert.equal(stdout, '');
66 } else {
67 const result = JSON.parse(stdout);
68 const ids = scenario === 'mixed' ? [1, 4] : scenario === 'partial-zero' ? [1, 3] : scenario === 'fail-fast' ? [1] : scenario === 'empty' ? [] : [1, 2, 3];
69 assert.deepEqual(result.results, ids.map(index => ({ id: `item-${index}`, value: index * 2 })));
70 }
71}
72
73async function verifyLive(runtime) {
74 const child = spawn(runtime.command, [...runtime.args, 'live'], { stdio: ['pipe', 'pipe', 'pipe'] });
75 let stdout = '';
76 let stderr = '';
77 let pending = '';
78 let released = false;
79 let failure;
80 const timeout = setTimeout(() => {
81 failure = new Error(`${runtime.name}: no live notification before watchdog`);
82 child.kill();
83 }, 15000);
84 child.stdout.setEncoding('utf8');
85 child.stderr.setEncoding('utf8');
86 child.stdin.on('error', error => { failure ??= error; });
87 child.stdout.on('data', chunk => { stdout += chunk; });
88 child.stderr.on('data', chunk => {
89 stderr += chunk;
90 pending += chunk;
91 let newline;
92 while ((newline = pending.indexOf('\n')) !== -1) {
93 const line = pending.slice(0, newline).trim();
94 pending = pending.slice(newline + 1);
95 try {
96 const record = JSON.parse(line);
97 if (record.event === 'progress' && !released) {
98 assert.equal(child.exitCode, null);
99 assert.equal(child.signalCode, null);
100 assert.equal(record.processed, 1);
101 released = true;
102 child.stdin.end('\n');
103 }
104 } catch (error) {
105 failure ??= error;
106 child.kill();
107 }
108 }
109 });
110 const code = await new Promise((resolve, reject) => {
111 child.once('error', reject);
112 child.once('close', resolve);
113 }).finally(() => clearTimeout(timeout));
114 if (failure) throw failure;
115 assert.ok(released, `${runtime.name}: notification not observed before exit`);
116 verify(stdout, stderr, code, 'live');
117}
118
119assert.throws(() => accounting({ processed: 2, succeeded: 1, failed: 0, skipped: 0 }), assert.AssertionError);
120console.log('PASS negative control: corrupted accounting rejected');
121let passed = 0;
122for (const runtime of runtimes) {
123 for (const scenario of Object.keys(cases).filter(name => name !== 'live')) {
124 const result = spawnSync(runtime.command, [...runtime.args, scenario], { encoding: 'utf8', timeout: 15000 });
125 if (result.error) throw result.error;
126 assert.equal(result.signal, null);
127 verify(result.stdout, result.stderr, result.status, scenario);
128 console.log(`PASS ${runtime.name} ${scenario}`);
129 passed++;
130 }
131 await verifyLive(runtime);
132 console.log(`PASS ${runtime.name} live output received before release and exit`);
133 passed++;
134}
135console.log(`BATCH_OUTPUT_CONTRACT_PASSED scenarios=${passed}`);