From fbe5a3ee531c7486fb45fc06da11aa3665875ab0 Mon Sep 17 00:00:00 2001 From: David Alsh Date: Mon, 12 May 2025 12:52:49 +1000 Subject: [PATCH 1/3] node-api: minimal C node_embedding_api function Co-authored-by: vmoroz --- doc/api/embedding.md | 42 ++++ node.gyp | 5 + src/node_runtime_api.cc | 25 +++ src/node_runtime_api.h | 28 +++ test/embedding/embedtest.cc | 7 +- test/embedding/embedtest_c_api_main.c | 8 + test/embedding/embedtest_main.cc | 37 +++ test/embedding/test-embedding.js | 310 +++++++++++++++++--------- 8 files changed, 346 insertions(+), 116 deletions(-) create mode 100644 src/node_runtime_api.cc create mode 100644 src/node_runtime_api.h create mode 100644 test/embedding/embedtest_c_api_main.c create mode 100644 test/embedding/embedtest_main.cc diff --git a/doc/api/embedding.md b/doc/api/embedding.md index 114f1128af0a42..46082a9781efc8 100644 --- a/doc/api/embedding.md +++ b/doc/api/embedding.md @@ -167,8 +167,50 @@ int RunNodeInstance(MultiIsolatePlatform* platform, } ``` +## C runtime API + + + +While Node.js provides an extensive C++ embedding API that can be used from C++ +applications, the C-based API is useful when Node.js is embedded as a shared +libnode library into C++ or non-C++ applications. + +### API design overview + +One of the goals for the C based runtime API is to be ABI stable. It means that +applications must be able to use newer libnode versions without recompilation. +The following design principles are targeting to achieve that goal. + +* Follow the best practices for the [node-api][] design and build on top of + the [node-api][]. + +### API reference + +#### Functions + +##### `node_rt_main` + + + +> Stability: 1 - Experimental + +Runs Node.js runtime instance the same way as the Node.js executable. + +```c +int32_t NAPI_CDECL node_rt_main( + int32_t argc, + char* argv[]); +``` + +* `[in] argc`: Number of items in the `argv` array. +* `[in] argv`: CLI arguments as an array of zero terminated strings. + Returns `int32_t` with runtime instance exit code. + [CLI options]: cli.md [`process.memoryUsage()`]: process.md#processmemoryusage [deprecation policy]: deprecations.md [embedtest.cc]: https://github.com/nodejs/node/blob/HEAD/test/embedding/embedtest.cc +[node-api]: n-api.md [src/node.h]: https://github.com/nodejs/node/blob/HEAD/src/node.h diff --git a/node.gyp b/node.gyp index 83351d1d82627e..f71977c4e3e499 100644 --- a/node.gyp +++ b/node.gyp @@ -144,6 +144,7 @@ 'src/node_report.cc', 'src/node_report_module.cc', 'src/node_report_utils.cc', + 'src/node_runtime_api.cc', 'src/node_sea.cc', 'src/node_sea_bin.cc', 'src/node_serdes.cc', @@ -238,6 +239,7 @@ 'src/module_wrap.h', 'src/node.h', 'src/node_api.h', + 'src/node_api_internals.h', 'src/node_api_types.h', 'src/node_binding.h', 'src/node_blob.h', @@ -282,6 +284,7 @@ 'src/node_report.h', 'src/node_revert.h', 'src/node_root_certs.h', + 'src/node_runtime_api.h', 'src/node_sea.h', 'src/node_shadow_realm.h', 'src/node_snapshotable.h', @@ -1389,6 +1392,8 @@ 'sources': [ 'src/node_snapshot_stub.cc', 'test/embedding/embedtest.cc', + 'test/embedding/embedtest_c_api_main.c', + 'test/embedding/embedtest_main.cc', ], 'conditions': [ diff --git a/src/node_runtime_api.cc b/src/node_runtime_api.cc new file mode 100644 index 00000000000000..602f7b6c118c31 --- /dev/null +++ b/src/node_runtime_api.cc @@ -0,0 +1,25 @@ +// +// Description: C-based API for embedding Node.js. +// +// !!! WARNING !!! WARNING !!! WARNING !!! +// This is a new API and is subject to change. +// While it is C-based, it is not ABI safe yet. +// Consider all functions and data structures as experimental. +// !!! WARNING !!! WARNING !!! WARNING !!! +// +// This file contains the C-based API for embedding Node.js in a host +// application. The API is designed to be used by applications that want to +// embed Node.js as a shared library (.so or .dll) and can interop with +// C-based API. +// + +#include "node_runtime_api.h" +#include "node.h" + +EXTERN_C_START + +int32_t NAPI_CDECL node_rt_main(int32_t argc, char* argv[]) { + return node::Start(argc, argv); +} + +EXTERN_C_END diff --git a/src/node_runtime_api.h b/src/node_runtime_api.h new file mode 100644 index 00000000000000..7eefa8b2dd129b --- /dev/null +++ b/src/node_runtime_api.h @@ -0,0 +1,28 @@ +// +// Description: C-based API for embedding Node.js. +// +// !!! WARNING !!! WARNING !!! WARNING !!! +// This is a new API and is subject to change. +// While it is C-based, it is not ABI safe yet. +// Consider all functions and data structures as experimental. +// !!! WARNING !!! WARNING !!! WARNING !!! +// +// This file contains the C-based API for embedding Node.js in a host +// application. The API is designed to be used by applications that want to +// embed Node.js as a shared library (.so or .dll) and can interop with +// C-based API. +// + +#ifndef SRC_NODE_RUNTIME_API_H_ +#define SRC_NODE_RUNTIME_API_H_ + +#include "node_api.h" + +EXTERN_C_START + +// Runs Node.js main function. It is the same as running Node.js from CLI. +NAPI_EXTERN int32_t NAPI_CDECL node_rt_main(int32_t argc, char* argv[]); + +EXTERN_C_END + +#endif // SRC_NODE_RUNTIME_API_H_ diff --git a/test/embedding/embedtest.cc b/test/embedding/embedtest.cc index 982eed74f9540b..b2b4b3533cb9c5 100644 --- a/test/embedding/embedtest.cc +++ b/test/embedding/embedtest.cc @@ -3,8 +3,8 @@ #endif #include #include "cppgc/platform.h" -#include "executable_wrapper.h" #include "node.h" +#include "uv.h" #include @@ -28,10 +28,7 @@ static int RunNodeInstance(MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args); -NODE_MAIN(int argc, node::argv_type raw_argv[]) { - char** argv = nullptr; - node::FixupMain(argc, raw_argv, &argv); - +int32_t test_main_cpp_api(int32_t argc, char* argv[]) { std::vector args(argv, argv + argc); std::shared_ptr result = node::InitializeOncePerProcess( diff --git a/test/embedding/embedtest_c_api_main.c b/test/embedding/embedtest_c_api_main.c new file mode 100644 index 00000000000000..93cdc5600e0ff8 --- /dev/null +++ b/test/embedding/embedtest_c_api_main.c @@ -0,0 +1,8 @@ +#include "node_runtime_api.h" + +// The simplest Node.js embedding scenario where the Node.js main function is +// invoked from the libnode shared library as it would be run from the Node.js +// CLI. +int32_t test_main_c_api_nodejs_main(int32_t argc, char* argv[]) { + return node_rt_main(argc, argv); +} diff --git a/test/embedding/embedtest_main.cc b/test/embedding/embedtest_main.cc new file mode 100644 index 00000000000000..323505532f91a1 --- /dev/null +++ b/test/embedding/embedtest_main.cc @@ -0,0 +1,37 @@ +#include +#include +#include +#include "executable_wrapper.h" + +int32_t test_main_cpp_api(int32_t argc, char* argv[]); + +extern "C" int32_t test_main_c_api_nodejs_main(int32_t argc, char* argv[]); + +using MainCallback = int32_t (*)(int32_t argc, char* argv[]); + +int32_t CallWithoutArg1(MainCallback main, int32_t argc, char* argv[]) { + for (int32_t i = 2; i < argc; i++) { + argv[i - 1] = argv[i]; + } + argv[--argc] = nullptr; + return main(argc, argv); +} + +NODE_MAIN(int32_t argc, node::argv_type raw_argv[]) { + char** argv = nullptr; + node::FixupMain(argc, raw_argv, &argv); + + const std::unordered_map main_map = { + {"cpp-api", test_main_cpp_api}, + {"c-api-nodejs-main", test_main_c_api_nodejs_main}, + }; + if (argc > 1) { + char* arg1 = argv[1]; + for (const auto& [key, value] : main_map) { + if (key == arg1) { + return CallWithoutArg1(value, argc, argv); + } + } + } + return test_main_cpp_api(argc, argv); +} diff --git a/test/embedding/test-embedding.js b/test/embedding/test-embedding.js index 79d79079b4b8c6..f2f9f4a9b5f2b6 100644 --- a/test/embedding/test-embedding.js +++ b/test/embedding/test-embedding.js @@ -9,7 +9,6 @@ const { spawnSyncAndExitWithoutError, } = require('../common/child_process'); const fs = require('fs'); -const os = require('os'); tmpdir.refresh(); common.allowGlobals(global.require); @@ -17,121 +16,85 @@ common.allowGlobals(global.embedVars); const binary = common.resolveBuiltBinary('embedtest'); -spawnSyncAndAssert( - binary, - ['console.log(42)'], - { - trim: true, - stdout: '42', - }); - -spawnSyncAndAssert( - binary, - ['console.log(embedVars.nön_ascıı)'], - { - trim: true, - stdout: '🏳️‍🌈', - }); - -spawnSyncAndExit( - binary, - ['throw new Error()'], - { - status: 1, - signal: null, - }); +function runTest(testName, spawn, ...args) { + process.stdout.write(`Run test: ${testName} ... `); + spawn(binary, ...args); + console.log('ok'); +} -spawnSyncAndExit( - binary, - ['require("lib/internal/test/binding")'], - { - status: 1, - signal: null, - }); +function runCommonApiTests(apiType) { + runTest( + `${apiType}: console.log`, + spawnSyncAndAssert, + [apiType, 'console.log(42)'], + { + trim: true, + stdout: '42', + }, + ); -spawnSyncAndExit( - binary, - ['process.exitCode = 8'], - { - status: 8, - signal: null, - }); - -const fixturePath = JSON.stringify(fixtures.path('exit.js')); -spawnSyncAndExit( - binary, - [`require(${fixturePath})`, 92], - { - status: 92, - signal: null, - }); + runTest( + `${apiType}: console.log non-ascii`, + spawnSyncAndAssert, + [apiType, 'console.log(embedVars.nön_ascıı)'], + { + trim: true, + stdout: '🏳️‍🌈', + }, + ); -function getReadFileCodeForPath(path) { - return `(require("fs").readFileSync(${JSON.stringify(path)}, "utf8"))`; -} + runTest( + `${apiType}: throw new Error()`, + spawnSyncAndExit, + [apiType, 'throw new Error()'], + { + status: 1, + signal: null, + }, + ); -// Basic snapshot support -for (const extraSnapshotArgs of [ - [], ['--embedder-snapshot-as-file'], ['--without-code-cache'], -]) { - // readSync + eval since snapshots don't support userland require() (yet) - const snapshotFixture = fixtures.path('snapshot', 'echo-args.js'); - const blobPath = tmpdir.resolve('embedder-snapshot.blob'); - const buildSnapshotExecArgs = [ - `eval(${getReadFileCodeForPath(snapshotFixture)})`, 'arg1', 'arg2', - ]; - const embedTestBuildArgs = [ - '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create', - ...extraSnapshotArgs, - ]; - const buildSnapshotArgs = [ - ...buildSnapshotExecArgs, - ...embedTestBuildArgs, - ]; - - const runSnapshotExecArgs = [ - 'arg3', 'arg4', - ]; - const embedTestRunArgs = [ - '--embedder-snapshot-blob', blobPath, - ...extraSnapshotArgs, - ]; - const runSnapshotArgs = [ - ...runSnapshotExecArgs, - ...embedTestRunArgs, - ]; + runTest( + `${apiType}: require("lib/internal/test/binding")`, + spawnSyncAndExit, + [apiType, 'require("lib/internal/test/binding")'], + { + status: 1, + signal: null, + }, + ); - fs.rmSync(blobPath, { force: true }); - spawnSyncAndExitWithoutError( - binary, - [ '--', ...buildSnapshotArgs ], - { cwd: tmpdir.path }); - spawnSyncAndAssert( - binary, - [ '--', ...runSnapshotArgs ], - { cwd: tmpdir.path }, + runTest( + `${apiType}: process.exitCode = 8`, + spawnSyncAndExit, + [apiType, 'process.exitCode = 8'], { - stdout(output) { - assert.deepStrictEqual(JSON.parse(output), { - originalArgv: [binary, '__node_anonymous_main', ...buildSnapshotExecArgs], - currentArgv: [binary, ...runSnapshotExecArgs], - }); - return true; + status: 8, + signal: null, + }, + ); + + { + const fixturePath = JSON.stringify(fixtures.path('exit.js')); + runTest( + `${apiType}: require(fixturePath)`, + spawnSyncAndExit, + [apiType, `require(${fixturePath})`, 92], + { + status: 92, + signal: null, }, - }); -} + ); + } -// Create workers and vm contexts after deserialization -{ - const snapshotFixture = fixtures.path('snapshot', 'create-worker-and-vm.js'); - const blobPath = tmpdir.resolve('embedder-snapshot.blob'); - const buildSnapshotArgs = [ - `eval(${getReadFileCodeForPath(snapshotFixture)})`, - '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create', - ]; - const runEmbeddedArgs = [ - '--embedder-snapshot-blob', blobPath, - ]; + runTest( + `${apiType}: syntax error`, + spawnSyncAndExit, + [apiType, '0syntax_error'], + { + status: 1, + stderr: /SyntaxError: Invalid or unexpected token/, + }, + ); fs.rmSync(blobPath, { force: true }); @@ -153,12 +116,137 @@ if (!process.config.variables.node_without_node_options) { { env: { ...process.env, - 'NODE_REPL_EXTERNAL_MODULE': 'fs', + NODE_REPL_EXTERNAL_MODULE: 'fs', }, }, { status: 9, signal: null, - stderr: `${binary}: NODE_REPL_EXTERNAL_MODULE can't be used with kDisableNodeOptionsEnv${os.EOL}`, - }); + trim: true, + stderr: + `${binary}: NODE_REPL_EXTERNAL_MODULE can't be used with` + + ' kDisableNodeOptionsEnv', + }, + ); } + +runCommonApiTests('cpp-api'); + +function getReadFileCodeForPath(path) { + return `(require("fs").readFileSync(${JSON.stringify(path)}, "utf8"))`; +} + +function runSnapshotTests(apiType) { + // Basic snapshot support + for (const extraSnapshotArgs of [ + [], ['--embedder-snapshot-as-file'], ['--without-code-cache'], + ]) { + // readSync + eval since snapshots don't support userland require() (yet) + const snapshotFixture = fixtures.path('snapshot', 'echo-args.js'); + const blobPath = tmpdir.resolve('embedder-snapshot.blob'); + const buildSnapshotExecArgs = [ + `eval(${getReadFileCodeForPath(snapshotFixture)})`, 'arg1', 'arg2', + ]; + const embedTestBuildArgs = [ + '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create', + ...extraSnapshotArgs, + ]; + const buildSnapshotArgs = [ + ...buildSnapshotExecArgs, + ...embedTestBuildArgs, + ]; + + const runSnapshotExecArgs = [ + 'arg3', 'arg4', + ]; + const embedTestRunArgs = [ + '--embedder-snapshot-blob', blobPath, + ...extraSnapshotArgs, + ]; + const runSnapshotArgs = [ + ...runSnapshotExecArgs, + ...embedTestRunArgs, + ]; + + fs.rmSync(blobPath, { force: true }); + + runTest( + `${apiType}: build basic snapshot ${extraSnapshotArgs.join(' ')}`, + spawnSyncAndExitWithoutError, + [apiType, '--', ...buildSnapshotArgs], + { + cwd: tmpdir.path, + }, + ); + + runTest( + `${apiType}: run basic snapshot ${extraSnapshotArgs.join(' ')}`, + spawnSyncAndAssert, + [apiType, '--', ...runSnapshotArgs], + { cwd: tmpdir.path }, + { + stdout: common.mustCall((output) => { + assert.deepStrictEqual(JSON.parse(output), { + originalArgv: [ + binary, + '__node_anonymous_main', + ...buildSnapshotExecArgs, + ], + currentArgv: [binary, ...runSnapshotExecArgs], + }); + return true; + }), + }, + ); + } + + // Create workers and vm contexts after deserialization + { + const snapshotFixture = fixtures.path('snapshot', 'create-worker-and-vm.js'); + const blobPath = tmpdir.resolve('embedder-snapshot.blob'); + const buildSnapshotArgs = [ + `eval(${getReadFileCodeForPath(snapshotFixture)})`, + '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create', + ]; + const runEmbeddedArgs = [ + '--embedder-snapshot-blob', blobPath, + ]; + + fs.rmSync(blobPath, { force: true }); + + runTest( + `${apiType}: build create-worker-and-vm snapshot`, + spawnSyncAndExitWithoutError, + [apiType, '--', ...buildSnapshotArgs], + { + cwd: tmpdir.path, + }, + ); + + runTest( + `${apiType}: run create-worker-and-vm snapshot`, + spawnSyncAndExitWithoutError, + [apiType, '--', ...runEmbeddedArgs], + { + cwd: tmpdir.path, + }, + ); + } +} + +runSnapshotTests('cpp-api'); + +// C-API specific tests +function runCApiTests(apiType) { + runTest( + `${apiType}-nodejs-main: run Node.js CLI`, + spawnSyncAndAssert, + [`${apiType}-nodejs-main`, '--eval', 'console.log("Hello World")'], + { + trim: true, + stdout: 'Hello World', + }, + ); +} + +runCApiTests('c-api'); From 23de792ffc79129d794218ed0c1d0dc3288ae090 Mon Sep 17 00:00:00 2001 From: David Alsh Date: Sat, 31 Jan 2026 08:58:09 +1100 Subject: [PATCH 2/3] tests --- test/embedding/test-embedding.js | 316 ++++++++++++------------------- 1 file changed, 119 insertions(+), 197 deletions(-) diff --git a/test/embedding/test-embedding.js b/test/embedding/test-embedding.js index f2f9f4a9b5f2b6..3931f7d7f2d665 100644 --- a/test/embedding/test-embedding.js +++ b/test/embedding/test-embedding.js @@ -9,6 +9,7 @@ const { spawnSyncAndExitWithoutError, } = require('../common/child_process'); const fs = require('fs'); +const os = require('os'); tmpdir.refresh(); common.allowGlobals(global.require); @@ -16,85 +17,121 @@ common.allowGlobals(global.embedVars); const binary = common.resolveBuiltBinary('embedtest'); -function runTest(testName, spawn, ...args) { - process.stdout.write(`Run test: ${testName} ... `); - spawn(binary, ...args); - console.log('ok'); -} +spawnSyncAndAssert( + binary, + ['console.log(42)'], + { + trim: true, + stdout: '42', + }); -function runCommonApiTests(apiType) { - runTest( - `${apiType}: console.log`, - spawnSyncAndAssert, - [apiType, 'console.log(42)'], - { - trim: true, - stdout: '42', - }, - ); +spawnSyncAndAssert( + binary, + ['console.log(embedVars.nön_ascıı)'], + { + trim: true, + stdout: '🏳️‍🌈', + }); - runTest( - `${apiType}: console.log non-ascii`, - spawnSyncAndAssert, - [apiType, 'console.log(embedVars.nön_ascıı)'], - { - trim: true, - stdout: '🏳️‍🌈', - }, - ); +spawnSyncAndExit( + binary, + ['throw new Error()'], + { + status: 1, + signal: null, + }); - runTest( - `${apiType}: throw new Error()`, - spawnSyncAndExit, - [apiType, 'throw new Error()'], - { - status: 1, - signal: null, - }, - ); +spawnSyncAndExit( + binary, + ['require("lib/internal/test/binding")'], + { + status: 1, + signal: null, + }); - runTest( - `${apiType}: require("lib/internal/test/binding")`, - spawnSyncAndExit, - [apiType, 'require("lib/internal/test/binding")'], - { - status: 1, - signal: null, - }, - ); +spawnSyncAndExit( + binary, + ['process.exitCode = 8'], + { + status: 8, + signal: null, + }); + +const fixturePath = JSON.stringify(fixtures.path('exit.js')); +spawnSyncAndExit( + binary, + [`require(${fixturePath})`, 92], + { + status: 92, + signal: null, + }); - runTest( - `${apiType}: process.exitCode = 8`, - spawnSyncAndExit, - [apiType, 'process.exitCode = 8'], - { - status: 8, - signal: null, - }, - ); +function getReadFileCodeForPath(path) { + return `(require("fs").readFileSync(${JSON.stringify(path)}, "utf8"))`; +} - { - const fixturePath = JSON.stringify(fixtures.path('exit.js')); - runTest( - `${apiType}: require(fixturePath)`, - spawnSyncAndExit, - [apiType, `require(${fixturePath})`, 92], - { - status: 92, - signal: null, - }, - ); - } +// Basic snapshot support +for (const extraSnapshotArgs of [ + [], ['--embedder-snapshot-as-file'], ['--without-code-cache'], +]) { + // readSync + eval since snapshots don't support userland require() (yet) + const snapshotFixture = fixtures.path('snapshot', 'echo-args.js'); + const blobPath = tmpdir.resolve('embedder-snapshot.blob'); + const buildSnapshotExecArgs = [ + `eval(${getReadFileCodeForPath(snapshotFixture)})`, 'arg1', 'arg2', + ]; + const embedTestBuildArgs = [ + '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create', + ...extraSnapshotArgs, + ]; + const buildSnapshotArgs = [ + ...buildSnapshotExecArgs, + ...embedTestBuildArgs, + ]; + + const runSnapshotExecArgs = [ + 'arg3', 'arg4', + ]; + const embedTestRunArgs = [ + '--embedder-snapshot-blob', blobPath, + ...extraSnapshotArgs, + ]; + const runSnapshotArgs = [ + ...runSnapshotExecArgs, + ...embedTestRunArgs, + ]; - runTest( - `${apiType}: syntax error`, - spawnSyncAndExit, - [apiType, '0syntax_error'], + fs.rmSync(blobPath, { force: true }); + spawnSyncAndExitWithoutError( + binary, + [ '--', ...buildSnapshotArgs ], + { cwd: tmpdir.path }); + spawnSyncAndAssert( + binary, + [ '--', ...runSnapshotArgs ], + { cwd: tmpdir.path }, { - status: 1, - stderr: /SyntaxError: Invalid or unexpected token/, - }, - ); + stdout(output) { + assert.deepStrictEqual(JSON.parse(output), { + originalArgv: [binary, '__node_anonymous_main', ...buildSnapshotExecArgs], + currentArgv: [binary, ...runSnapshotExecArgs], + }); + return true; + }, + }); +} + +// Create workers and vm contexts after deserialization +{ + const snapshotFixture = fixtures.path('snapshot', 'create-worker-and-vm.js'); + const blobPath = tmpdir.resolve('embedder-snapshot.blob'); + const buildSnapshotArgs = [ + `eval(${getReadFileCodeForPath(snapshotFixture)})`, + '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create', + ]; + const runEmbeddedArgs = [ + '--embedder-snapshot-blob', blobPath, + ]; fs.rmSync(blobPath, { force: true }); @@ -116,137 +153,22 @@ if (!process.config.variables.node_without_node_options) { { env: { ...process.env, - NODE_REPL_EXTERNAL_MODULE: 'fs', + 'NODE_REPL_EXTERNAL_MODULE': 'fs', }, }, { status: 9, signal: null, - trim: true, - stderr: - `${binary}: NODE_REPL_EXTERNAL_MODULE can't be used with` + - ' kDisableNodeOptionsEnv', - }, - ); + stderr: `${binary}: NODE_REPL_EXTERNAL_MODULE can't be used with kDisableNodeOptionsEnv${os.EOL}`, + }); } -runCommonApiTests('cpp-api'); - -function getReadFileCodeForPath(path) { - return `(require("fs").readFileSync(${JSON.stringify(path)}, "utf8"))`; -} - -function runSnapshotTests(apiType) { - // Basic snapshot support - for (const extraSnapshotArgs of [ - [], ['--embedder-snapshot-as-file'], ['--without-code-cache'], - ]) { - // readSync + eval since snapshots don't support userland require() (yet) - const snapshotFixture = fixtures.path('snapshot', 'echo-args.js'); - const blobPath = tmpdir.resolve('embedder-snapshot.blob'); - const buildSnapshotExecArgs = [ - `eval(${getReadFileCodeForPath(snapshotFixture)})`, 'arg1', 'arg2', - ]; - const embedTestBuildArgs = [ - '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create', - ...extraSnapshotArgs, - ]; - const buildSnapshotArgs = [ - ...buildSnapshotExecArgs, - ...embedTestBuildArgs, - ]; - - const runSnapshotExecArgs = [ - 'arg3', 'arg4', - ]; - const embedTestRunArgs = [ - '--embedder-snapshot-blob', blobPath, - ...extraSnapshotArgs, - ]; - const runSnapshotArgs = [ - ...runSnapshotExecArgs, - ...embedTestRunArgs, - ]; - - fs.rmSync(blobPath, { force: true }); - - runTest( - `${apiType}: build basic snapshot ${extraSnapshotArgs.join(' ')}`, - spawnSyncAndExitWithoutError, - [apiType, '--', ...buildSnapshotArgs], - { - cwd: tmpdir.path, - }, - ); - - runTest( - `${apiType}: run basic snapshot ${extraSnapshotArgs.join(' ')}`, - spawnSyncAndAssert, - [apiType, '--', ...runSnapshotArgs], - { cwd: tmpdir.path }, - { - stdout: common.mustCall((output) => { - assert.deepStrictEqual(JSON.parse(output), { - originalArgv: [ - binary, - '__node_anonymous_main', - ...buildSnapshotExecArgs, - ], - currentArgv: [binary, ...runSnapshotExecArgs], - }); - return true; - }), - }, - ); - } - - // Create workers and vm contexts after deserialization - { - const snapshotFixture = fixtures.path('snapshot', 'create-worker-and-vm.js'); - const blobPath = tmpdir.resolve('embedder-snapshot.blob'); - const buildSnapshotArgs = [ - `eval(${getReadFileCodeForPath(snapshotFixture)})`, - '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create', - ]; - const runEmbeddedArgs = [ - '--embedder-snapshot-blob', blobPath, - ]; - - fs.rmSync(blobPath, { force: true }); - - runTest( - `${apiType}: build create-worker-and-vm snapshot`, - spawnSyncAndExitWithoutError, - [apiType, '--', ...buildSnapshotArgs], - { - cwd: tmpdir.path, - }, - ); - - runTest( - `${apiType}: run create-worker-and-vm snapshot`, - spawnSyncAndExitWithoutError, - [apiType, '--', ...runEmbeddedArgs], - { - cwd: tmpdir.path, - }, - ); - } -} - -runSnapshotTests('cpp-api'); - // C-API specific tests -function runCApiTests(apiType) { - runTest( - `${apiType}-nodejs-main: run Node.js CLI`, - spawnSyncAndAssert, - [`${apiType}-nodejs-main`, '--eval', 'console.log("Hello World")'], - { - trim: true, - stdout: 'Hello World', - }, - ); -} - -runCApiTests('c-api'); +spawnSyncAndAssert( + binary, + ['c-api-nodejs-main', '--eval', 'console.log("Hello World")'], + { + trim: true, + stdout: 'Hello World', + }, +) From c59403e8d89d60a90dbd84271de2a25a22b7eadb Mon Sep 17 00:00:00 2001 From: David Alsh Date: Sat, 31 Jan 2026 09:05:11 +1100 Subject: [PATCH 3/3] Changed to 'node_embedding_start' --- doc/api/embedding.md | 4 ++-- src/node_runtime_api.cc | 2 +- src/node_runtime_api.h | 2 +- test/embedding/embedtest_c_api_main.c | 2 +- test/embedding/test-embedding.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/api/embedding.md b/doc/api/embedding.md index 46082a9781efc8..34382cb3bd22e3 100644 --- a/doc/api/embedding.md +++ b/doc/api/embedding.md @@ -188,7 +188,7 @@ The following design principles are targeting to achieve that goal. #### Functions -##### `node_rt_main` +##### `node_embedding_start`