From a9ba8c06f657f51db7251da1124801b03a6b3c1e Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 8 Oct 2026 16:40:00 +0800 Subject: [PATCH] Run PGlite unit tests in separate processes on Linux Node 24 on Linux can abort with a V8 fatal check in ThreadIsolation::UnregisterWasmAllocation while it frees PGlite's Wasm code after a file finishes in a worker thread. The abort kills the shared Vitest process, so CI shows no summary and no failing test. The API integration project already uses forks on Linux for this reason. Move the five PGlite files out of platform-unit into a sibling platform-unit-pglite project with the same Linux forks pool, and keep the other platform unit files on threads. CI's --project 'platform-unit*' selects both projects, and a config test keeps any new PGlite unit file in the new project. Co-Authored-By: Claude Opus 5.5 (1M context) --- TESTING.md | 6 ++--- packages/void/test/unit/test-projects.test.ts | 20 ++++++++++++++++ test/affected.mjs | 4 +++- vite.config.ts | 23 ++++++++++++++++++- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/TESTING.md b/TESTING.md index 122ee50b8..aa936de85 100644 --- a/TESTING.md +++ b/TESTING.md @@ -91,9 +91,9 @@ Subprocess and provisioning-lock unit tests start alongside the platform project process deadlines can overlap database work. The remaining SDK unit and integration files then share a scheduling phase. `vp run test:unit` includes both `unit` and `cli-unit`; CI does the same. Unit files and most API integration files use isolated worker threads to reduce startup time. On Linux, -API integration files use forks to isolate PGlite's WebAssembly JIT allocations after native Node 24 -cleanup crashes in shared worker processes. API tests that change the process timezone also use forks, -because threads cannot change Node's timezone. Select every +API integration files and the PGlite unit files (`platform-unit-pglite`) use forks to isolate PGlite's +WebAssembly JIT allocations after native Node 24 cleanup crashes in shared worker processes. API tests +that change the process timezone also use forks, because threads cannot change Node's timezone. Select every API file with `vp test run --project 'platform-api-integration*'`. SDK integration files use forks. This keeps database engines from competing with all framework compilers at startup. Keep module isolation enabled: these suites mock globals, keep database state, and exercise real process/lock boundaries. diff --git a/packages/void/test/unit/test-projects.test.ts b/packages/void/test/unit/test-projects.test.ts index 56194a41a..e6a416ce8 100644 --- a/packages/void/test/unit/test-projects.test.ts +++ b/packages/void/test/unit/test-projects.test.ts @@ -46,6 +46,26 @@ it('keeps process-timezone tests in forks', () => { } }); +it('assigns every platform unit file exactly once and keeps PGlite files in their own project', () => { + const discovered = [ + ...globSync('platform/packages/*/test/**/*.test.ts', { + cwd: root, + exclude: ['platform/packages/api/test/integration/**'], + }), + ]; + const unitProjects = projects.filter((project) => project.test.name.startsWith('platform-unit')); + const selected = unitProjects.flatMap(files); + expect(selected.sort()).toEqual(discovered.sort()); + expect(new Set(selected).size).toBe(discovered.length); + for (const project of unitProjects) { + for (const file of files(project)) { + if (readFileSync(join(root, file), 'utf8').includes('@electric-sql/pglite')) { + expect(project.test.name, file).toBe('platform-unit-pglite'); + } + } + } +}); + it('assigns every SDK unit file exactly once including the subprocess project', () => { const discovered = [...globSync('packages/*/test/unit/**/*.test.ts', { cwd: root })]; const selected = projects diff --git a/test/affected.mjs b/test/affected.mjs index 21164d89f..423a9b58e 100644 --- a/test/affected.mjs +++ b/test/affected.mjs @@ -58,7 +58,9 @@ export async function runAffected(root, values) { watch: false, changed: initial.full ? false : changed, experimental: { vcsProvider }, - ...(values.unit ? { project: ['unit', 'cli-unit', 'platform-unit'] } : {}), + ...(values.unit + ? { project: ['unit', 'cli-unit', 'platform-unit', 'platform-unit-pglite'] } + : {}), }); try { const all = await vitest.globTestSpecifications(); diff --git a/vite.config.ts b/vite.config.ts index f46ac501b..fe9afc2b9 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,6 +9,14 @@ const apiTimezoneTests = [ 'platform/packages/api/test/integration/api-delete-accounting.test.ts', 'platform/packages/api/test/integration/email-connections.test.ts', ]; +// Platform unit files that start PGlite. Add new PGlite unit files here. +const pgliteUnitTests = [ + 'platform/packages/api/test/unit/admin-upgrade-compatibility.test.ts', + 'platform/packages/api/test/unit/email-migration.test.ts', + 'platform/packages/api/test/unit/email-shared-recipient-migration.test.ts', + 'platform/packages/api/test/unit/github-authority-constraints-migration.test.ts', + 'platform/packages/api/test/unit/github-authority-migration.test.ts', +]; const cliUnitTests = [ 'packages/void/test/unit/cf-*.test.ts', 'packages/void/test/unit/provision-*.test.ts', @@ -443,7 +451,20 @@ export default defineConfig({ fsModuleCache: true, pool: 'threads', include: ['platform/packages/*/test/**/*.test.ts'], - exclude: ['platform/packages/api/test/integration/**'], + exclude: ['platform/packages/api/test/integration/**', ...pgliteUnitTests], + maxWorkers, + }, + }, + { + extends: false, + resolve: { alias: platformAlias }, + test: { + name: 'platform-unit-pglite', + fsModuleCache: true, + // Node 24 on Linux can abort the whole run while unregistering PGlite's Wasm JIT + // allocations across worker threads. Keep each database in its own process. + pool: process.platform === 'linux' ? 'forks' : 'threads', + include: pgliteUnitTests, maxWorkers, }, },