返回 CodeWhale
native-host-receipt.test.js
根目录 / npm / codewhale / test / native-host-receipt.test.js
1 'use strict';
2 const { test } = require('node:test');
3 const assert = require('node:assert/strict');
4 const fs = require('node:fs');
5 const path = require('node:path');
6 const os = require('node:os');
7 const crypto = require('node:crypto');
8 const { spawnSync } = require('node:child_process');
9 const script = path.resolve(__dirname, '../../../scripts/compiled-host-native-receipt.py');
10 const identity = 'extension_host::tests::compiled_native_host_cannot_read_secrets_or_write_outside_its_data_dir';
11 const memoryIdentity = 'extension_host::tests::compiled_native_host_memory_cap_is_enforced';
12 const digest = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex');
13 const escape = (text) => text.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
14 const marker = `compiled-native-containment=passed platform=${{darwin:'macos',win32:'windows',linux:'linux'}[process.platform]} arch=${{x64:'x86_64',arm64:'aarch64'}[process.arch]}`;
15 const memoryMarker = marker.replace('compiled-native-containment=', 'compiled-native-memory=');
16 const testcase = (name, body = '') => `<testcase name="${name}" time="0.2">${body}</testcase>`;
17 function fixture(t, body = `<system-err>${escape(marker)}\n</system-err>`) {
18 const root = fs.mkdtempSync(path.join(os.tmpdir(), 'native-receipt-fixture-'));
19 t.after(() => fs.rmSync(root, { recursive: true, force: true }));
20 const host = path.join(root, 'codewhale-extension-host' + (process.platform === 'win32' ? '.exe' : ''));
21 const bundle = path.join(root, 'bundle.mjs'), bun = path.join(root, 'bun-runtime'), junit = path.join(root, 'junit.xml');
22 fs.writeFileSync(host, 'synthetic image; no executable or runtime proof');
23 fs.writeFileSync(bundle, 'synthetic canonical bundle'); fs.writeFileSync(bun, 'synthetic compiler');
24 fs.writeFileSync(junit, `<testsuites><testsuite>${testcase(identity, body)}${testcase(memoryIdentity, `<system-err>${escape(memoryMarker)}\n</system-err>`)}</testsuite></testsuites>`);
25 const output = path.join(root, 'receipt');
26 const args = [script, '--junit', junit, '--compiled-image', host, '--bundle', bundle, '--bun', bun,
27 '--source-sha', '1'.repeat(40), '--image-platform', process.platform, '--image-arch', process.arch, '--host-sha256', digest(fs.readFileSync(host)),
28 '--bundle-sha256', digest(fs.readFileSync(bundle)), '--runtime-sha256', digest(fs.readFileSync(bun)), '--output', output];
29 return { root, host, bundle, bun, junit, output, args,
30 run: () => spawnSync('python3', args, { encoding: 'utf8' }) };
31 }
32 function windowsCases(omit = '') {
33 const names = ['windows_native_lpac_node_owner_boundary', 'windows_native_lpac_bun_owner_boundary',
34 'windows_native_lpac_compiled_owner_boundary', 'windows_native_profiles_are_distinct_and_acl_grant_refuses_junctions',
35 'windows_argv_and_environment_keep_exact_values_and_reject_nul',
36 'windows_profile_retirement_removes_only_its_grants_and_inherited_data_on_restarts',
37 'windows_profile_directory_budget_and_recorded_identity_refuse_before_overwrite'];
38 return names.filter(name => name !== omit).map(name => testcase(`extension_host::windows::tests::${name}`)).join('');
39 }
40 function includeWindows(f) {
41 if (process.platform === 'win32') fs.writeFileSync(f.junit,
42 fs.readFileSync(f.junit, 'utf8').replace('</testsuite>', windowsCases() + '</testsuite>'));
43 }
44 test('receipt exports exact bytes and real report fields; synthetic fixture is not runtime proof', t => {
45 const f = fixture(t); includeWindows(f);
46 const result = f.run(); assert.equal(result.status, 0, result.stderr);
47 const receipt = JSON.parse(fs.readFileSync(path.join(f.output, 'native-receipt.json'), 'utf8'));
48 assert.equal(receipt.scope, 'native-compiled-host'); assert.equal(receipt.platform, process.platform); assert.equal(receipt.arch, process.arch);
49 assert.equal(receipt.passed, process.platform === 'win32' ? 9 : 2); assert.equal(receipt.failed, 0); assert.equal(receipt.skipped, 0);
50 assert.deepEqual(fs.readFileSync(path.join(f.output, path.basename(f.host))), fs.readFileSync(f.host));
51 assert.equal(receipt.host_sha256, digest(fs.readFileSync(f.host)));
52 assert.equal(receipt.log_sha256, digest(fs.readFileSync(path.join(f.output, receipt.log))));
53 if (process.platform === 'linux') assert.equal(receipt.libc.family, 'glibc');
54 });
55 test('receipt refuses missing Native testcase', t => {
56 const f = fixture(t); fs.writeFileSync(f.junit, '<testsuites/>');
57 const r = f.run(); assert.notEqual(r.status, 0); assert.match(r.stderr, /required Native testcase is missing/); assert.ok(!fs.existsSync(f.output));
58 });
59 test('receipt refuses an ordinary successful early return without complete Native marker', t => {
60 const f = fixture(t, ''); includeWindows(f);
61 const r = f.run(); assert.notEqual(r.status, 0); assert.match(r.stderr, /completion marker is missing/);
62 });
63 for (const element of ['failure', 'error', 'skipped', 'flakyFailure']) {
64 test(`receipt refuses ${element} rather than upgrading it to a pass`, t => {
65 const f = fixture(t, `<${element}/><system-err>${escape(marker)}</system-err>`); includeWindows(f);
66 const r = f.run(); assert.notEqual(r.status, 0); assert.match(r.stderr, /did not pass once/);
67 });
68 }
69 test('receipt refuses duplicate/retried Native results', t => {
70 const f = fixture(t); includeWindows(f);
71 fs.writeFileSync(f.junit, fs.readFileSync(f.junit, 'utf8').replace('</testsuite>', testcase(identity) + '</testsuite>'));
72 const r = f.run(); assert.notEqual(r.status, 0); assert.match(r.stderr, /duplicate\/retried/);
73 });
74 test('receipt refuses compiled image mutated after its Native invocation', t => {
75 const f = fixture(t); includeWindows(f); fs.appendFileSync(f.host, 'changed');
76 const r = f.run(); assert.notEqual(r.status, 0); assert.match(r.stderr, /qualified input changed/);
77 });
78 test('receipt refuses prior output directory and noncanonical filename', t => {
79 const f = fixture(t); includeWindows(f); fs.mkdirSync(f.output);
80 assert.notEqual(f.run().status, 0);
81 fs.rmSync(f.output, { recursive: true });
82 const other = path.join(f.root, 'host-imposter'); fs.renameSync(f.host, other);
83 f.args[f.args.indexOf('--compiled-image') + 1] = other;
84 const r = f.run(); assert.notEqual(r.status, 0); assert.match(r.stderr, /canonical image basename/);
85 });
86 test('Windows unit extraction requires all actual LPAC/reparse consumers', t => {
87 const f = fixture(t);
88 const invoke = (report) => spawnSync('python3', ['-c',
89 'import runpy,sys; m=runpy.run_path(sys.argv[1]); print(m["extract"](sys.stdin.buffer.read(),"win32","x64")[0])', script],
90 { input: report, encoding: 'utf8' });
91 const compiled = testcase(identity, '<system-err>compiled-native-containment=passed platform=windows arch=x86_64\n</system-err>');
92 const compiledMemory = testcase(memoryIdentity, '<system-err>compiled-native-memory=passed platform=windows arch=x86_64\n</system-err>');
93 const r = invoke(`<testsuites><testsuite>${compiled}${compiledMemory}${windowsCases()}</testsuite></testsuites>`);
94 assert.equal(r.status, 0, r.stderr); assert.equal(r.stdout.trim(), '9');
95 const missing = invoke(`<testsuites><testsuite>${compiled}${compiledMemory}${windowsCases('windows_native_lpac_node_owner_boundary')}</testsuite></testsuites>`);
96 assert.notEqual(missing.status, 0); assert.match(missing.stderr, /required Native testcase is missing/);
97 });
98 test('receipt refuses XML entities and another OS completion marker', t => {
99 const f = fixture(t); fs.writeFileSync(f.junit, '<!DOCTYPE x><testsuites/>');
100 assert.match(f.run().stderr, /invalid or oversized/);
101 const r = spawnSync('python3', ['-c', 'import runpy,sys; m=runpy.run_path(sys.argv[1]); m["extract"](sys.stdin.buffer.read(),"win32","x64")', script],
102 { input: `<testsuites><testsuite>${testcase(identity, '<system-err>compiled-native-containment=passed platform=macos arch=x86_64\n</system-err>')}${testcase(memoryIdentity, '<system-err>compiled-native-memory=passed platform=windows arch=x86_64\n</system-err>')}${windowsCases()}</testsuite></testsuites>`, encoding: 'utf8' });
103 assert.notEqual(r.status, 0); assert.match(r.stderr, /another native target/);
104 });
105
106 test('receipt refuses architecture/emulation label transfer even with matching image bytes', t => {
107 const f = fixture(t); includeWindows(f);
108 f.args[f.args.indexOf('--image-arch') + 1] = process.arch === 'arm64' ? 'x64' : 'arm64';
109 const r = f.run(); assert.notEqual(r.status, 0); assert.match(r.stderr, /identity differs from native runner/);
110 });
111
111 lines JAVASCRIPT