From 73f21e1264358dac4989c2e369a02328b01070ec Mon Sep 17 00:00:00 2001 From: XingYu-Zhong <1736101137@qq.com> Date: Tue, 4 Aug 2026 22:32:54 +0800 Subject: [PATCH] fix(release): stabilize macOS and Linux packaging checks --- .../graph/graph-scheduler-loop-gate.test.ts | 5 +- scripts/smoke-packaged-extension-appimage.cjs | 47 +++++++++++++++---- ...smoke-packaged-extension-appimage.test.cjs | 43 +++++++++++++++++ 3 files changed, 84 insertions(+), 11 deletions(-) diff --git a/kun/src/graph/graph-scheduler-loop-gate.test.ts b/kun/src/graph/graph-scheduler-loop-gate.test.ts index bce7825b3..48e20829d 100644 --- a/kun/src/graph/graph-scheduler-loop-gate.test.ts +++ b/kun/src/graph/graph-scheduler-loop-gate.test.ts @@ -19,8 +19,9 @@ import { } from '../../tests/graph-scheduler-test-harness.js' const execFileAsync = promisify(execFile) -const schedulerWaitTimeoutMs = process.platform === 'win32' ? 30_000 : 15_000 -const schedulerTestTimeoutMs = process.platform === 'win32' ? 60_000 : 30_000 +const usesSlowerSchedulerPersistence = process.platform === 'darwin' || process.platform === 'win32' +const schedulerWaitTimeoutMs = usesSlowerSchedulerPersistence ? 30_000 : 15_000 +const schedulerTestTimeoutMs = usesSlowerSchedulerPersistence ? 60_000 : 30_000 function task( key: string, diff --git a/scripts/smoke-packaged-extension-appimage.cjs b/scripts/smoke-packaged-extension-appimage.cjs index 82e8bbca9..81ae427bc 100644 --- a/scripts/smoke-packaged-extension-appimage.cjs +++ b/scripts/smoke-packaged-extension-appimage.cjs @@ -22,6 +22,9 @@ const { const APPIMAGE_FILE_PATTERN = /^Kun-[0-9A-Za-z][0-9A-Za-z._-]*-linux-x86_64\.AppImage$/ const APPIMAGE_EXTRACTION_TIMEOUT_MS = 120_000 +const APPIMAGE_DESKTOP_SMOKE_MAX_BUFFER = 1024 * 1024 +const TRANSIENT_CHROMIUM_SIGTRAP_PATTERN = + /Packaged Electron exited before the desktop smoke completed \(exit=133, signal=null\)/u function assertLinuxX64(platform = process.platform, arch = process.arch) { if (platform !== 'linux' || arch !== 'x64') { @@ -112,7 +115,9 @@ function createAppImageSmokeInvocation({ options: { env, shell: false, - stdio: 'inherit', + stdio: ['ignore', 'pipe', 'pipe'], + encoding: 'utf8', + maxBuffer: APPIMAGE_DESKTOP_SMOKE_MAX_BUFFER, windowsHide: true } } @@ -160,7 +165,8 @@ function runAppImageSmoke(options = {}) { runInvocation( smoke, options.spawnSyncCommand ?? spawnSync, - 'Final Linux AppImage Extension desktop smoke failed' + 'Final Linux AppImage Extension desktop smoke failed', + { retryTransientChromiumSigtrap: true } ) return appImage } finally { @@ -294,19 +300,42 @@ function inspectEmptyExtractionDirectory(extractionDirectory) { } } -function runInvocation(invocation, spawnSyncCommand, failureMessage) { - const result = spawnSyncCommand(invocation.command, invocation.args, invocation.options) - if (result.error?.code === 'ETIMEDOUT') { - throw new Error(`${failureMessage} (timed out after ${String(invocation.options.timeout)} ms)`) - } - if (result.error) throw result.error - if (result.status !== 0) { +function runInvocation( + invocation, + spawnSyncCommand, + failureMessage, + { retryTransientChromiumSigtrap = false } = {} +) { + const maxAttempts = retryTransientChromiumSigtrap ? 2 : 1 + for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + const result = spawnSyncCommand(invocation.command, invocation.args, invocation.options) + forwardInvocationOutput(result) + if (result.error?.code === 'ETIMEDOUT') { + throw new Error(`${failureMessage} (timed out after ${String(invocation.options.timeout)} ms)`) + } + if (result.error) throw result.error + if (result.status === 0) return + if ( + attempt < maxAttempts && + result.status === 1 && + TRANSIENT_CHROMIUM_SIGTRAP_PATTERN.test(`${result.stdout ?? ''}\n${result.stderr ?? ''}`) + ) { + process.stderr.write( + 'Final Linux AppImage Chromium smoke hit a transient SIGTRAP; retrying once.\n' + ) + continue + } throw new Error( `${failureMessage}${result.signal ? ` (signal ${result.signal})` : ` (exit ${String(result.status)})`}` ) } } +function forwardInvocationOutput(result) { + if (result.stdout) process.stdout.write(result.stdout) + if (result.stderr) process.stderr.write(result.stderr) +} + function assertContained(root, candidate, label) { const rel = relative(root, candidate) if (!rel || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) { diff --git a/scripts/smoke-packaged-extension-appimage.test.cjs b/scripts/smoke-packaged-extension-appimage.test.cjs index d654cc098..0aef7ce45 100644 --- a/scripts/smoke-packaged-extension-appimage.test.cjs +++ b/scripts/smoke-packaged-extension-appimage.test.cjs @@ -111,6 +111,9 @@ test('builds FUSE-free validation and verified-AppRun desktop invocations', () = resolve('/tmp/kun-appimage-extract/squashfs-root/AppRun') ]) assert.equal(smoke.options.shell, false) + assert.deepEqual(smoke.options.stdio, ['ignore', 'pipe', 'pipe']) + assert.equal(smoke.options.encoding, 'utf8') + assert.equal(smoke.options.maxBuffer, 1024 * 1024) assert.equal(smoke.options.timeout, undefined) assert.equal(smoke.options.killSignal, undefined) assert.equal(smoke.options.env.ELECTRON_RUN_AS_NODE, undefined) @@ -166,6 +169,46 @@ test('extracts and validates before launching the verified AppRun', { assert.equal(statSync(appImage).mode & 0o111, 0o111) }) +test('retries the verified AppRun once after the exact transient Chromium SIGTRAP', { + skip: process.platform === 'win32' && 'requires POSIX executable modes' +}, (t) => { + const dist = temporaryDirectory(t) + const extractionDirectory = temporaryDirectory(t, 'kun-appimage-sigtrap-test-') + const appImage = join(dist, 'Kun-1.2.3-linux-x86_64.AppImage') + writeFileSync(appImage, 'appimage') + let desktopAttempts = 0 + + assert.doesNotThrow(() => runAppImageSmoke({ + platform: 'linux', + arch: 'x64', + distDirectory: dist, + extractionDirectory, + spawnSyncCommand: (command, args, options) => { + if (command === appImage) { + writeExtractedBundle(options.cwd) + return { status: 0, signal: null } + } + desktopAttempts += 1 + if (desktopAttempts === 1) { + return { + status: 1, + signal: null, + stdout: '', + stderr: + 'Packaged Electron exited before the desktop smoke completed ' + + '(exit=133, signal=null)\n' + } + } + assert.deepEqual(args.slice(-2), [ + '--desktop-executable', + join(extractionDirectory, 'squashfs-root', 'AppRun') + ]) + return { status: 0, signal: null, stdout: '', stderr: '' } + } + })) + assert.equal(desktopAttempts, 2) +}) + test('rejects a symlinked extracted AppRun before desktop launch', (t) => { if (process.platform === 'win32') return const dist = temporaryDirectory(t)