From f4171ed73fd5ce351cace2cb2dae425f06fc97fe Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 26 Feb 2024 16:56:51 +0100 Subject: [PATCH] test: skip SEA tests when SEA generation fails In the SEA tests, if any of these steps fail: 1. Copy the executable 2. Inject the SEA blob 3. Signing the SEA We skip the test because the error likely comes from the system or postject and is not something the Node.js core can fix. We only leave an exception for a basic test that test injecting empty files as SEA to ensure the workflow is working (but we still skip if copying fails or signing fails on Windows). --- test/common/README.md | 11 ++- test/common/sea.js | 87 ++++++++++++------- ...ingle-executable-application-assets-raw.js | 5 +- ...st-single-executable-application-assets.js | 5 +- ...cation-disable-experimental-sea-warning.js | 5 +- ...est-single-executable-application-empty.js | 22 +++-- ...ble-application-snapshot-and-code-cache.js | 5 +- ...-single-executable-application-snapshot.js | 5 +- ...e-executable-application-use-code-cache.js | 5 +- .../test-single-executable-application.js | 5 +- 10 files changed, 96 insertions(+), 59 deletions(-) diff --git a/test/common/README.md b/test/common/README.md index fa78b2792ef6ac..d272d7f354f762 100644 --- a/test/common/README.md +++ b/test/common/README.md @@ -1048,11 +1048,14 @@ Application functionality. Skip the rest of the tests if single executable applications are not supported in the current configuration. -### `injectAndCodeSign(targetExecutable, resource)` +### `generateSEA(targetExecutable, sourceExecutable, seaBlob, verifyWorkflow)` -Uses Postect to inject the contents of the file at the path `resource` into -the target executable file at the path `targetExecutable` and ultimately code -sign the final binary. +Copy `sourceExecutable` to `targetExecutable`, use postject to inject `seaBlob` +into `targetExecutable` and sign it if necessary. + +If `verifyWorkflow` is false (default) and any of the steps fails, it skips the tests. +Otherwise, an error is thrown. On Windows, if the failure happens at signing, +the stderr string is attached to `error.stderr` in the `error` being thrown. ## tick Module diff --git a/test/common/sea.js b/test/common/sea.js index d57c9e4238d867..da97c52fa44ab1 100644 --- a/test/common/sea.js +++ b/test/common/sea.js @@ -3,8 +3,9 @@ const common = require('../common'); const fixtures = require('../common/fixtures'); const tmpdir = require('../common/tmpdir'); +const { inspect } = require('util'); -const { readFileSync } = require('fs'); +const { readFileSync, copyFileSync } = require('fs'); const { spawnSyncAndExitWithoutError, } = require('../common/child_process'); @@ -54,47 +55,75 @@ function skipIfSingleExecutableIsNotSupported() { } } -function injectAndCodeSign(targetExecutable, resource) { +function generateSEA(targetExecutable, sourceExecutable, seaBlob) { + try { + copyFileSync(sourceExecutable, targetExecutable); + } catch(e) { + const message = `Cannot copy ${sourceExecutable} to ${targetExecutable}: ${inspect(e)}`; + if (verifyWorkflow) { + throw new Error(message) + } + common.skip(message); + } + console.log(`Copied ${sourceExecutable} to ${targetExecutable}`); + const postjectFile = fixtures.path('postject-copy', 'node_modules', 'postject', 'dist', 'cli.js'); - spawnSyncAndExitWithoutError(process.execPath, [ - postjectFile, - targetExecutable, - 'NODE_SEA_BLOB', - resource, - '--sentinel-fuse', 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2', - ...process.platform === 'darwin' ? [ '--macho-segment-name', 'NODE_SEA' ] : [], - ], {}); + try { + spawnSyncAndExitWithoutError(process.execPath, [ + postjectFile, + targetExecutable, + 'NODE_SEA_BLOB', + seaBlob, + '--sentinel-fuse', 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2', + ...process.platform === 'darwin' ? [ '--macho-segment-name', 'NODE_SEA' ] : [], + ]); + } catch(e) { + const message = `Cannot inject ${seaBlob} into ${targetExecutable}: ${inspect(e)}`; + if (verifyWorkflow) { + throw new Error(message) + } + common.skip(message); + } + console.log(`Injected ${seaBlob} into ${targetExecutable}`); if (process.platform === 'darwin') { - spawnSyncAndExitWithoutError('codesign', [ '--sign', '-', targetExecutable ], {}); - spawnSyncAndExitWithoutError('codesign', [ '--verify', targetExecutable ], {}); + try { + spawnSyncAndExitWithoutError('codesign', [ '--sign', '-', targetExecutable ], {}); + spawnSyncAndExitWithoutError('codesign', [ '--verify', targetExecutable ], {}); + } catch(e) { + const message = `Cannot sign ${targetExecutable}: ${inspect(e)}`; + if (verifyWorkflow) { + throw new Error(message); + } + common.skip(message); + } + console.log(`Signed ${targetExecutable}`); } else if (process.platform === 'win32') { - let signtoolFound = false; try { spawnSyncAndExitWithoutError('where', [ 'signtool' ], {}); - signtoolFound = true; } catch (err) { - console.log(err.message); - } - if (signtoolFound) { - let certificatesFound = false; - let stderr; - try { - ({ stderr } = spawnSyncAndExitWithoutError('signtool', [ 'sign', '/fd', 'SHA256', targetExecutable ], {})); - certificatesFound = true; - } catch (err) { - if (!/SignTool Error: No certificates were found that met all the given criteria/.test(stderr)) { - throw err; - } + const message = `Cannot find signtool: ${inspect(e)}`; + if (verifyWorkflow) { + throw new Error(message); } - if (certificatesFound) { - spawnSyncAndExitWithoutError('signtool', 'verify', '/pa', 'SHA256', targetExecutable, {}); + common.skip(message); + } + let stderr; + try { + ({ stderr } = spawnSyncAndExitWithoutError('signtool', [ 'sign', '/fd', 'SHA256', targetExecutable ], {})); + spawnSyncAndExitWithoutError('signtool', 'verify', '/pa', 'SHA256', targetExecutable, {}); + } catch (err) { + const message = `Cannot sign ${targetExecutable}: ${inspect(e)}\n${stderr}`; + if (verifyWorkflow) { + throw new Error(message); } + common.skip(message); } + console.log(`Signed ${targetExecutable}`); } } module.exports = { skipIfSingleExecutableIsNotSupported, - injectAndCodeSign, + generateSEA, }; diff --git a/test/sequential/test-single-executable-application-assets-raw.js b/test/sequential/test-single-executable-application-assets-raw.js index 6f0a8a77486fb6..4122e8b323a569 100644 --- a/test/sequential/test-single-executable-application-assets-raw.js +++ b/test/sequential/test-single-executable-application-assets-raw.js @@ -3,7 +3,7 @@ const common = require('../common'); const { - injectAndCodeSign, + generateSEA, skipIfSingleExecutableIsNotSupported, } = require('../common/sea'); @@ -56,8 +56,7 @@ const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'se assert(existsSync(seaPrepBlob)); - copyFileSync(process.execPath, outputFile); - injectAndCodeSign(outputFile, seaPrepBlob); + generateSEA(outputFile, process.execPath, seaPrepBlob); spawnSyncAndExitWithoutError( outputFile, diff --git a/test/sequential/test-single-executable-application-assets.js b/test/sequential/test-single-executable-application-assets.js index 366b606e37ce64..ed10e076ced883 100644 --- a/test/sequential/test-single-executable-application-assets.js +++ b/test/sequential/test-single-executable-application-assets.js @@ -3,7 +3,7 @@ const common = require('../common'); const { - injectAndCodeSign, + generateSEA, skipIfSingleExecutableIsNotSupported, } = require('../common/sea'); @@ -109,8 +109,7 @@ const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'se assert(existsSync(seaPrepBlob)); - copyFileSync(process.execPath, outputFile); - injectAndCodeSign(outputFile, seaPrepBlob); + generateSEA(outputFile, process.execPath, seaPrepBlob); spawnSyncAndExitWithoutError( outputFile, diff --git a/test/sequential/test-single-executable-application-disable-experimental-sea-warning.js b/test/sequential/test-single-executable-application-disable-experimental-sea-warning.js index fdd0c23a26da3e..5b1912f1a99eb0 100644 --- a/test/sequential/test-single-executable-application-disable-experimental-sea-warning.js +++ b/test/sequential/test-single-executable-application-disable-experimental-sea-warning.js @@ -3,7 +3,7 @@ require('../common'); const { - injectAndCodeSign, + generateSEA, skipIfSingleExecutableIsNotSupported, } = require('../common/sea'); @@ -51,8 +51,7 @@ spawnSyncAndExitWithoutError( assert(existsSync(seaPrepBlob)); -copyFileSync(process.execPath, outputFile); -injectAndCodeSign(outputFile, seaPrepBlob); +generateSEA(outputFile, process.execPath, seaPrepBlob); spawnSyncAndExitWithoutError( outputFile, diff --git a/test/sequential/test-single-executable-application-empty.js b/test/sequential/test-single-executable-application-empty.js index 047685b0074aa9..d26d72e47dafdd 100644 --- a/test/sequential/test-single-executable-application-empty.js +++ b/test/sequential/test-single-executable-application-empty.js @@ -1,9 +1,9 @@ 'use strict'; -require('../common'); +const common = require('../common'); const { - injectAndCodeSign, + generateSEA, skipIfSingleExecutableIsNotSupported, } = require('../common/sea'); @@ -13,7 +13,7 @@ skipIfSingleExecutableIsNotSupported(); // script. const tmpdir = require('../common/tmpdir'); -const { copyFileSync, writeFileSync, existsSync } = require('fs'); +const { writeFileSync, existsSync } = require('fs'); const { spawnSyncAndExitWithoutError } = require('../common/child_process'); const assert = require('assert'); @@ -38,8 +38,20 @@ spawnSyncAndExitWithoutError( assert(existsSync(seaPrepBlob)); -copyFileSync(process.execPath, outputFile); -injectAndCodeSign(outputFile, seaPrepBlob); +// Verify the workflow. +try { + generateSEA(outputFile, process.execPath, seaPrepBlob, true); +} catch (e) { + if (/Cannot copy/.test(e.message)) { + common.skip(e.message); + } else if (common.isWindows) { + if (/Cannot sign/.test(e.message) || /Cannot find signtool/.test(e.message)) { + common.skip(e.message); + } + } + + throw e; +} spawnSyncAndExitWithoutError( outputFile, diff --git a/test/sequential/test-single-executable-application-snapshot-and-code-cache.js b/test/sequential/test-single-executable-application-snapshot-and-code-cache.js index 952003cf02c585..40ae2a4ed44d50 100644 --- a/test/sequential/test-single-executable-application-snapshot-and-code-cache.js +++ b/test/sequential/test-single-executable-application-snapshot-and-code-cache.js @@ -3,7 +3,7 @@ require('../common'); const { - injectAndCodeSign, + generateSEA, skipIfSingleExecutableIsNotSupported, } = require('../common/sea'); @@ -62,8 +62,7 @@ const outputFile = join(tmpdir.path, process.platform === 'win32' ? 'sea.exe' : assert(existsSync(seaPrepBlob)); - copyFileSync(process.execPath, outputFile); - injectAndCodeSign(outputFile, seaPrepBlob); + generateSEA(outputFile, process.execPath, seaPrepBlob); spawnSyncAndExitWithoutError( outputFile, diff --git a/test/sequential/test-single-executable-application-snapshot.js b/test/sequential/test-single-executable-application-snapshot.js index 402505a6122c74..5bdad24adc93ca 100644 --- a/test/sequential/test-single-executable-application-snapshot.js +++ b/test/sequential/test-single-executable-application-snapshot.js @@ -3,7 +3,7 @@ require('../common'); const { - injectAndCodeSign, + generateSEA, skipIfSingleExecutableIsNotSupported, } = require('../common/sea'); @@ -85,8 +85,7 @@ const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'se assert(existsSync(seaPrepBlob)); - copyFileSync(process.execPath, outputFile); - injectAndCodeSign(outputFile, seaPrepBlob); + generateSEA(outputFile, process.execPath, seaPrepBlob); spawnSyncAndExitWithoutError( outputFile, diff --git a/test/sequential/test-single-executable-application-use-code-cache.js b/test/sequential/test-single-executable-application-use-code-cache.js index af5f2855ed6318..151e631eda7ea8 100644 --- a/test/sequential/test-single-executable-application-use-code-cache.js +++ b/test/sequential/test-single-executable-application-use-code-cache.js @@ -3,7 +3,7 @@ require('../common'); const { - injectAndCodeSign, + generateSEA, skipIfSingleExecutableIsNotSupported, } = require('../common/sea'); @@ -56,8 +56,7 @@ spawnSyncAndExitWithoutError( assert(existsSync(seaPrepBlob)); -copyFileSync(process.execPath, outputFile); -injectAndCodeSign(outputFile, seaPrepBlob); +generateSEA(outputFile, process.execPath, seaPrepBlob); spawnSyncAndExitWithoutError( outputFile, diff --git a/test/sequential/test-single-executable-application.js b/test/sequential/test-single-executable-application.js index 6379dfd2ea4b6d..2e5c7c1e5d564a 100644 --- a/test/sequential/test-single-executable-application.js +++ b/test/sequential/test-single-executable-application.js @@ -3,7 +3,7 @@ require('../common'); const { - injectAndCodeSign, + generateSEA, skipIfSingleExecutableIsNotSupported, } = require('../common/sea'); @@ -50,8 +50,7 @@ spawnSyncAndExitWithoutError( assert(existsSync(seaPrepBlob)); -copyFileSync(process.execPath, outputFile); -injectAndCodeSign(outputFile, seaPrepBlob); +generateSEA(outputFile, process.execPath, seaPrepBlob); spawnSyncAndExitWithoutError( outputFile,