Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

55
test/README.md Normal file
View File

@ -0,0 +1,55 @@
# Node.js Core Tests
This directory contains code and data used to test the Node.js implementation.
For a detailed guide on how to write tests in this
directory, see [the guide on writing tests](../doc/contributing/writing-tests.md).
On how to run tests in this directory, see
[the contributing guide](../doc/contributing/pull-requests.md#step-6-test).
For the tests to run on Windows, be sure to clone Node.js source code with the
`autocrlf` git config flag set to true.
## Test Directories
| Directory | Runs on CI | Purpose |
| ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------- |
| `abort` | Yes | Tests that use `--abort-on-uncaught-exception` and other cases where we want to avoid generating a core file. |
| `addons` | Yes | Tests for [addon][] functionality along with some tests that require an addon. |
| `async-hooks` | Yes | Tests for [async\_hooks][async_hooks] functionality. |
| `benchmark` | Yes | Test minimal functionality of benchmarks. |
| `cctest` | Yes | C++ tests that are run as part of the build process. |
| `code-cache` | No | Tests for a Node.js binary compiled with V8 code cache. |
| `common` | _N/A_ | Common modules shared among many tests.[^1] |
| `doctool` | Yes | Tests for the documentation generator. |
| `es-module` | Yes | Test ESM module loading. |
| `fixtures` | _N/A_ | Test fixtures used in various tests throughout the test suite. |
| `internet` | No | Tests that make real outbound network connections.[^2] |
| `js-native-api` | Yes | Tests for Node.js-agnostic [Node-API][] functionality. |
| `known_issues` | Yes | Tests reproducing known issues within the system.[^3] |
| `message` | Yes | Tests for messages that are output for various conditions |
| `node-api` | Yes | Tests for Node.js-specific [Node-API][] functionality. |
| `parallel` | Yes | Various tests that are able to be run in parallel. |
| `pseudo-tty` | Yes | Tests that require stdin/stdout/stderr to be a TTY. |
| `pummel` | No | Various tests for various modules / system functionality operating under load. |
| `sequential` | Yes | Various tests that must not run in parallel. |
| `testpy` | _N/A_ | Test configuration utility used by various test suites. |
| `tick-processor` | No | Tests for the V8 tick processor integration.[^4] |
| `v8-updates` | No | Tests for V8 performance integration. |
[^1]: [Documentation](../test/common/README.md)
[^2]: Tests for networking related modules may also be present in other directories, but those tests do
not make outbound connections.
[^3]: All tests inside of this directory are expected to fail. If a test doesn't fail on certain platforms,
those should be skipped via `known_issues.status`.
[^4]: The tests are for the logic in `lib/internal/v8_prof_processor.js` and `lib/internal/v8_prof_polyfill.js`.
The tests confirm that the profile processor packages the correct set of scripts from V8 and introduces the
correct platform specific logic.
[Node-API]: https://nodejs.org/api/n-api.html
[addon]: https://nodejs.org/api/addons.html
[async_hooks]: https://nodejs.org/api/async_hooks.html

7
test/abort/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.buildstamp
.docbuildstamp
Makefile
*.Makefile
*.mk
gyp-mac-tool
/*/build

21
test/abort/abort.status Normal file
View File

@ -0,0 +1,21 @@
prefix abort
# To mark a test as flaky, list the test name in the appropriate section
# below, without ".js", followed by ": PASS,FLAKY". Example:
# sample-test : PASS,FLAKY
[true] # This section applies to all platforms
[$system==ibmi]
# https://github.com/nodejs/node/issues/34410
test-addon-register-signal-handler: PASS,FLAKY
[$system==solaris] # Also applies to SmartOS
# https://github.com/nodejs/node/issues/43457
test-abort-fatal-error: PASS, FLAKY
test-abort-uncaught-exception: PASS, FLAKY
test-addon-register-signal-handler: PASS, FLAKY
test-process-abort-exitcode: PASS, FLAKY
test-signal-handler: PASS, FLAKY
test-worker-abort-uncaught-exception: PASS, FLAKY
test-zlib-invalid-internals-usage: PASS, FLAKY

View File

@ -0,0 +1,70 @@
'use strict';
require('../common');
const assert = require('assert');
const cp = require('child_process');
function getPrintedStackTrace(stderr) {
const lines = stderr.split('\n');
let state = 'initial';
const result = {
message: [],
nativeStack: [],
jsStack: [],
};
for (let i = 0; i < lines.length; ++i) {
const line = lines[i].trim();
if (line.length === 0) {
continue; // Skip empty lines.
}
switch (state) {
case 'initial':
result.message.push(line);
if (line.includes('Native stack trace')) {
state = 'native-stack';
} else {
result.message.push(line);
}
break;
case 'native-stack':
if (line.includes('JavaScript stack trace')) {
state = 'js-stack';
} else {
result.nativeStack.push(line);
}
break;
case 'js-stack':
result.jsStack.push(line);
break;
}
}
return result;
}
if (process.argv[2] === 'child') {
process.abort();
} else {
const child = cp.spawnSync(`${process.execPath}`, [`${__filename}`, 'child']);
const stderr = child.stderr.toString();
assert.strictEqual(child.stdout.toString(), '');
const { nativeStack, jsStack } = getPrintedStackTrace(stderr);
if (!nativeStack.every((frame, index) => frame.startsWith(`${index + 1}:`))) {
assert.fail(`Each frame should start with a frame number:\n${stderr}`);
}
// For systems that don't support backtraces, the native stack is
// going to be empty.
if (process.platform !== 'win32' && nativeStack.length > 0) {
const { getBinaryPath } = require('../common/shared-lib-util');
if (!nativeStack.some((frame) => frame.includes(`[${getBinaryPath()}]`))) {
assert.fail(`Some native stack frame include the binary name:\n${stderr}`);
}
}
if (jsStack.length > 0) {
assert(jsStack.some((frame) => frame.includes(__filename)));
}
}

View File

@ -0,0 +1,43 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
if (common.isWindows)
common.skip('no RLIMIT_NOFILE on Windows');
const assert = require('assert');
const exec = require('child_process').exec;
const cmdline =
common.escapePOSIXShell`ulimit -c 0; "${
process.execPath
}" --max-old-space-size=16 --max-semi-space-size=4 -e "a = []; for (i = 0; i < 1e9; i++) { a.push({}) }"`;
exec(...cmdline, common.mustCall((err, stdout, stderr) => {
if (!err) {
console.log(stdout);
console.log(stderr);
assert(false, 'this test should fail');
}
assert(common.nodeProcessAborted(err.code, err.signal));
}));

View File

@ -0,0 +1,40 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const spawn = require('child_process').spawn;
const vm = require('vm');
const node = process.execPath;
if (process.argv[2] === 'child') {
throw new Error('child error');
} else if (process.argv[2] === 'vm') {
// Refs: https://github.com/nodejs/node/issues/13258
// This *should* still crash.
new vm.Script('[', {});
} else {
run('', 'child', null);
run('--abort-on-uncaught-exception', 'child',
['SIGABRT', 'SIGTRAP', 'SIGILL']);
run('--abort-on-uncaught-exception', 'vm', ['SIGABRT', 'SIGTRAP', 'SIGILL']);
}
function run(flags, argv2, signals) {
const args = [__filename, argv2];
if (flags)
args.unshift(flags);
const child = spawn(node, args);
child.on('exit', common.mustCall(function(code, sig) {
if (common.isWindows) {
if (signals)
assert.strictEqual(code, 0x80000003);
else
assert.strictEqual(code, 1);
} else if (signals) {
assert(signals.includes(sig), `Unexpected signal ${sig}`);
} else {
assert.strictEqual(sig, null);
}
}));
}

View File

@ -0,0 +1,7 @@
'use strict';
require('../common');
// This is a sibling test to test/addons/register-signal-handler/
process.env.ALLOW_CRASHES = true;
require('../addons/register-signal-handler/test');

View File

@ -0,0 +1,136 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const cp = require('child_process');
const { spawnSync } = require('child_process');
// This is a sibling test to test/addons/uv-handle-leak.
const bindingPath = path.resolve(
__dirname, '..', 'addons', 'uv-handle-leak', 'build',
`${common.buildType}/binding.node`);
if (!fs.existsSync(bindingPath))
common.skip('binding not built yet');
if (process.argv[2] === 'child') {
const { Worker } = require('worker_threads');
// The worker thread loads and then unloads `bindingPath`. Because of this the
// symbols in `bindingPath` are lost when the worker thread quits, but the
// number of open handles in the worker thread's event loop is assessed in the
// main thread afterwards, and the names of the callbacks associated with the
// open handles is retrieved at that time as well. Thus, we require
// `bindingPath` here so that the symbols and their names survive the life
// cycle of the worker thread.
require(bindingPath);
new Worker(`
const binding = require(${JSON.stringify(bindingPath)});
binding.leakHandle();
binding.leakHandle(0);
binding.leakHandle(0x42);
`, { eval: true });
} else {
const child = cp.spawnSync(process.execPath, [__filename, 'child']);
const stderr = child.stderr.toString();
assert.strictEqual(child.stdout.toString(), '');
const lines = stderr.split('\n');
let state = 'initial';
// Parse output that is formatted like this:
// uv loop at [0x559b65ed5770] has open handles:
// [0x7f2de0018430] timer (active)
// Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
// Data: 0x7f2df33df140 example_instance [...]
// (First field): 0x7f2df33dedc0 vtable for ExampleOwnerClass [...]
// [0x7f2de000b870] timer
// Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
// Data: (nil)
// [0x7f2de000b910] timer
// Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
// Data: 0x42
// uv loop at [0x559b65ed5770] has 3 open handles in total
function isGlibc() {
try {
const lddOut = spawnSync('ldd', [process.execPath]).stdout;
const libcInfo = lddOut.toString().split('\n').map(
(line) => line.match(/libc\.so.+=>\s*(\S+)\s/)).filter((info) => info);
if (libcInfo.length === 0)
return false;
const nmOut = spawnSync('nm', ['-D', libcInfo[0][1]]).stdout;
if (/gnu_get_libc_version/.test(nmOut))
return true;
} catch {
return false;
}
}
if (!(common.isFreeBSD ||
common.isAIX ||
common.isIBMi ||
(common.isLinux && !isGlibc()) ||
common.isWindows)) {
assert(stderr.includes('ExampleOwnerClass'), stderr);
assert(stderr.includes('CloseCallback'), stderr);
assert(stderr.includes('example_instance'), stderr);
}
while (lines.length > 0) {
const line = lines.shift().trim();
if (line.length === 0) {
continue; // Skip empty lines.
}
switch (state) {
case 'initial':
assert.match(line, /^uv loop at \[.+\] has open handles:$/);
state = 'handle-start';
break;
case 'handle-start':
if (/^uv loop at \[.+\] has \d+ open handles in total$/.test(line)) {
state = 'source-line';
break;
}
assert.match(line, /^\[.+\] timer( \(active\))?$/);
state = 'close-callback';
break;
case 'close-callback':
assert.match(line, /^Close callback:/);
state = 'data';
break;
case 'data':
assert.match(line, /^Data: .+$/);
state = 'maybe-first-field';
break;
case 'maybe-first-field':
if (!/^\(First field\)/.test(line)) {
lines.unshift(line);
}
state = 'handle-start';
break;
case 'source-line':
assert.match(line, /CheckedUvLoopClose/);
state = 'assertion-failure';
break;
case 'assertion-failure':
assert.match(line, /Assertion failed:/);
state = 'done';
break;
case 'done':
break;
}
}
assert.strictEqual(state, 'done');
}

View File

@ -0,0 +1,33 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { createServer, get } = require('http');
const { spawn } = require('child_process');
if (process.argv[2] === 'child') {
// sub-process
const server = createServer(common.mustCall((_, res) => res.end('h')));
server.listen(0, common.mustCall(() => {
const rr = get({ port: server.address().port }, common.mustCall(() => {
// This bad input (0) should abort the parser and the process
rr.parser.consume(0);
// This line should be unreachable.
assert.fail('this should be unreachable');
}));
}));
} else {
// super-process
const child = spawn(process.execPath, [__filename, 'child']);
child.stdout.on('data', common.mustNotCall());
let stderr = '';
child.stderr.on('data', common.mustCallAtLeast((data) => {
assert(Buffer.isBuffer(data));
stderr += data.toString('utf8');
}, 1));
child.on('exit', common.mustCall((code, signal) => {
assert(stderr.includes('failed'), `stderr: ${stderr}`);
const didAbort = common.nodeProcessAborted(code, signal);
assert(didAbort, `process did not abort, code:${code} signal:${signal}`);
}));
}

View File

@ -0,0 +1,24 @@
'use strict';
const common = require('../common');
const assert = require('assert');
// This test makes sure that an aborted node process
// exits with code 3 on Windows, and SIGABRT on POSIX.
// Spawn a child, force an abort, and then check the
// exit code in the parent.
const spawn = require('child_process').spawn;
if (process.argv[2] === 'child') {
process.abort();
} else {
const child = spawn(process.execPath, [__filename, 'child']);
child.on('exit', common.mustCall((code, signal) => {
if (common.isWindows) {
assert.strictEqual(code, 134);
assert.strictEqual(signal, null);
} else {
assert.strictEqual(code, null);
assert.strictEqual(signal, 'SIGABRT');
}
}));
}

View File

@ -0,0 +1,27 @@
'use strict';
const common = require('../common');
if (common.isWindows)
common.skip('No signals on Window');
const assert = require('assert');
const { spawnSync } = require('child_process');
// Test that a hard crash does not cause an endless loop.
if (process.argv[2] === 'child') {
const { internalBinding } = require('internal/test/binding');
const { causeSegfault } = internalBinding('process_methods');
causeSegfault();
} else {
const child = spawnSync(process.execPath,
['--expose-internals', __filename, 'child'],
{ stdio: 'inherit' });
// FreeBSD uses SIGILL (v12.2) or SIGBUS (v12.4 and greater) for this kind of crash.
// macOS uses SIGILL or SIGTRAP (arm64) for this kind of crash.
const allowedSignals = ['SIGSEGV', 'SIGILL', 'SIGTRAP', 'SIGBUS'];
assert(
allowedSignals.includes(child.signal),
`child.signal = ${child.signal}`,
);
}

View File

@ -0,0 +1,24 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { spawn } = require('child_process');
const { Worker } = require('worker_threads');
// Tests that --abort-on-uncaught-exception applies to workers as well.
if (process.argv[2] === 'child') {
new Worker('throw new Error("foo");', { eval: true });
return;
}
const child = spawn(process.execPath, [
'--abort-on-uncaught-exception', __filename, 'child',
]);
child.on('exit', common.mustCall((code, sig) => {
if (common.isWindows) {
assert.strictEqual(code, 0x80000003);
} else {
assert(['SIGABRT', 'SIGTRAP', 'SIGILL'].includes(sig),
`Unexpected signal ${sig}`);
}
}));

View File

@ -0,0 +1,22 @@
'use strict';
require('../common');
const assert = require('assert');
const os = require('os');
const cp = require('child_process');
if (process.argv[2] === 'child') {
const { internalBinding } = require('internal/test/binding');
// This is the heart of the test.
new (internalBinding('zlib').Zlib)(0).init(1, 2, 3, 4, 5);
} else {
const child = cp.spawnSync(
`${process.execPath}`, ['--expose-internals', `${__filename}`, 'child']);
assert.strictEqual(child.stdout.toString(), '');
assert.ok(child.stderr.includes(
'WARNING: You are likely using a version of node-tar or npm that ' +
`is incompatible with this version of Node.js.${os.EOL}` +
'Please use either the version of npm that is bundled with Node.js, or ' +
'a version of npm (> 5.5.1 or < 5.4.0) or node-tar (> 4.0.1) that is ' +
`compatible with Node.js 9 and above.${os.EOL}`));
}

6
test/abort/testcfg.py Normal file
View File

@ -0,0 +1,6 @@
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import testpy
def GetConfiguration(context, root):
return testpy.AbortTestConfiguration(context, root, 'abort')

8
test/addons/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.buildstamp
.docbuildstamp
Makefile
*.Makefile
*.mk
gyp-mac-tool
/*/build
/esm/node_modules/*/build

22
test/addons/addons.status Normal file
View File

@ -0,0 +1,22 @@
prefix addons
# To mark a test as flaky, list the test name in the appropriate section
# below, without ".js", followed by ": PASS,FLAKY". Example:
# sample-test : PASS,FLAKY
[true] # This section applies to all platforms
[$arch==arm]
# https://github.com/nodejs/node/issues/30786
openssl-binding/test: PASS,FLAKY
[$system==ibmi]
openssl-binding/test: SKIP
openssl-providers/test-default-only-config: SKIP
openssl-providers/test-legacy-provider-config: SKIP
openssl-providers/test-legacy-provider-inactive-config: SKIP
openssl-providers/test-legacy-provider-option: SKIP
openssl-providers/test-no-legacy-provider-option: SKIP
zlib-binding/test: SKIP
# https://github.com/nodejs/node/issues/34410
register-signal-handler/test: PASS,FLAKY

View File

@ -0,0 +1,59 @@
#include <node.h>
#include <uv.h>
#include <assert.h>
void MustNotCall(void* arg, void(*cb)(void*), void* cbarg) {
assert(0);
}
struct AsyncData {
uv_async_t async;
v8::Isolate* isolate;
node::AsyncCleanupHookHandle handle;
void (*done_cb)(void*);
void* done_arg;
};
void AsyncCleanupHook(void* arg, void(*cb)(void*), void* cbarg) {
AsyncData* data = static_cast<AsyncData*>(arg);
uv_loop_t* loop = node::GetCurrentEventLoop(data->isolate);
assert(loop != nullptr);
int err = uv_async_init(loop, &data->async, [](uv_async_t* async) {
AsyncData* data = static_cast<AsyncData*>(async->data);
// Attempting to remove the cleanup hook here should be a no-op since it
// has already been started.
node::RemoveEnvironmentCleanupHook(std::move(data->handle));
uv_close(reinterpret_cast<uv_handle_t*>(async), [](uv_handle_t* handle) {
AsyncData* data = static_cast<AsyncData*>(handle->data);
data->done_cb(data->done_arg);
delete data;
});
});
assert(err == 0);
data->async.data = data;
data->done_cb = cb;
data->done_arg = cbarg;
uv_async_send(&data->async);
}
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> context) {
AsyncData* data = new AsyncData();
data->isolate = context->GetIsolate();
auto handle = node::AddEnvironmentCleanupHook(
context->GetIsolate(),
AsyncCleanupHook,
data);
data->handle = std::move(handle);
auto must_not_call_handle = node::AddEnvironmentCleanupHook(
context->GetIsolate(),
MustNotCall,
nullptr);
node::RemoveEnvironmentCleanupHook(std::move(must_not_call_handle));
}
NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,8 @@
'use strict';
const common = require('../../common');
const path = require('path');
const { Worker } = require('worker_threads');
const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`);
const w = new Worker(`require(${JSON.stringify(binding)})`, { eval: true });
w.on('exit', common.mustCall(() => require(binding)));

View File

@ -0,0 +1,86 @@
#include <node.h>
#include <v8.h>
#include <uv.h>
struct async_req {
uv_work_t req;
int input;
int output;
v8::Isolate* isolate;
v8::Global<v8::Function> callback;
node::async_context context;
};
void DoAsync(uv_work_t* r) {
async_req* req = reinterpret_cast<async_req*>(r->data);
// Simulate CPU intensive process...
uv_sleep(1000);
req->output = req->input * 2;
}
template <bool use_makecallback>
void AfterAsync(uv_work_t* r) {
async_req* req = reinterpret_cast<async_req*>(r->data);
v8::Isolate* isolate = req->isolate;
v8::HandleScope scope(isolate);
v8::Local<v8::Value> argv[2] = {
v8::Null(isolate),
v8::Integer::New(isolate, req->output)
};
v8::TryCatch try_catch(isolate);
v8::Local<v8::Object> global = isolate->GetCurrentContext()->Global();
v8::Local<v8::Function> callback =
v8::Local<v8::Function>::New(isolate, req->callback);
if (use_makecallback) {
v8::Local<v8::Value> ret =
node::MakeCallback(isolate, global, callback, 2, argv, req->context)
.ToLocalChecked();
// This should be changed to an empty handle.
assert(!ret.IsEmpty());
} else {
callback->Call(isolate->GetCurrentContext(),
global, 2, argv).ToLocalChecked();
}
// None of the following operations should allocate handles into this scope.
v8::SealHandleScope seal_handle_scope(isolate);
// cleanup
node::EmitAsyncDestroy(isolate, req->context);
delete req;
if (try_catch.HasCaught()) {
node::FatalException(isolate, try_catch);
}
}
template <bool use_makecallback>
void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
async_req* req = new async_req;
req->req.data = req;
req->input = args[0].As<v8::Integer>()->Value();
req->output = 0;
req->isolate = isolate;
req->context = node::EmitAsyncInit(isolate, v8::Object::New(isolate), "test");
v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(args[1]);
req->callback.Reset(isolate, callback);
uv_queue_work(node::GetCurrentEventLoop(isolate),
&req->req,
DoAsync,
(uv_after_work_cb)AfterAsync<use_makecallback>);
}
void init(v8::Local<v8::Object> exports, v8::Local<v8::Object> module) {
NODE_SET_METHOD(exports, "runCall", Method<false>);
NODE_SET_METHOD(exports, "runMakeCallback", Method<true>);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,9 @@
'use strict';
const common = require('../../common');
const { runMakeCallback } = require(`./build/${common.buildType}/binding`);
process.on('uncaughtException', common.mustCall());
runMakeCallback(5, common.mustCall(() => {
throw new Error('foo');
}));

View File

@ -0,0 +1,10 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const { runMakeCallback } = require(`./build/${common.buildType}/binding`);
runMakeCallback(5, common.mustCall((err, val) => {
assert.strictEqual(err, null);
assert.strictEqual(val, 10);
process.nextTick(common.mustCall());
}));

View File

@ -0,0 +1,10 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const { runCall } = require(`./build/${common.buildType}/binding`);
runCall(5, common.mustCall((err, val) => {
assert.strictEqual(err, null);
assert.strictEqual(val, 10);
process.nextTick(common.mustCall());
}));

View File

@ -0,0 +1,35 @@
#include "node.h"
namespace {
using v8::FunctionCallbackInfo;
using v8::Local;
using v8::Object;
using v8::Value;
void GetExecutionAsyncId(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(
node::AsyncHooksGetExecutionAsyncId(args.GetIsolate()));
}
void GetExecutionAsyncIdWithContext(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(node::AsyncHooksGetExecutionAsyncId(
args.GetIsolate()->GetCurrentContext()));
}
void GetTriggerAsyncId(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(
node::AsyncHooksGetTriggerAsyncId(args.GetIsolate()));
}
void Initialize(Local<Object> exports) {
NODE_SET_METHOD(exports, "getExecutionAsyncId", GetExecutionAsyncId);
NODE_SET_METHOD(exports,
"getExecutionAsyncIdWithContext",
GetExecutionAsyncIdWithContext);
NODE_SET_METHOD(exports, "getTriggerAsyncId", GetTriggerAsyncId);
}
} // anonymous namespace
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,34 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const binding = require(`./build/${common.buildType}/binding`);
const async_hooks = require('async_hooks');
assert.strictEqual(
binding.getExecutionAsyncId(),
async_hooks.executionAsyncId(),
);
assert.strictEqual(
binding.getExecutionAsyncIdWithContext(),
async_hooks.executionAsyncId(),
);
assert.strictEqual(
binding.getTriggerAsyncId(),
async_hooks.triggerAsyncId(),
);
process.nextTick(common.mustCall(() => {
assert.strictEqual(
binding.getExecutionAsyncId(),
async_hooks.executionAsyncId(),
);
assert.strictEqual(
binding.getExecutionAsyncIdWithContext(),
async_hooks.executionAsyncId(),
);
assert.strictEqual(
binding.getTriggerAsyncId(),
async_hooks.triggerAsyncId(),
);
}));

View File

@ -0,0 +1,134 @@
#include "node.h"
#include <assert.h>
#include <vector>
namespace {
using node::AsyncResource;
using v8::External;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::Object;
using v8::String;
using v8::Value;
int custom_async_resource_destructor_calls = 0;
class CustomAsyncResource : public AsyncResource {
public:
CustomAsyncResource(Isolate* isolate, Local<Object> resource)
: AsyncResource(isolate, resource, "CustomAsyncResource") {}
~CustomAsyncResource() {
custom_async_resource_destructor_calls++;
}
};
void CreateAsyncResource(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
assert(args[0]->IsObject());
AsyncResource* r;
if (args[1]->IsInt32()) {
r = new AsyncResource(isolate, args[0].As<Object>(), "foobär",
args[1].As<Integer>()->Value());
} else {
r = new AsyncResource(isolate, args[0].As<Object>(), "foobär");
}
args.GetReturnValue().Set(
External::New(isolate, static_cast<void*>(r)));
}
void DestroyAsyncResource(const FunctionCallbackInfo<Value>& args) {
assert(args[0]->IsExternal());
auto r = static_cast<AsyncResource*>(args[0].As<External>()->Value());
delete r;
}
void CallViaFunction(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
assert(args[0]->IsExternal());
auto r = static_cast<AsyncResource*>(args[0].As<External>()->Value());
Local<String> name =
String::NewFromUtf8(isolate, "methöd").ToLocalChecked();
Local<Value> fn =
r->get_resource()->Get(isolate->GetCurrentContext(), name)
.ToLocalChecked();
assert(fn->IsFunction());
Local<Value> arg = Integer::New(isolate, 42);
MaybeLocal<Value> ret = r->MakeCallback(fn.As<Function>(), 1, &arg);
args.GetReturnValue().Set(ret.FromMaybe(Local<Value>()));
}
void CallViaString(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
assert(args[0]->IsExternal());
auto r = static_cast<AsyncResource*>(args[0].As<External>()->Value());
Local<String> name =
String::NewFromUtf8(isolate, "methöd").ToLocalChecked();
Local<Value> arg = Integer::New(isolate, 42);
MaybeLocal<Value> ret = r->MakeCallback(name, 1, &arg);
args.GetReturnValue().Set(ret.FromMaybe(Local<Value>()));
}
void CallViaUtf8Name(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
assert(args[0]->IsExternal());
auto r = static_cast<AsyncResource*>(args[0].As<External>()->Value());
Local<Value> arg = Integer::New(isolate, 42);
MaybeLocal<Value> ret = r->MakeCallback("methöd", 1, &arg);
args.GetReturnValue().Set(ret.FromMaybe(Local<Value>()));
}
void GetAsyncId(const FunctionCallbackInfo<Value>& args) {
assert(args[0]->IsExternal());
auto r = static_cast<AsyncResource*>(args[0].As<External>()->Value());
args.GetReturnValue().Set(r->get_async_id());
}
void GetTriggerAsyncId(const FunctionCallbackInfo<Value>& args) {
assert(args[0]->IsExternal());
auto r = static_cast<AsyncResource*>(args[0].As<External>()->Value());
args.GetReturnValue().Set(r->get_trigger_async_id());
}
void GetResource(const FunctionCallbackInfo<Value>& args) {
assert(args[0]->IsExternal());
auto r = static_cast<AsyncResource*>(args[0].As<External>()->Value());
args.GetReturnValue().Set(r->get_resource());
}
void RunSubclassTest(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Object> obj = Object::New(isolate);
assert(custom_async_resource_destructor_calls == 0);
CustomAsyncResource* resource = new CustomAsyncResource(isolate, obj);
delete static_cast<AsyncResource*>(resource);
assert(custom_async_resource_destructor_calls == 1);
}
void Initialize(Local<Object> exports) {
NODE_SET_METHOD(exports, "createAsyncResource", CreateAsyncResource);
NODE_SET_METHOD(exports, "destroyAsyncResource", DestroyAsyncResource);
NODE_SET_METHOD(exports, "callViaFunction", CallViaFunction);
NODE_SET_METHOD(exports, "callViaString", CallViaString);
NODE_SET_METHOD(exports, "callViaUtf8Name", CallViaUtf8Name);
NODE_SET_METHOD(exports, "getAsyncId", GetAsyncId);
NODE_SET_METHOD(exports, "getTriggerAsyncId", GetTriggerAsyncId);
NODE_SET_METHOD(exports, "getResource", GetResource);
NODE_SET_METHOD(exports, "runSubclassTest", RunSubclassTest);
}
} // anonymous namespace
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,82 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const binding = require(`./build/${common.buildType}/binding`);
const async_hooks = require('async_hooks');
binding.runSubclassTest();
const kObjectTag = Symbol('kObjectTag');
const rootAsyncId = async_hooks.executionAsyncId();
const bindingUids = [];
let expectedTriggerId;
let before = 0;
let after = 0;
let destroy = 0;
async_hooks.createHook({
init(id, type, triggerAsyncId, resource) {
assert.strictEqual(typeof id, 'number');
assert.strictEqual(typeof resource, 'object');
assert(id > 1);
if (type === 'foobär') {
assert.strictEqual(resource.kObjectTag, kObjectTag);
assert.strictEqual(triggerAsyncId, expectedTriggerId);
bindingUids.push(id);
}
},
before(id) {
if (bindingUids.includes(id)) before++;
},
after(id) {
if (bindingUids.includes(id)) after++;
},
destroy(id) {
if (bindingUids.includes(id)) destroy++;
},
}).enable();
for (const call of [binding.callViaFunction,
binding.callViaString,
binding.callViaUtf8Name]) {
for (const passedTriggerId of [undefined, 12345]) {
let uid;
const object = {
methöd(arg) {
assert.strictEqual(this, object);
assert.strictEqual(arg, 42);
assert.strictEqual(async_hooks.executionAsyncId(), uid);
return 'baz';
},
kObjectTag,
};
if (passedTriggerId === undefined)
expectedTriggerId = rootAsyncId;
else
expectedTriggerId = passedTriggerId;
const resource = binding.createAsyncResource(object, passedTriggerId);
uid = bindingUids[bindingUids.length - 1];
const ret = call(resource);
assert.strictEqual(ret, 'baz');
assert.strictEqual(binding.getResource(resource), object);
assert.strictEqual(binding.getAsyncId(resource), uid);
assert.strictEqual(binding.getTriggerAsyncId(resource), expectedTriggerId);
binding.destroyAsyncResource(resource);
}
}
setImmediate(common.mustCall(() => {
assert.strictEqual(bindingUids.length, 6);
assert.strictEqual(before, bindingUids.length);
assert.strictEqual(after, bindingUids.length);
assert.strictEqual(destroy, bindingUids.length);
}));

View File

@ -0,0 +1,44 @@
#include <node.h>
#include <node_buffer.h>
#include <v8.h>
#include <assert.h>
static int alive;
static char buf[1024];
static void FreeCallback(char* data, void* hint) {
alive--;
}
void Alloc(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
alive++;
uintptr_t alignment = args[1].As<v8::Integer>()->Value();
uintptr_t offset = args[2].As<v8::Integer>()->Value();
uintptr_t static_offset = reinterpret_cast<uintptr_t>(buf) % alignment;
char* aligned = buf + (alignment - static_offset) + offset;
args.GetReturnValue().Set(node::Buffer::New(
isolate,
aligned,
args[0].As<v8::Integer>()->Value(),
FreeCallback,
nullptr).ToLocalChecked());
}
void Check(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
isolate->RequestGarbageCollectionForTesting(
v8::Isolate::kFullGarbageCollection);
assert(alive > 0);
}
void init(v8::Local<v8::Object> exports) {
NODE_SET_METHOD(exports, "alloc", Alloc);
NODE_SET_METHOD(exports, "check", Check);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,39 @@
'use strict';
// Flags: --expose-gc
const common = require('../../common');
const binding = require(`./build/${common.buildType}/binding`);
function check(size, alignment, offset) {
let buf = binding.alloc(size, alignment, offset);
let slice = buf.slice(size >>> 1);
buf = null;
binding.check(slice);
slice = null;
global.gc();
global.gc();
global.gc();
}
// NOTE: If adding more check() test cases,
// be sure to not duplicate alignment/offset.
// Refs: https://github.com/nodejs/node/issues/31061#issuecomment-568612283
check(64, 1, 0);
// Buffers can have weird sizes.
check(97, 1, 1);
// Buffers can be unaligned
check(64, 8, 0);
check(64, 16, 0);
check(64, 8, 1);
check(64, 16, 1);
check(97, 8, 3);
check(97, 16, 3);
check(97, 8, 5);
check(97, 16, 5);
// Empty ArrayBuffer does not allocate data, worth checking
check(0, 1, 2);

View File

@ -0,0 +1,77 @@
#include "node.h"
#include "v8.h"
#include "uv.h"
#include <assert.h>
#include <vector>
#include <memory>
namespace {
void RunInCallbackScope(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
assert(args.Length() == 4);
assert(args[0]->IsObject());
assert(args[1]->IsNumber());
assert(args[2]->IsNumber());
assert(args[3]->IsFunction());
node::async_context asyncContext = {
args[1].As<v8::Number>()->Value(),
args[2].As<v8::Number>()->Value()
};
node::CallbackScope scope(isolate, args[0].As<v8::Object>(), asyncContext);
v8::Local<v8::Function> fn = args[3].As<v8::Function>();
v8::MaybeLocal<v8::Value> ret =
fn->Call(isolate->GetCurrentContext(), args[0], 0, nullptr);
if (!ret.IsEmpty())
args.GetReturnValue().Set(ret.ToLocalChecked());
}
static void Callback(uv_work_t* req, int ignored) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope scope(isolate);
node::CallbackScope callback_scope(isolate, v8::Object::New(isolate),
node::async_context{0, 0});
std::unique_ptr<v8::Global<v8::Promise::Resolver>> persistent {
static_cast<v8::Global<v8::Promise::Resolver>*>(req->data) };
v8::Local<v8::Promise::Resolver> local = persistent->Get(isolate);
local->Resolve(isolate->GetCurrentContext(),
v8::Undefined(isolate)).ToChecked();
delete req;
}
static void TestResolveAsync(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
v8::Global<v8::Promise::Resolver>* persistent =
new v8::Global<v8::Promise::Resolver>(
isolate,
v8::Promise::Resolver::New(
isolate->GetCurrentContext()).ToLocalChecked());
uv_work_t* req = new uv_work_t;
req->data = static_cast<void*>(persistent);
uv_queue_work(node::GetCurrentEventLoop(isolate),
req,
[](uv_work_t*) {},
Callback);
v8::Local<v8::Promise::Resolver> local = persistent->Get(isolate);
args.GetReturnValue().Set(local->GetPromise());
}
void Initialize(v8::Local<v8::Object> exports) {
NODE_SET_METHOD(exports, "runInCallbackScope", RunInCallbackScope);
NODE_SET_METHOD(exports, "testResolveAsync", TestResolveAsync);
}
} // anonymous namespace
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,22 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const async_hooks = require('async_hooks');
const { runInCallbackScope } = require(`./build/${common.buildType}/binding`);
let insideHook = false;
async_hooks.createHook({
before: common.mustCall((id) => {
assert.strictEqual(id, 1000);
insideHook = true;
}),
after: common.mustCall((id) => {
assert.strictEqual(id, 1000);
insideHook = false;
}),
}).enable();
runInCallbackScope({}, 1000, 1000, () => {
assert(insideHook);
});

View File

@ -0,0 +1,12 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const { testResolveAsync } = require(`./build/${common.buildType}/binding`);
// Checks that resolving promises from C++ works.
let called = false;
testResolveAsync().then(() => { called = true; });
process.on('beforeExit', () => { assert(called); });

View File

@ -0,0 +1,17 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const { runInCallbackScope } = require(`./build/${common.buildType}/binding`);
assert.strictEqual(runInCallbackScope({}, 0, 0, () => 42), 42);
{
process.once('uncaughtException', common.mustCall((err) => {
assert.strictEqual(err.message, 'foo');
}));
runInCallbackScope({}, 0, 0, () => {
throw new Error('foo');
});
}

8
test/addons/common.gypi Normal file
View File

@ -0,0 +1,8 @@
{
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
'conditions': [
[ 'OS in "linux freebsd openbsd solaris android aix os400 cloudabi"', {
'cflags': ['-Wno-cast-function-type'],
}],
],
}

View File

@ -0,0 +1,76 @@
#include <assert.h>
#include <cppgc/allocation.h>
#include <cppgc/garbage-collected.h>
#include <cppgc/heap.h>
#include <node.h>
#include <v8-cppgc.h>
#include <v8-sandbox.h>
#include <v8.h>
#include <algorithm>
class CppGCed : public cppgc::GarbageCollected<CppGCed> {
public:
static uint16_t states[2];
static constexpr int kDestructCount = 0;
static constexpr int kTraceCount = 1;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
v8::Local<v8::Object> js_object = args.This();
auto* heap = isolate->GetCppHeap();
assert(heap != nullptr);
CppGCed* gc_object =
cppgc::MakeGarbageCollected<CppGCed>(heap->GetAllocationHandle());
node::SetCppgcReference(isolate, js_object, gc_object);
args.GetReturnValue().Set(js_object);
}
static v8::Local<v8::Function> GetConstructor(
v8::Local<v8::Context> context) {
auto ft = v8::FunctionTemplate::New(context->GetIsolate(), New);
return ft->GetFunction(context).ToLocalChecked();
}
CppGCed() = default;
~CppGCed() { states[kDestructCount]++; }
void Trace(cppgc::Visitor* visitor) const { states[kTraceCount]++; }
};
uint16_t CppGCed::states[] = {0, 0};
void InitModule(v8::Local<v8::Object> exports) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
auto context = isolate->GetCurrentContext();
auto store = v8::ArrayBuffer::NewBackingStore(
CppGCed::states,
sizeof(uint16_t) * 2,
[](void*, size_t, void*) {},
nullptr);
auto ab = v8::ArrayBuffer::New(isolate, std::move(store));
exports
->Set(context,
v8::String::NewFromUtf8(isolate, "CppGCed").ToLocalChecked(),
CppGCed::GetConstructor(context))
.FromJust();
exports
->Set(context,
v8::String::NewFromUtf8(isolate, "states").ToLocalChecked(),
v8::Uint16Array::New(ab, 0, 2))
.FromJust();
exports
->Set(context,
v8::String::NewFromUtf8(isolate, "kDestructCount").ToLocalChecked(),
v8::Integer::New(isolate, CppGCed::kDestructCount))
.FromJust();
exports
->Set(context,
v8::String::NewFromUtf8(isolate, "kTraceCount").ToLocalChecked(),
v8::Integer::New(isolate, CppGCed::kTraceCount))
.FromJust();
}
NODE_MODULE(NODE_GYP_MODULE_NAME, InitModule)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,51 @@
'use strict';
// Flags: --expose-gc
const common = require('../../common');
const { gcUntil } = require('../../common/gc');
// Verify that addons can create GarbageCollected objects and
// have them traced properly.
const assert = require('assert');
const {
CppGCed, states, kDestructCount, kTraceCount,
} = require(`./build/${common.buildType}/binding`);
assert.strictEqual(states[kDestructCount], 0);
assert.strictEqual(states[kTraceCount], 0);
let array = [];
const count = 100;
for (let i = 0; i < count; ++i) {
array.push(new CppGCed());
}
globalThis.gc();
setTimeout(async function() {
// GC should have invoked Trace() on at least some of the CppGCed objects,
// but they should all be alive at this point.
assert.strictEqual(states[kDestructCount], 0);
assert.notStrictEqual(states[kTraceCount], 0);
// Replace the old CppGCed objects with new ones, after GC we should have
// destructed all the old ones and called Trace() on the
// new ones.
for (let i = 0; i < count; ++i) {
array[i] = new CppGCed();
}
await gcUntil(
'All old CppGCed are destroyed',
() => states[kDestructCount] === count,
);
// Release all the CppGCed objects, after GC we should have destructed
// all of them.
array = null;
globalThis.gc();
await gcUntil(
'All old CppGCed are destroyed',
() => states[kDestructCount] === count * 2,
);
}, 1);

View File

@ -0,0 +1,52 @@
#include <node.h>
#include <v8.h>
#ifndef _WIN32
#include <dlfcn.h>
extern "C"
__attribute__((visibility("default")))
const char* dlopen_pong(void) {
return "pong";
}
namespace {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
typedef const char* (*ping)(void);
static ping ping_func;
void LoadLibrary(const FunctionCallbackInfo<Value>& args) {
const String::Utf8Value filename(args.GetIsolate(), args[0]);
void* handle = dlopen(*filename, RTLD_LAZY);
if (handle == nullptr) fprintf(stderr, "%s\n", dlerror());
assert(handle != nullptr);
ping_func = reinterpret_cast<ping>(dlsym(handle, "dlopen_ping"));
assert(ping_func != nullptr);
}
void Ping(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
assert(ping_func != nullptr);
args.GetReturnValue().Set(String::NewFromUtf8(
isolate, ping_func()).ToLocalChecked());
}
void init(Local<Object> exports) {
NODE_SET_METHOD(exports, "load", LoadLibrary);
NODE_SET_METHOD(exports, "ping", Ping);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
} // anonymous namespace
#endif // _WIN32

View File

@ -0,0 +1,24 @@
{
'targets': [
{
'target_name': 'ping',
'product_extension': 'so',
'type': 'shared_library',
'sources': [ 'ping.c' ],
'conditions': [
['OS=="mac"', {
'xcode_settings': {
'OTHER_LDFLAGS': [ '-Wl,-undefined', '-Wl,dynamic_lookup' ]
}}],
# Enable the shared object to be linked by runtime linker
['OS in "aix os400"', {
'ldflags': [ '-Wl,-G' ]
}]],
},
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,10 @@
#ifndef _WIN32
const char* dlopen_pong(void);
__attribute__((visibility("default")))
const char* dlopen_ping(void) {
return dlopen_pong();
}
#endif // _WIN32

View File

@ -0,0 +1,21 @@
'use strict';
const common = require('../../common');
if (common.isWindows)
common.skip('dlopen global symbol loading is not supported on this os.');
const assert = require('assert');
const { Worker } = require('worker_threads');
// Check that modules that are not declared as context-aware cannot be re-loaded
// from workers.
const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
require(bindingPath);
new Worker(`require(${JSON.stringify(bindingPath)})`, { eval: true })
.on('error', common.mustCall((err) => {
assert.strictEqual(err.constructor, Error);
assert.strictEqual(err.message,
`Module did not self-register: '${bindingPath}'.`);
}));

View File

@ -0,0 +1,23 @@
'use strict';
const common = require('../../common');
if (common.isWindows)
common.skip('dlopen global symbol loading is not supported on this os.');
const assert = require('assert');
const path = require('path');
const os = require('os');
const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
console.log('process.dlopen:', bindingPath);
process.dlopen(module, bindingPath,
os.constants.dlopen.RTLD_NOW | os.constants.dlopen.RTLD_GLOBAL);
console.log('module.exports.load:', `${path.dirname(bindingPath)}/ping.so`);
module.exports.load(`${path.dirname(bindingPath)}/ping.so`);
assert.strictEqual(module.exports.ping(), 'pong');
// Check that after the addon is loaded with
// process.dlopen() a require() call fails.
console.log('require:', `./build/${common.buildType}/binding`);
const re = /^Error: Module did not self-register: '.*[\\/]binding\.node'\.$/;
assert.throws(() => require(`./build/${common.buildType}/binding`), re);

View File

@ -0,0 +1,18 @@
#include <node.h>
#include <v8.h>
void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
v8::HandleScope scope(isolate);
args.GetReturnValue().Set(node::ErrnoException(isolate,
10,
"syscall",
"some error msg",
"päth"));
}
void init(v8::Local<v8::Object> exports) {
NODE_SET_METHOD(exports, "errno", Method);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,15 @@
'use strict';
const common = require('../../common');
// Verify that the path argument to node::ErrnoException() can contain UTF-8
// characters.
const assert = require('assert');
const binding = require(`./build/${common.buildType}/binding`);
const err = binding.errno();
assert.strictEqual(err.syscall, 'syscall');
assert.strictEqual(err.errno, 10);
assert.strictEqual(err.path, 'päth');
assert.match(err.toString(), /^Error:\s\w+, some error msg 'päth'$/);

View File

@ -0,0 +1,17 @@
#include <node.h>
#include <uv.h>
#include <v8.h>
static void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(
v8::String::NewFromUtf8(isolate, "hello world").ToLocalChecked());
}
static void InitModule(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> context) {
NODE_SET_METHOD(exports, "default", Method);
}
NODE_MODULE_CONTEXT_AWARE(Binding, InitModule)

View File

@ -0,0 +1,17 @@
#include <node.h>
#include <uv.h>
#include <v8.h>
static void InitModule(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module_val,
v8::Local<v8::Context> context) {
v8::Isolate* isolate = context->GetIsolate();
v8::Local<v8::Object> module = module_val.As<v8::Object>();
module
->Set(context,
v8::String::NewFromUtf8(isolate, "exports").ToLocalChecked(),
v8::String::NewFromUtf8(isolate, "hello world").ToLocalChecked())
.FromJust();
}
NODE_MODULE_CONTEXT_AWARE(Binding, InitModule)

View File

@ -0,0 +1,14 @@
{
'targets': [
{
'target_name': 'binding-export-default',
'sources': [ 'binding-export-default.cc' ],
'includes': ['../common.gypi'],
},
{
'target_name': 'binding-export-primitive',
'sources': [ 'binding-export-primitive.cc' ],
'includes': ['../common.gypi'],
},
]
}

View File

@ -0,0 +1,37 @@
/**
* This file is supposed to be loaded by `test-import.js` and `test-require.js`
* to verify that `import('*.node')` is working properly either been loaded with
* the ESM loader or the CJS loader.
*/
import { buildType } from '../../common/index.mjs';
import assert from 'node:assert';
import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
const require = createRequire(import.meta.url);
export async function run() {
// binding-export-default.node
{
const bindingPath = require.resolve(`./build/${buildType}/binding-export-default.node`);
// Test with order of require+import
const bindingRequire = require(bindingPath);
const ns = await import(pathToFileURL(bindingPath));
assert.strictEqual(ns.default, bindingRequire);
// As same as ESM-import-CJS, the default export is the value of `module.exports`.
assert.strictEqual(ns.default, ns['module.exports']);
assert.strictEqual(ns.default.default(), 'hello world');
}
// binding-export-primitive.node
{
const bindingPath = require.resolve(`./build/${buildType}/binding-export-primitive.node`);
const ns = await import(pathToFileURL(bindingPath));
// As same as ESM-import-CJS, the default export is the value of `module.exports`.
assert.strictEqual(ns.default, ns['module.exports']);
assert.strictEqual(ns.default, 'hello world');
}
}

View File

@ -0,0 +1,6 @@
// Flags: --experimental-addon-modules
'use strict';
const common = require('../../common');
require('./test-esm.mjs')
.run().then(common.mustCall());

View File

@ -0,0 +1,17 @@
#include <node.h>
#include <uv.h>
#include <v8.h>
static void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(
v8::String::NewFromUtf8(isolate, "world").ToLocalChecked());
}
static void InitModule(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> context) {
NODE_SET_METHOD(exports, "hello", Method);
}
NODE_MODULE_CONTEXT_AWARE(Binding, InitModule)

View File

@ -0,0 +1,30 @@
{
'variables': {
'source_dir': '<!("<(python)" -c "import os; print(os.getcwd())")',
},
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
},
{
'target_name': 'node_modules',
'type': 'none',
'dependencies': [
'binding',
],
# The `exports` in package.json can not reference files outside the package
# directory. Copy the `binding.node` into the package directory so that
# it can be exported with the conditional exports.
'copies': [
{
'files': [
'<(PRODUCT_DIR)/binding.node'
],
'destination': '<(source_dir)/node_modules/esm-package/build',
},
],
}
]
}

View File

@ -0,0 +1,8 @@
{
"name": "esm-package",
"exports": {
"./binding": {
"node-addons": "./build/binding.node"
}
}
}

View File

@ -0,0 +1,34 @@
/**
* This file is supposed to be loaded by `test-import.js` and `test-require.js`
* to verify that `import('*.node')` is working properly either been loaded with
* the ESM loader or the CJS loader.
*/
import { buildType } from '../../common/index.mjs';
import assert from 'node:assert';
import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
const require = createRequire(import.meta.url);
export async function run() {
// binding.node
{
const bindingPath = require.resolve(`./build/${buildType}/binding.node`);
// Test with order of import+require
const { default: binding, 'module.exports': exports } = await import(pathToFileURL(bindingPath));
assert.strictEqual(binding, exports);
assert.strictEqual(binding.hello(), 'world');
const bindingRequire = require(bindingPath);
assert.strictEqual(binding, bindingRequire);
assert.strictEqual(binding.hello, bindingRequire.hello);
// Test multiple loading of the same addon.
// ESM cache can not be removed.
delete require.cache[bindingPath];
const { default: rebinding } = await import(pathToFileURL(bindingPath));
assert.strictEqual(rebinding.hello(), 'world');
assert.strictEqual(binding.hello, rebinding.hello);
}
}

View File

@ -0,0 +1,15 @@
// Flags: --experimental-addon-modules
'use strict';
const common = require('../../common');
const assert = require('node:assert');
/**
* Test that the export condition `node-addons` can be used
* with `*.node` files with the ESM loader.
*/
import('esm-package/binding')
.then((mod) => {
assert.strictEqual(mod.default.hello(), 'world');
})
.then(common.mustCall());

View File

@ -0,0 +1,7 @@
// Flags: --experimental-addon-modules
'use strict';
const common = require('../../common');
import('./test-esm.mjs')
.then((mod) => mod.run())
.then(common.mustCall());

View File

@ -0,0 +1,12 @@
// Flags: --experimental-addon-modules
'use strict';
require('../../common');
const assert = require('node:assert');
/**
* Test that the export condition `node-addons` can be used
* with `*.node` files with the CJS loader.
*/
const mod = require('esm-package/binding');
assert.strictEqual(mod.hello(), 'world');

View File

@ -0,0 +1,6 @@
// Flags: --experimental-addon-modules
'use strict';
const common = require('../../common');
require('./test-esm.mjs')
.run().then(common.mustCall());

View File

@ -0,0 +1,6 @@
#include <node.h>
#include <v8.h>
void init(v8::Local<v8::Object> exports) {}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,8 @@
// Flags: --force-context-aware
'use strict';
const common = require('../../common');
const assert = require('assert');
assert.throws(() => {
require(`./build/${common.buildType}/binding`);
}, /^Error: Loading non context-aware native addons has been disabled$/);

View File

@ -0,0 +1,31 @@
#include "node.h"
#include "v8.h"
#include "v8-profiler.h"
namespace {
inline void Test(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* const isolate = args.GetIsolate();
const v8::HeapSnapshot* const heap_snapshot =
isolate->GetHeapProfiler()->TakeHeapSnapshot();
struct : public v8::OutputStream {
WriteResult WriteAsciiChunk(char*, int) override { return kContinue; }
void EndOfStream() override {}
} output_stream;
heap_snapshot->Serialize(&output_stream, v8::HeapSnapshot::kJSON);
const_cast<v8::HeapSnapshot*>(heap_snapshot)->Delete();
}
inline void Initialize(v8::Local<v8::Object> binding) {
v8::Isolate* const isolate = binding->GetIsolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
binding->Set(context, v8::String::NewFromUtf8(
isolate, "test").ToLocalChecked(),
v8::FunctionTemplate::New(isolate, Test)
->GetFunction(context)
.ToLocalChecked()).FromJust();
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
} // anonymous namespace

View File

@ -0,0 +1,10 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'win_delay_load_hook': 'false',
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,14 @@
'use strict';
const common = require('../../common');
const binding = require(`./build/${common.buildType}/binding`);
// Create an AsyncWrap object.
const timer = setTimeout(common.mustNotCall(), 1);
timer.unref();
// Stress-test the heap profiler.
binding.test();
clearTimeout(timer);

View File

@ -0,0 +1,14 @@
#include <node.h>
#include <v8.h>
void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(v8::String::NewFromUtf8(
isolate, "world").ToLocalChecked());
}
void init(v8::Local<v8::Object> exports, v8::Local<v8::Object> module) {
NODE_SET_METHOD(module, "exports", Method);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,6 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const binding = require(`./build/${common.buildType}/binding`);
assert.strictEqual(binding(), 'world');
console.log('binding.hello() =', binding());

View File

@ -0,0 +1,33 @@
#include <node.h>
#include <v8.h>
static void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(v8::String::NewFromUtf8(
isolate, "world").ToLocalChecked());
}
// Not using the full NODE_MODULE_INIT() macro here because we want to test the
// addon loader's reaction to the FakeInit() entry point below.
extern "C" NODE_MODULE_EXPORT void
NODE_MODULE_INITIALIZER(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> context) {
NODE_SET_METHOD(exports, "hello", Method);
}
static void FakeInit(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> context) {
auto isolate = context->GetIsolate();
auto exception = v8::Exception::Error(v8::String::NewFromUtf8(isolate,
"FakeInit should never run!").ToLocalChecked());
isolate->ThrowException(exception);
}
// Define a Node.js module, but with the wrong version. Node.js should still be
// able to load this module, multiple times even, because it exposes the
// specially named initializer above.
#undef NODE_MODULE_VERSION
#define NODE_MODULE_VERSION 3
NODE_MODULE(NODE_GYP_MODULE_NAME, FakeInit)

View File

@ -0,0 +1,14 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
},
{
'target_name': 'binding2',
'sources': [ 'binding2.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,21 @@
// Include uv.h and v8.h ahead of node.h to verify that node.h doesn't need to
// be included first. Disable clang-format as it will sort the include lists.
// clang-format off
#include <uv.h>
#include <v8.h>
#include <node.h>
// clang-format on
static void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(
v8::String::NewFromUtf8(isolate, "world").ToLocalChecked());
}
static void InitModule(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> context) {
NODE_SET_METHOD(exports, "hello", Method);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, InitModule)

View File

@ -0,0 +1,13 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const path = require('path');
const { Worker } = require('worker_threads');
const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`);
const w = new Worker(`
require('worker_threads').parentPort.postMessage(
require(${JSON.stringify(binding)}).hello());`, { eval: true });
w.on('message', common.mustCall((message) => {
assert.strictEqual(message, 'world');
}));

View File

@ -0,0 +1,16 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
const binding = require(bindingPath);
assert.strictEqual(binding.hello(), 'world');
console.log('binding.hello() =', binding.hello());
const binding2 = require(require.resolve(`./build/${common.buildType}/binding2`));
assert.strictEqual(binding2.hello(), 'world');
// Test multiple loading of the same module.
delete require.cache[bindingPath];
const rebinding = require(bindingPath);
assert.strictEqual(rebinding.hello(), 'world');
assert.notStrictEqual(binding.hello, rebinding.hello);

View File

@ -0,0 +1,14 @@
#include <node.h>
#include <v8.h>
void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(v8::String::NewFromUtf8(
isolate, "world").ToLocalChecked());
}
void init(v8::Local<v8::Object> exports) {
NODE_SET_METHOD(exports, "hello", Method);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,53 @@
'use strict';
const common = require('../../common');
if (common.isWindows && (process.env.PROCESSOR_ARCHITEW6432 !== undefined))
common.skip('doesn\'t work on WOW64');
const fs = require('fs');
const path = require('path');
const assert = require('assert');
const { fork } = require('child_process');
const tmpdir = require('../../common/tmpdir');
// Make a path that is more than 260 chars long.
// Any given folder cannot have a name longer than 260 characters,
// so create 10 nested folders each with 30 character long names.
let addonDestinationDir = path.resolve(tmpdir.path);
for (let i = 0; i < 10; i++) {
addonDestinationDir = path.join(addonDestinationDir, 'x'.repeat(30));
}
const addonPath = path.join(__dirname,
'build',
common.buildType,
'binding.node');
const addonDestinationPath = path.join(addonDestinationDir, 'binding.node');
// Loading an addon keeps the file open until the process terminates. Load
// the addon in a child process so that when the parent terminates the file
// is already closed and the tmpdir can be cleaned up.
// Child
if (process.argv[2] === 'child') {
// Attempt to load at long path destination
const addon = require(addonDestinationPath);
assert.notStrictEqual(addon, null);
assert.strictEqual(addon.hello(), 'world');
return;
}
// Parent
tmpdir.refresh();
// Copy binary to long path destination
fs.mkdirSync(addonDestinationDir, { recursive: true });
const contents = fs.readFileSync(addonPath);
fs.writeFileSync(addonDestinationPath, contents);
// Run test
const child = fork(__filename, ['child'], { stdio: 'inherit' });
child.on('exit', common.mustCall((code) => {
assert.strictEqual(code, 0);
}));

View File

@ -0,0 +1,32 @@
#include "node.h"
#include "v8.h"
#include <assert.h>
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::Value;
namespace {
void MakeCallback(const FunctionCallbackInfo<Value>& args) {
assert(args[0]->IsObject());
assert(args[1]->IsFunction());
Isolate* isolate = args.GetIsolate();
Local<Object> recv = args[0].As<Object>();
Local<Function> method = args[1].As<Function>();
node::MakeCallback(isolate, recv, method, 0, nullptr,
node::async_context{0, 0});
}
void Initialize(Local<Object> exports) {
NODE_SET_METHOD(exports, "makeCallback", MakeCallback);
}
} // namespace
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,43 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const domain = require('domain');
const binding = require(`./build/${common.buildType}/binding`);
function makeCallback(object, cb) {
binding.makeCallback(object, function someMethod() { setImmediate(cb); });
}
let latestWarning = null;
process.on('warning', (warning) => {
latestWarning = warning;
});
const d = domain.create();
class Resource {
constructor(domain) {
this.domain = domain;
}
}
// When domain is disabled, no warning will be emitted
makeCallback(new Resource(d), common.mustCall(() => {
assert.strictEqual(latestWarning, null);
d.run(common.mustCall(() => {
// No warning will be emitted when no domain property is applied
makeCallback({}, common.mustCall(() => {
assert.strictEqual(latestWarning, null);
// Warning is emitted when domain property is used and domain is enabled
makeCallback(new Resource(d), common.mustCall(() => {
assert.match(latestWarning.message,
/Triggered by calling someMethod on Resource\./);
assert.strictEqual(latestWarning.name, 'DeprecationWarning');
assert.strictEqual(latestWarning.code, 'DEP0097');
}));
}));
}));
}));

View File

@ -0,0 +1,37 @@
#include "node.h"
#include "v8.h"
#include <assert.h>
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::TryCatch;
using v8::Value;
namespace {
void MakeCallback(const FunctionCallbackInfo<Value>& args) {
assert(args[0]->IsObject());
assert(args[1]->IsFunction());
Isolate* isolate = args.GetIsolate();
Local<Object> recv = args[0].As<Object>();
Local<Function> method = args[1].As<Function>();
TryCatch try_catch(isolate);
node::MakeCallback(isolate, recv, method, 0, nullptr,
node::async_context{0, 0});
if (try_catch.HasCaught()) {
try_catch.ReThrow();
}
}
void Initialize(Local<Object> exports) {
NODE_SET_METHOD(exports, "makeCallback", MakeCallback);
}
} // anonymous namespace
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,149 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const domain = require('domain');
const binding = require(`./build/${common.buildType}/binding`);
const makeCallback = binding.makeCallback;
// Make sure this is run in the future.
const mustCallCheckDomains = common.mustCall(checkDomains);
// Make sure that using MakeCallback allows the error to propagate.
assert.throws(() => {
makeCallback({}, () => {
throw new Error('hi from domain error');
});
}, /^Error: hi from domain error$/);
// Check the execution order of the nextTickQueue and MicrotaskQueue in
// relation to running multiple MakeCallback's from bootstrap,
// node::MakeCallback() and node::AsyncWrap::MakeCallback().
// TODO(trevnorris): Is there a way to verify this is being run during
// bootstrap?
(function verifyExecutionOrder(arg) {
const results = [];
// Processing of the MicrotaskQueue is manually handled by node. They are not
// processed until after the nextTickQueue has been processed.
Promise.resolve(1).then(common.mustCall(() => {
results.push(7);
}));
// The nextTick should run after all immediately invoked calls.
process.nextTick(common.mustCall(() => {
results.push(3);
// Run same test again but while processing the nextTickQueue to make sure
// the following MakeCallback call breaks in the middle of processing the
// queue and allows the script to run normally.
process.nextTick(common.mustCall(() => {
results.push(6);
}));
makeCallback({}, common.mustCall(() => {
results.push(4);
}));
results.push(5);
}));
results.push(0);
// MakeCallback is calling the function immediately, but should then detect
// that a script is already in the middle of execution and return before
// either the nextTickQueue or MicrotaskQueue are processed.
makeCallback({}, common.mustCall(() => {
results.push(1);
}));
// This should run before either the nextTickQueue or MicrotaskQueue are
// processed. Previously MakeCallback would not detect this circumstance
// and process them immediately.
results.push(2);
setImmediate(common.mustCall(() => {
for (let i = 0; i < results.length; i++) {
assert.strictEqual(results[i], i,
`verifyExecutionOrder(${arg}) results: ${results}`);
}
if (arg === 1) {
// The tests are first run on bootstrap during LoadEnvironment() in
// src/node.cc. Now run the tests through node::MakeCallback().
setImmediate(() => {
makeCallback({}, common.mustCall(() => {
verifyExecutionOrder(2);
}));
});
} else if (arg === 2) {
// Make sure there are no conflicts using node::MakeCallback()
// within timers.
setTimeout(common.mustCall(() => {
verifyExecutionOrder(3);
}), 10);
} else if (arg === 3) {
mustCallCheckDomains();
} else {
throw new Error('UNREACHABLE');
}
}));
}(1));
function checkDomains() {
// Check that domains are properly entered/exited when called in multiple
// levels from both node::MakeCallback() and AsyncWrap::MakeCallback
setImmediate(common.mustCall(() => {
const d1 = domain.create();
const d2 = domain.create();
const d3 = domain.create();
makeCallback({ domain: d1 }, common.mustCall(() => {
assert.strictEqual(d1, process.domain);
makeCallback({ domain: d2 }, common.mustCall(() => {
assert.strictEqual(d2, process.domain);
makeCallback({ domain: d3 }, common.mustCall(() => {
assert.strictEqual(d3, process.domain);
}));
assert.strictEqual(d2, process.domain);
}));
assert.strictEqual(d1, process.domain);
}));
}));
setTimeout(common.mustCall(() => {
const d1 = domain.create();
const d2 = domain.create();
const d3 = domain.create();
makeCallback({ domain: d1 }, common.mustCall(() => {
assert.strictEqual(d1, process.domain);
makeCallback({ domain: d2 }, common.mustCall(() => {
assert.strictEqual(d2, process.domain);
makeCallback({ domain: d3 }, common.mustCall(() => {
assert.strictEqual(d3, process.domain);
}));
assert.strictEqual(d2, process.domain);
}));
assert.strictEqual(d1, process.domain);
}));
}), 1);
function testTimer(id) {
// Make sure nextTick, setImmediate and setTimeout can all recover properly
// after a thrown makeCallback call.
const d = domain.create();
d.on('error', common.mustCall((e) => {
assert.strictEqual(e.message, `throw from domain ${id}`);
}));
makeCallback({ domain: d }, () => {
throw new Error(`throw from domain ${id}`);
});
throw new Error('UNREACHABLE');
}
process.nextTick(common.mustCall(testTimer), 3);
setImmediate(common.mustCall(testTimer), 2);
setTimeout(common.mustCall(testTimer), 1, 1);
}

View File

@ -0,0 +1,41 @@
#include "node.h"
#include "v8.h"
#include <assert.h>
#include <vector>
namespace {
void MakeCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
assert(args[0]->IsObject());
assert(args[1]->IsFunction() || args[1]->IsString());
auto isolate = args.GetIsolate();
auto recv = args[0].As<v8::Object>();
v8::LocalVector<v8::Value> argv(isolate);
for (size_t n = 2; n < static_cast<size_t>(args.Length()); n += 1) {
argv.push_back(args[n]);
}
v8::Local<v8::Value> result;
if (args[1]->IsFunction()) {
auto method = args[1].As<v8::Function>();
result =
node::MakeCallback(isolate, recv, method, argv.size(), argv.data(),
node::async_context{0, 0}).ToLocalChecked();
} else if (args[1]->IsString()) {
auto method = args[1].As<v8::String>();
result =
node::MakeCallback(isolate, recv, method, argv.size(), argv.data(),
node::async_context{0, 0}).ToLocalChecked();
} else {
assert(0 && "unreachable");
}
args.GetReturnValue().Set(result);
}
void Initialize(v8::Local<v8::Object> exports) {
NODE_SET_METHOD(exports, "makeCallback", MakeCallback);
}
} // anonymous namespace
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,65 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const vm = require('vm');
const binding = require(`./build/${common.buildType}/binding`);
const makeCallback = binding.makeCallback;
assert.strictEqual(makeCallback(process, common.mustCall(function() {
assert.strictEqual(arguments.length, 0);
assert.strictEqual(process, this);
return 42;
})), 42);
assert.strictEqual(makeCallback(process, common.mustCall(function(x) {
assert.strictEqual(arguments.length, 1);
assert.strictEqual(process, this);
assert.strictEqual(x, 1337);
return 42;
}), 1337), 42);
const recv = {
one: common.mustCall(function() {
assert.strictEqual(arguments.length, 0);
assert.strictEqual(recv, this);
return 42;
}),
two: common.mustCall(function(x) {
assert.strictEqual(arguments.length, 1);
assert.strictEqual(recv, this);
assert.strictEqual(x, 1337);
return 42;
}),
};
assert.strictEqual(makeCallback(recv, 'one'), 42);
assert.strictEqual(makeCallback(recv, 'two', 1337), 42);
// Check that callbacks on a receiver from a different context works.
const foreignObject = vm.runInNewContext('({ fortytwo() { return 42; } })');
assert.strictEqual(makeCallback(foreignObject, 'fortytwo'), 42);
// Check that the callback is made in the context of the receiver.
const target = vm.runInNewContext(`
(function($Object) {
if (Object === $Object)
throw new Error('bad');
return Object;
})
`);
assert.notStrictEqual(makeCallback(process, target, Object), Object);
// Runs in inner context.
const forward = vm.runInNewContext(`
(function(forward) {
return forward(Object);
})
`);
// Runs in outer context.
function endpoint($Object) {
if (Object === $Object)
throw new Error('bad');
return Object;
}
assert.strictEqual(makeCallback(process, forward, endpoint), Object);

View File

@ -0,0 +1,76 @@
#include <assert.h>
#include <node.h>
using node::Environment;
using node::MultiIsolatePlatform;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Locker;
using v8::MaybeLocal;
using v8::Object;
using v8::SharedArrayBuffer;
using v8::String;
using v8::Unlocker;
using v8::Value;
void RunInSeparateIsolate(const FunctionCallbackInfo<Value>& args) {
Isolate* parent_isolate = args.GetIsolate();
assert(args[0]->IsString());
String::Utf8Value code(parent_isolate, args[0]);
assert(*code != nullptr);
assert(args[1]->IsSharedArrayBuffer());
auto arg_bs = args[1].As<SharedArrayBuffer>()->GetBackingStore();
Environment* parent_env =
node::GetCurrentEnvironment(parent_isolate->GetCurrentContext());
assert(parent_env != nullptr);
MultiIsolatePlatform* platform = node::GetMultiIsolatePlatform(parent_env);
assert(parent_env != nullptr);
{
Unlocker unlocker(parent_isolate);
std::vector<std::string> errors;
const std::vector<std::string> empty_args;
auto setup =
node::CommonEnvironmentSetup::Create(platform,
&errors,
empty_args,
empty_args,
node::EnvironmentFlags::kNoFlags);
assert(setup);
{
Locker locker(setup->isolate());
Isolate::Scope isolate_scope(setup->isolate());
HandleScope handle_scope(setup->isolate());
Context::Scope context_scope(setup->context());
auto arg = SharedArrayBuffer::New(setup->isolate(), arg_bs);
auto result = setup->context()->Global()->Set(
setup->context(),
v8::String::NewFromUtf8Literal(setup->isolate(), "arg"),
arg);
assert(!result.IsNothing());
MaybeLocal<Value> loadenv_ret =
node::LoadEnvironment(setup->env(), *code);
assert(!loadenv_ret.IsEmpty());
(void)node::SpinEventLoop(setup->env());
node::Stop(setup->env());
}
}
}
void Initialize(Local<Object> exports,
Local<Value> module,
Local<Context> context) {
NODE_SET_METHOD(exports, "runInSeparateIsolate", RunInSeparateIsolate);
}
NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize)

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,6 @@
// Flags: --no-node-snapshot
'use strict';
require('../../common');
// Just re-execute the main test.
require('./test');

View File

@ -0,0 +1,8 @@
'use strict';
const common = require('../../common');
const binding = require(`./build/${common.buildType}/binding`);
const assert = require('assert');
const arg = new SharedArrayBuffer(1);
binding.runInSeparateIsolate('const arr = new Uint8Array(arg); arr[0] = 0x42;', arg);
assert.deepStrictEqual(new Uint8Array(arg), new Uint8Array([0x42]));

View File

@ -0,0 +1,24 @@
#include <node.h>
#include <v8.h>
namespace {
inline void NewClass(const v8::FunctionCallbackInfo<v8::Value>& args) {
// this != new.target since we are being invoked through super().
assert(args.IsConstructCall());
assert(!args.This()->StrictEquals(args.NewTarget()));
}
inline void Initialize(v8::Local<v8::Object> binding) {
auto isolate = binding->GetIsolate();
auto context = isolate->GetCurrentContext();
binding->Set(context, v8::String::NewFromUtf8(
isolate, "Class").ToLocalChecked(),
v8::FunctionTemplate::New(isolate, NewClass)
->GetFunction(context)
.ToLocalChecked()).FromJust();
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
} // anonymous namespace

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,18 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const binding = require(`./build/${common.buildType}/binding`);
class Class extends binding.Class {
constructor() {
super();
this.method();
}
method() {
this.ok = true;
}
}
assert.ok(new Class() instanceof binding.Class);
assert.ok(new Class().ok);

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ '../hello-world/binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

View File

@ -0,0 +1,43 @@
// Flags: --permission --allow-fs-read=*
'use strict';
const common = require('../../common');
const assert = require('assert');
const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
const assertError = (error) => {
assert(error instanceof Error);
assert.strictEqual(error.code, 'ERR_DLOPEN_DISABLED');
assert.strictEqual(
error.message,
'Cannot load native addon because loading addons is disabled.',
);
};
{
let threw = false;
try {
require(bindingPath);
} catch (error) {
assertError(error);
threw = true;
}
assert(threw);
}
{
let threw = false;
try {
process.dlopen({ exports: {} }, bindingPath);
} catch (error) {
assertError(error);
threw = true;
}
assert(threw);
}

View File

@ -0,0 +1,59 @@
// Flags: --no-addons
'use strict';
const common = require('../../common');
const assert = require('assert');
const path = require('path');
const { Worker } = require('worker_threads');
const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`);
const assertError = (error) => {
assert.strictEqual(error.code, 'ERR_DLOPEN_DISABLED');
assert.strictEqual(
error.message,
'Cannot load native addon because loading addons is disabled.',
);
};
{
// Flags should be inherited
const worker = new Worker(`require(${JSON.stringify(binding)})`, {
eval: true,
});
worker.on('error', common.mustCall(assertError));
}
{
// Should throw when using `process.dlopen` directly
const worker = new Worker(
`process.dlopen({ exports: {} }, ${JSON.stringify(binding)});`,
{
eval: true,
},
);
worker.on('error', common.mustCall(assertError));
}
{
// Explicitly pass `--no-addons`
const worker = new Worker(`require(${JSON.stringify(binding)})`, {
eval: true,
execArgv: ['--no-addons'],
});
worker.on('error', common.mustCall(assertError));
}
{
// If `execArgv` is overwritten it should still fail to load addons
const worker = new Worker(`require(${JSON.stringify(binding)})`, {
eval: true,
execArgv: [],
});
worker.on('error', common.mustCall(assertError));
}

View File

@ -0,0 +1,43 @@
// Flags: --no-addons
'use strict';
const common = require('../../common');
const assert = require('assert');
const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
const assertError = (error) => {
assert(error instanceof Error);
assert.strictEqual(error.code, 'ERR_DLOPEN_DISABLED');
assert.strictEqual(
error.message,
'Cannot load native addon because loading addons is disabled.',
);
};
{
let threw = false;
try {
require(bindingPath);
} catch (error) {
assertError(error);
threw = true;
}
assert(threw);
}
{
let threw = false;
try {
process.dlopen({ exports: {} }, bindingPath);
} catch (error) {
assertError(error);
threw = true;
}
assert(threw);
}

Some files were not shown because too many files have changed in this diff Show More