package-matrix.test.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import assert from 'node:assert/strict';
  2. import fs from 'node:fs';
  3. import os from 'node:os';
  4. import path from 'node:path';
  5. import { test } from 'node:test';
  6. import { spawnSync } from 'node:child_process';
  7. import { fileURLToPath } from 'node:url';
  8. import { verifyPlatformBinaries } from '../scripts/repo.mjs';
  9. test('the real Landlock subpath imports without platform packages or dlopen, while the root is unexported', { timeout: 120_000 }, (t) => {
  10. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'system-landlock-entry-'));
  11. t.after(() => fs.rmSync(dir, { recursive: true, force: true }), { timeout: 120_000 });
  12. const entry = fileURLToPath(new URL('../packages/entry/', import.meta.url));
  13. const installed = path.join(dir, 'node_modules', '@deepseek-ai', 'node-addon-system');
  14. fs.mkdirSync(installed, { recursive: true });
  15. fs.copyFileSync(path.join(entry, 'package.json'), path.join(installed, 'package.json'));
  16. // Only the real entry payload is present; no platform package or addon is copied.
  17. fs.cpSync(path.join(entry, 'lib'), path.join(installed, 'lib'), { recursive: true });
  18. const manifest = JSON.parse(fs.readFileSync(path.join(installed, 'package.json'), 'utf8'));
  19. assert.equal(manifest.main, undefined);
  20. assert.equal(manifest.types, undefined);
  21. const result = spawnSync(process.execPath, ['--no-addons', '--input-type=module', '--eval', `
  22. import assert from 'node:assert/strict';
  23. import { createRequire } from 'node:module';
  24. const originalDlopen = process.dlopen;
  25. let dlopenCalls = 0;
  26. try {
  27. process.dlopen = () => {
  28. dlopenCalls++;
  29. throw new Error('Landlock import attempted dlopen');
  30. };
  31. const api = await import('@deepseek-ai/node-addon-system/landlock-run');
  32. assert.equal(api.LAUNCHER_BIN, 'landlock-run');
  33. assert.deepEqual(api.grantArgs({}), []);
  34. assert.equal(dlopenCalls, 0);
  35. await assert.rejects(import('@deepseek-ai/node-addon-system'), {
  36. code: 'ERR_PACKAGE_PATH_NOT_EXPORTED',
  37. });
  38. assert.throws(() => createRequire(import.meta.url).resolve('@deepseek-ai/node-addon-system'), {
  39. code: 'ERR_PACKAGE_PATH_NOT_EXPORTED',
  40. });
  41. } finally {
  42. process.dlopen = originalDlopen;
  43. }
  44. `], {
  45. cwd: dir,
  46. encoding: 'utf8',
  47. timeout: 120_000,
  48. env: Object.fromEntries(Object.entries(process.env)
  49. .filter(([key]) => !/KEY|TOKEN|SECRET|PASSWORD|^NODE_PATH$/i.test(key))),
  50. });
  51. assert.equal(result.error, undefined);
  52. assert.equal(result.signal, null, result.stderr);
  53. assert.equal(result.status, 0, result.stderr);
  54. });
  55. // These minimal headers exercise format rejection, not executable behavior.
  56. // flock.test.js and packed-install verification execute the real addon.
  57. function fixture(t, { platform = 'linux', arch = 'x64', kind = 'node-api' } = {}) {
  58. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'system-package-'));
  59. t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
  60. const executable = kind === 'static-musl';
  61. const binary = executable
  62. ? { tool: 'landlock-run', kind, path: 'bin/landlock-run' }
  63. : { tool: 'flock', kind, napi: 8, ...(platform === 'linux' ? { libc: 'glibc' } : {}), path: 'bin/system.node' };
  64. const spec = { platform: `${platform}-${arch}`, binaries: [binary] };
  65. const manifest = { name: 'fixture', os: [platform], cpu: [arch] };
  66. const bytes = Buffer.alloc(256);
  67. if (platform === 'linux') {
  68. bytes.writeUInt32LE(0x464c457f, 0);
  69. bytes[4] = 2;
  70. bytes[5] = 1;
  71. bytes.writeUInt16LE(executable ? 2 : 3, 16);
  72. bytes.writeUInt16LE(arch === 'x64' ? 62 : 183, 18);
  73. } else {
  74. bytes.writeUInt32LE(0xfeedfacf, 0);
  75. bytes.writeUInt32LE(arch === 'x64' ? 0x01000007 : 0x0100000c, 4);
  76. bytes.writeUInt32LE(8, 12);
  77. }
  78. bytes.write('napi_register_module_v1\0node_api_module_get_api_version_v1', 64);
  79. const file = path.join(dir, binary.path);
  80. fs.mkdirSync(path.dirname(file), { recursive: true });
  81. fs.writeFileSync(file, bytes, { mode: executable ? 0o755 : 0o644 });
  82. const save = () => {
  83. fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify(manifest));
  84. fs.writeFileSync(path.join(dir, 'prebuilds.json'), JSON.stringify(spec));
  85. };
  86. save();
  87. return { dir, file, bytes, binary, spec, manifest, save };
  88. }
  89. for (const platform of ['linux', 'darwin']) {
  90. for (const arch of ['x64', 'arm64']) {
  91. test(`accepts ${platform}-${arch} addon metadata and header`, (t) => {
  92. assert.equal(verifyPlatformBinaries(fixture(t, { platform, arch }).dir).count, 1);
  93. });
  94. }
  95. }
  96. test('accepts the Linux static launcher format', (t) => {
  97. assert.equal(verifyPlatformBinaries(fixture(t, { kind: 'static-musl' }).dir).count, 1);
  98. });
  99. for (const [name, change, expected] of [
  100. ['unknown platform', (f) => { f.manifest.os = ['win32']; }, /os\/cpu/],
  101. ['mismatched platform', (f) => { f.spec.platform = 'linux-arm64'; }, /disagrees/],
  102. ['path outside bin', (f) => { f.binary.path = '../system.node'; }, /inside bin/],
  103. ['duplicate binary', (f) => { f.spec.binaries.push({ ...f.binary }); }, /duplicate/],
  104. ['unknown kind', (f) => { f.binary.kind = 'unknown'; }, /kind\/tool\/NAPI/],
  105. ['wrong NAPI version', (f) => { f.binary.napi = 9; }, /kind\/tool\/NAPI/],
  106. ['missing Linux libc', (f) => { delete f.binary.libc; }, /declare glibc or musl/],
  107. ['missing payload', (f) => { fs.unlinkSync(f.file); }, /missing/],
  108. ['wrong ELF architecture', (f) => { f.bytes.writeUInt16LE(183, 18); fs.writeFileSync(f.file, f.bytes); }, /ELF architecture/],
  109. ['wrong ELF type', (f) => { f.bytes.writeUInt16LE(2, 16); fs.writeFileSync(f.file, f.bytes); }, /ELF file type/],
  110. ['truncated ELF', (f) => { fs.writeFileSync(f.file, Buffer.alloc(8)); }, /ELF64/],
  111. ['missing NAPI exports', (f) => { f.bytes.fill(0, 64); fs.writeFileSync(f.file, f.bytes); }, /Node-API entry points/],
  112. ['undeclared nested file', (f) => { fs.mkdirSync(path.join(f.dir, 'bin/extra')); fs.writeFileSync(path.join(f.dir, 'bin/extra/other.node'), 'x'); }, /undeclared/],
  113. ]) {
  114. test(`rejects ${name}`, (t) => {
  115. const f = fixture(t);
  116. change(f);
  117. f.save();
  118. assert.throws(() => verifyPlatformBinaries(f.dir), expected);
  119. });
  120. }
  121. test('rejects Linux libc metadata on macOS', (t) => {
  122. const f = fixture(t, { platform: 'darwin' });
  123. f.binary.libc = 'musl';
  124. f.save();
  125. assert.throws(() => verifyPlatformBinaries(f.dir), /must not declare/);
  126. });
  127. for (const [offset, value] of [[0, 0], [4, 0], [12, 2]]) {
  128. test(`rejects invalid Mach-O field at ${offset}`, (t) => {
  129. const f = fixture(t, { platform: 'darwin' });
  130. f.bytes.writeUInt32LE(value, offset);
  131. fs.writeFileSync(f.file, f.bytes);
  132. assert.throws(() => verifyPlatformBinaries(f.dir), /Mach-O/);
  133. });
  134. }
  135. test('rejects a launcher whose executable bit was lost', { skip: process.platform === 'win32' }, (t) => {
  136. const f = fixture(t, { kind: 'static-musl' });
  137. fs.chmodSync(f.file, 0o644);
  138. assert.throws(() => verifyPlatformBinaries(f.dir), /not executable/);
  139. });
  140. test('rejects a symbolic-link payload', { skip: process.platform === 'win32' }, (t) => {
  141. const f = fixture(t);
  142. fs.renameSync(f.file, f.file + '.target');
  143. fs.symlinkSync(f.file + '.target', f.file);
  144. assert.throws(() => verifyPlatformBinaries(f.dir), /not a regular file/);
  145. });
  146. test('entry prepack rejects a missing exported flock file even when the Landlock entry exists', (t) => {
  147. const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'system-entry-'));
  148. t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
  149. fs.mkdirSync(path.join(dir, 'lib'));
  150. fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({
  151. name: 'entry-fixture',
  152. exports: {
  153. './landlock-run': { types: './lib/index.d.ts', default: './lib/index.js' },
  154. './flock': { types: './lib/flock.d.ts', default: './lib/flock.js' },
  155. },
  156. }));
  157. for (const file of ['index.js', 'index.d.ts', 'flock.d.ts']) fs.writeFileSync(path.join(dir, 'lib', file), '');
  158. const script = fileURLToPath(new URL('../scripts/verify-entry-lib.mjs', import.meta.url));
  159. const options = {
  160. cwd: dir,
  161. encoding: 'utf8',
  162. timeout: 120_000,
  163. env: Object.fromEntries(Object.entries(process.env).filter(([key]) => !/KEY|TOKEN|SECRET|PASSWORD/i.test(key))),
  164. };
  165. const missing = spawnSync(process.execPath, [script], options);
  166. assert.equal(missing.error, undefined);
  167. assert.equal(missing.signal, null);
  168. assert.equal(missing.status, 1);
  169. assert.match(missing.stderr, /lib\/flock\.js/);
  170. fs.writeFileSync(path.join(dir, 'lib/flock.js'), '');
  171. const complete = spawnSync(process.execPath, [script], options);
  172. assert.equal(complete.error, undefined);
  173. assert.equal(complete.signal, null);
  174. assert.equal(complete.status, 0, complete.stderr);
  175. });