import { spawnSync, spawn, Glob } from "bun"; import { beforeAll, describe, expect, it } from "bun:test"; import { bunEnv, bunExe, isBroken, isCI, isIntelMacOS, isMusl, isWindows } from "harness"; import { join, dirname } from "path"; import os from "node:os"; const jsNativeApiRoot = join(__dirname, "node-napi-tests", "test", "js-native-api"); const nodeApiRoot = join(__dirname, "node-napi-tests", "test", "node-api"); const jsNativeApiTests = Array.from(new Glob("**/*.js").scanSync(jsNativeApiRoot)); const nodeApiTests = Array.from(new Glob("**/*.js").scanSync(nodeApiRoot)); // These js-native-api tests are known to fail and will be fixed in later PRs let failingJsNativeApiTests: string[] = [ // We skip certain parts of test_string/test.js because we don't support creating empty external // strings. We don't skip the entire thing because the other tests are useful to check. // "test_string/test.js", ]; // These are the tests from node-api that failed as of commit 83f536f4d, except for those that // passed in Bun v1.1.34. It'll take some time to get all these to work, as we've been focusing more // on js-native-api tests so far, and we don't want these tests to pollute CI. But we do want to // know if we regressed any of the other tests. let failingNodeApiTests = [ "test_uv_threadpool_size/test.js", "test_uv_threadpool_size/node-options.js", "test_uv_loop/test.js", "test_callback_scope/test-resolve-async.js", "test_callback_scope/test-async-hooks.js", "test_fatal/test.js", "test_fatal/test2.js", "test_fatal/test_threads.js", "test_threadsafe_function/test.js", "test_threadsafe_function/test_legacy_uncaught_exception.js", "test_worker_buffer_callback/test.js", "test_worker_buffer_callback/test-free-called.js", // TODO(@heimskr) "test_make_callback_recurse/test.js", "test_buffer/test.js", "test_instance_data/test.js", "test_make_callback/test-async-hooks.js", "test_async_context/test.js", "test_async_context/test-gcable.js", "test_async_context/test-gcable-callback.js", "test_async_cleanup_hook/test.js", "test_async/test.js", "test_async/test-uncaught.js", "test_async/test-async-hooks.js", "test_general/test.js", "test_env_teardown_gc/test.js", "test_worker_terminate/test.js", ]; if (isBroken && isIntelMacOS) { // TODO(@190n) // these are flaky on Intel Mac failingJsNativeApiTests.push("test_reference/test.js"); failingNodeApiTests.push("test_reference_by_node_api_version/test.js"); } if (isWindows) { if (isBroken) { failingNodeApiTests.push("test_callback_scope/test.js"); // TODO: remove once #12827 is fixed } for (const i in failingJsNativeApiTests) { failingJsNativeApiTests[i] = failingJsNativeApiTests[i].replaceAll("/", "\\"); } for (const i in failingNodeApiTests) { failingNodeApiTests[i] = failingNodeApiTests[i].replaceAll("/", "\\"); } } if (isMusl) { failingNodeApiTests = nodeApiTests; failingJsNativeApiTests = jsNativeApiTests; } for (const t of failingJsNativeApiTests) { if (!jsNativeApiTests.includes(t)) { console.error(`attempt to skip ${t} which is not a real js-native-api test`); process.exit(1); } } for (const t of failingNodeApiTests) { if (!nodeApiTests.includes(t)) { console.error(`attempt to skip ${t} which is not a real node-api test`); process.exit(1); } } beforeAll(async () => { const directories = jsNativeApiTests .filter(t => !failingJsNativeApiTests.includes(t)) .map(t => join(jsNativeApiRoot, t)) .concat(nodeApiTests.filter(t => !failingNodeApiTests.includes(t)).map(t => join(nodeApiRoot, t))) .map(t => dirname(t)); const uniqueDirectories = Array.from(new Set(directories)); async function buildOne(dir: string) { const child = spawn({ cmd: [bunExe(), "x", "node-gyp", "rebuild", "--debug"], cwd: dir, stderr: "pipe", stdout: "ignore", stdin: "inherit", env: { ...bunEnv, npm_config_target: "v23.2.0", // on linux CI, node-gyp will default to g++ and the version installed there is very old, // so we make it use clang instead ...(process.platform == "linux" && isCI ? { "CC": "/usr/lib/llvm-19/bin/clang", CXX: "/usr/lib/llvm-19/bin/clang++" } : {}), }, }); await child.exited; if (child.exitCode !== 0) { const stderr = await new Response(child.stderr).text(); console.error(`node-gyp rebuild in ${dir} failed:\n${stderr}`); console.error("bailing out!"); process.exit(1); } } async function worker() { while (uniqueDirectories.length > 0) { const dir = uniqueDirectories.pop(); await buildOne(dir!); } } const parallelism = Math.min(8, os.cpus().length, 1 /* TODO(@heimskr): remove */); const jobs: Promise[] = []; for (let i = 0; i < parallelism; i++) { jobs.push(worker()); } await Promise.all(jobs); }, 600000); describe.each([ ["js-native-api", jsNativeApiTests, jsNativeApiRoot, failingJsNativeApiTests], ["node-api", nodeApiTests, nodeApiRoot, failingNodeApiTests], ])("%s tests", (_name, tests, root, failing) => { describe.each(tests)("%s", test => { it.skipIf(failing.includes(test))( "passes", () => { const result = spawnSync({ cmd: [bunExe(), "run", test], cwd: root, stderr: "inherit", stdout: "ignore", stdin: "inherit", env: bunEnv, }); expect(result.success).toBeTrue(); expect(result.exitCode).toBe(0); }, 60000, // timeout ); }); });