| 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('&', '&').replaceAll('<', '<').replaceAll('"', '"'); |
| 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 |