From 0be89ffe43413afd6b39dae3c7440ba5ce6e8781 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 26 Feb 2024 16:56:51 +0100 Subject: [PATCH 1/2] 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 | 10 ++- test/common/sea.js | 89 ++++++++++++------- ...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 | 7 +- ...-single-executable-application-snapshot.js | 7 +- ...e-executable-application-use-code-cache.js | 5 +- .../test-single-executable-application.js | 5 +- 10 files changed, 98 insertions(+), 62 deletions(-) diff --git a/test/common/README.md b/test/common/README.md index fa78b2792ef6ac..c2dc8bf3015d2b 100644 --- a/test/common/README.md +++ b/test/common/README.md @@ -1048,11 +1048,13 @@ 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. ## tick Module diff --git a/test/common/sea.js b/test/common/sea.js index d57c9e4238d867..c726829cd2b6de 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, verifyWorkflow = false) { + 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; - } + } catch (e) { + 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 (e) { + 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..18d66884e36cd0 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'); @@ -12,7 +12,7 @@ skipIfSingleExecutableIsNotSupported(); // This tests "useCodeCache" is ignored when "useSnapshot" is true. const tmpdir = require('../common/tmpdir'); -const { copyFileSync, writeFileSync, existsSync } = require('fs'); +const { writeFileSync, existsSync } = require('fs'); const { spawnSyncAndExitWithoutError } = require('../common/child_process'); @@ -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..779b3e9fb5f332 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'); @@ -12,7 +12,7 @@ skipIfSingleExecutableIsNotSupported(); // This tests the snapshot support in single executable applications. const tmpdir = require('../common/tmpdir'); -const { copyFileSync, writeFileSync, existsSync } = require('fs'); +const { writeFileSync, existsSync } = require('fs'); const { spawnSyncAndExit, spawnSyncAndExitWithoutError @@ -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, From 34c1e85e0b6d13dc21c7e997efe5908248b5c163 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 26 Feb 2024 22:12:00 +0100 Subject: [PATCH 2/2] fixup! test: skip SEA tests when SEA generation fails --- test/common/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/common/README.md b/test/common/README.md index c2dc8bf3015d2b..415601d1e25ecb 100644 --- a/test/common/README.md +++ b/test/common/README.md @@ -1053,8 +1053,8 @@ in the current configuration. 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. +If `verifyWorkflow` is false (default) and any of the steps fails, +it skips the tests. Otherwise, an error is thrown. ## tick Module