返回 CodeWhale
compiled-host-native-receipt.test.js
根目录 / scripts / compiled-host-native-receipt.test.js
1 // Synthetic JUnit exercises exporter refusal and target contracts only.
2 // These source fixtures never qualify a runtime, image, OS or delivery target.
3 const test = require('node:test');
4 const assert = require('node:assert/strict');
5 const path = require('node:path');
6 const { spawnSync } = require('node:child_process');
7 const exporter = path.join(__dirname, 'compiled-host-native-receipt.py');
8 const containment = 'extension_host::tests::compiled_native_host_cannot_read_secrets_or_write_outside_its_data_dir';
9 const memory = 'extension_host::tests::compiled_native_host_memory_cap_is_enforced';
10 const windows = [
11 'windows_native_lpac_node_owner_boundary',
12 'windows_native_lpac_bun_owner_boundary',
13 'windows_native_lpac_compiled_owner_boundary',
14 'windows_native_profiles_are_distinct_and_acl_grant_refuses_junctions',
15 'windows_argv_and_environment_keep_exact_values_and_reject_nul',
16 'windows_profile_retirement_removes_only_its_grants_and_inherited_data_on_restarts',
17 'windows_profile_directory_budget_and_recorded_identity_refuse_before_overwrite',
18 ].map(name => 'extension_host::windows::tests::' + name);
19 const rustOS = { linux: 'linux', darwin: 'macos', win32: 'windows' };
20 const rustArch = { x64: 'x86_64', arm64: 'aarch64' };
21 const xml = value => value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('"', '&quot;');
22 function records(platform = 'linux', arch = 'x64') {
23 return [containment, memory, ...(platform === 'win32' ? windows : [])].map(name => ({
24 name, output: name === containment || name === memory
25 ? `compiled-native-${name === containment ? 'containment' : 'memory'}=passed platform=${rustOS[platform]} arch=${rustArch[arch]}` : '',
26 }));
27 }
28 function report(rows) {
29 return `<testsuites><testsuite>${rows.map(row => `<testcase name="${xml(row.name)}" time="0.1">${row.status ? `<${row.status}/>` : ''}<system-out>${xml(row.output)}</system-out></testcase>`).join('')}</testsuite></testsuites>`;
30 }
31 function extract(rows, platform = 'linux', arch = 'x64', raw) {
32 const source = `import sys, json, importlib.util\nsys.dont_write_bytecode=True\nspec=importlib.util.spec_from_file_location('native_receipt',sys.argv[1])\nmodule=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)\ndata=json.load(sys.stdin)\ntry:\n passed,log=module.extract(data['report'].encode(),data['platform'],data['arch'])\n print(json.dumps({'passed':passed,'log':log.decode()}))\nexcept Exception as error:\n print(str(error),file=sys.stderr);sys.exit(1)\n`;
33 return spawnSync(process.env.PYTHON || 'python3', ['-c', source, exporter], {
34 input: JSON.stringify({ report: raw ?? report(rows), platform, arch }), encoding: 'utf8', timeout: 10_000,
35 });
36 }
37 for (const platform of ['linux', 'darwin', 'win32']) test(`${platform} export requires actual matching containment and memory cases`, () => {
38 for (const arch of ['x64', 'arm64']) {
39 const rows = records(platform, arch), result = extract(rows, platform, arch);
40 assert.equal(result.status, 0, result.stderr);
41 const receipt = JSON.parse(result.stdout);
42 assert.equal(receipt.passed, platform === 'win32' ? 9 : 2);
43 assert.match(receipt.log, /compiled-native-containment=passed/);
44 assert.match(receipt.log, /compiled-native-memory=passed/);
45 assert.equal(extract(rows.filter(row => row.name !== memory), platform, arch).status, 1);
46 const noMarker = structuredClone(rows); noMarker.find(row => row.name === memory).output = '';
47 assert.equal(extract(noMarker, platform, arch).status, 1);
48 }
49 });
50 test('a marker from another platform or architecture cannot qualify the image', () => {
51 const rows = records();
52 for (const output of ['compiled-native-memory=passed platform=windows arch=x86_64', 'compiled-native-memory=passed platform=linux arch=aarch64', 'compiled-native-memory=passed platform=linux arch=x86_64 trailing']) {
53 rows.find(row => row.name === memory).output = output;
54 assert.equal(extract(rows).status, 1);
55 }
56 });
57 test('failed, skipped or retried required scenarios are never hidden by later success', () => {
58 for (const status of ['failure', 'error', 'skipped', 'flakyFailure', 'flakyError', 'rerunFailure', 'rerunError']) {
59 const rows = records(); rows.find(row => row.name === memory).status = status;
60 assert.equal(extract(rows).status, 1, status);
61 }
62 const rows = records(); rows.push(rows[1]);
63 assert.equal(extract(rows).status, 1);
64 });
65 test('Windows still requires each independent LPAC case alongside compiled memory', () => {
66 for (const missing of windows) {
67 const result = extract(records('win32').filter(row => row.name !== missing), 'win32');
68 assert.equal(result.status, 1); assert.match(result.stderr, /required Native testcase is missing/);
69 }
70 });
71 test('external entities refuse before parsing or receipt output', () => {
72 assert.equal(extract([], 'linux', 'x64', '<!DOCTYPE x><testsuites/>').status, 1);
73 assert.equal(extract([], 'linux', 'x64', '<!ENTITY x><testsuites/>').status, 1);
74 // The existing 64MiB report limit is checked inside the exporter. Do not
75 // allocate that entire fixture in every routine Node source test.
76 });
77
78 test('bounded logs refuse oversized captured output instead of trimming evidence', () => {
79 const rows = records(); rows[1].output += '\n' + 'x'.repeat(64 * 1024);
80 const result = extract(rows);
81 assert.equal(result.status, 1); assert.match(result.stderr, /log exceeds 64 KiB/);
82 });
83 test('unrelated successful cases cannot replace either required producing scenario', () => {
84 const rows = records().filter(row => row.name !== memory);
85 rows.push({ name: 'extension_host::tests::memory_cap_stops_a_bun_host', output: records()[1].output });
86 const result = extract(rows);
87 assert.equal(result.status, 1); assert.match(result.stderr, /required Native testcase is missing/);
88 });
89
89 lines JAVASCRIPT