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