Skip to content

Commit

Permalink
test: add extended embedder cctest
Browse files Browse the repository at this point in the history
Add an embedder cctest that also covers a multi-Environment situation,
including worker_threads-style inspector support.

Co-authored-by: Joyee Cheung <[email protected]>

PR-URL: #30467
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Gireesh Punathil <[email protected]>
  • Loading branch information
addaleax committed Mar 21, 2020
1 parent e04f599 commit 2061c33
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 21 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ coverage-report-js:
# Runs the C++ tests using the built `cctest` executable.
cctest: all
@out/$(BUILDTYPE)/$@ --gtest_filter=$(GTEST_FILTER)
@out/$(BUILDTYPE)/embedtest "require('./test/embedding/test.js')"
@out/$(BUILDTYPE)/embedtest "require('./test/embedding/test-embedding.js')"

.PHONY: list-gtests
list-gtests:
Expand Down Expand Up @@ -532,7 +532,7 @@ test-ci: | clear-stalled build-addons build-js-native-api-tests build-node-api-t
$(PYTHON) tools/test.py $(PARALLEL_ARGS) -p tap --logfile test.tap \
--mode=$(BUILDTYPE_LOWER) --flaky-tests=$(FLAKY_TESTS) \
$(TEST_CI_ARGS) $(CI_JS_SUITES) $(CI_NATIVE_SUITES) $(CI_DOC)
out/Release/embedtest 'require("./test/embedding/test.js")'
out/Release/embedtest 'require("./test/embedding/test-embedding.js")'
@echo "Clean up any leftover processes, error if found."
ps awwx | grep Release/node | grep -v grep | cat
@PS_OUT=`ps awwx | grep Release/node | grep -v grep | awk '{print $$1}'`; \
Expand Down
141 changes: 141 additions & 0 deletions test/cctest/test_environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,144 @@ TEST_F(EnvironmentTest, BufferWithFreeCallbackIsDetached) {
CHECK_EQ(callback_calls, 1);
CHECK_EQ(ab->ByteLength(), 0);
}

#if HAVE_INSPECTOR
TEST_F(EnvironmentTest, InspectorMultipleEmbeddedEnvironments) {
// Tests that child Environments can be created through the public API
// that are accessible by the inspector.
// This test sets a global variable in the child Environment, and reads it
// back both through the inspector and inside the child Environment, and
// makes sure that those correspond to the value that was originally set.
const v8::HandleScope handle_scope(isolate_);
const Argv argv;
Env env {handle_scope, argv};

v8::Local<v8::Context> context = isolate_->GetCurrentContext();
node::LoadEnvironment(*env,
"'use strict';\n"
"const { Worker } = require('worker_threads');\n"
"const { Session } = require('inspector');\n"

"const session = new Session();\n"
"session.connect();\n"
"session.on('NodeWorker.attachedToWorker', (\n"
" ({ params: { workerInfo, sessionId } }) => {\n"
" session.post('NodeWorker.sendMessageToWorker', {\n"
" sessionId,\n"
" message: JSON.stringify({\n"
" id: 1,\n"
" method: 'Runtime.evaluate',\n"
" params: {\n"
" expression: 'global.variableFromParent = 42;'\n"
" }\n"
" })\n"
" });\n"
" session.on('NodeWorker.receivedMessageFromWorker',\n"
" ({ params: { message } }) => {\n"
" global.messageFromWorker = \n"
" JSON.parse(message).result.result.value;\n"
" });\n"
" }));\n"
"session.post('NodeWorker.enable', { waitForDebuggerOnStart: false });\n")
.ToLocalChecked();

struct ChildEnvironmentData {
node::ThreadId thread_id;
std::unique_ptr<node::InspectorParentHandle> inspector_parent_handle;
node::MultiIsolatePlatform* platform;
int32_t extracted_value = -1;
uv_async_t thread_stopped_async;
};

ChildEnvironmentData data;
data.thread_id = node::AllocateEnvironmentThreadId();
data.inspector_parent_handle =
GetInspectorParentHandle(*env, data.thread_id, "file:///embedded.js");
CHECK(data.inspector_parent_handle);
data.platform = GetMultiIsolatePlatform(*env);
CHECK_NOT_NULL(data.platform);

bool thread_stopped = false;
int err = uv_async_init(
&current_loop, &data.thread_stopped_async, [](uv_async_t* async) {
*static_cast<bool*>(async->data) = true;
uv_close(reinterpret_cast<uv_handle_t*>(async), nullptr);
});
CHECK_EQ(err, 0);
data.thread_stopped_async.data = &thread_stopped;

uv_thread_t thread;
err = uv_thread_create(&thread, [](void* arg) {
ChildEnvironmentData* data = static_cast<ChildEnvironmentData*>(arg);
std::shared_ptr<node::ArrayBufferAllocator> aba =
node::ArrayBufferAllocator::Create();
uv_loop_t loop;
uv_loop_init(&loop);
v8::Isolate* isolate = NewIsolate(aba, &loop, data->platform);
CHECK_NOT_NULL(isolate);

{
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);

v8::Local<v8::Context> context = node::NewContext(isolate);
CHECK(!context.IsEmpty());
v8::Context::Scope context_scope(context);

node::IsolateData* isolate_data = node::CreateIsolateData(
isolate,
&loop,
data->platform);
CHECK_NOT_NULL(isolate_data);
node::Environment* environment = node::CreateEnvironment(
isolate_data,
context,
{ "dummy" },
{},
node::EnvironmentFlags::kNoFlags,
data->thread_id);
CHECK_NOT_NULL(environment);

v8::Local<v8::Value> extracted_value = LoadEnvironment(
environment,
"return global.variableFromParent;",
std::move(data->inspector_parent_handle)).ToLocalChecked();

uv_run(&loop, UV_RUN_DEFAULT);
CHECK(extracted_value->IsInt32());
data->extracted_value = extracted_value.As<v8::Int32>()->Value();

node::FreeEnvironment(environment);
node::FreeIsolateData(isolate_data);
}

data->platform->UnregisterIsolate(isolate);
isolate->Dispose();
uv_run(&loop, UV_RUN_DEFAULT);
CHECK_EQ(uv_loop_close(&loop), 0);

uv_async_send(&data->thread_stopped_async);
}, &data);
CHECK_EQ(err, 0);

bool more;
do {
uv_run(&current_loop, UV_RUN_DEFAULT);
data.platform->DrainTasks(isolate_);
more = uv_loop_alive(&current_loop);
} while (!thread_stopped || more);

uv_thread_join(&thread);

v8::Local<v8::Value> from_inspector =
context->Global()->Get(
context,
v8::String::NewFromOneByte(
isolate_,
reinterpret_cast<const uint8_t*>("messageFromWorker"),
v8::NewStringType::kNormal).ToLocalChecked())
.ToLocalChecked();
CHECK_EQ(data.extracted_value, 42);
CHECK_EQ(from_inspector->IntegerValue(context).FromJust(), 42);
}
#endif // HAVE_INSPECTOR
33 changes: 33 additions & 0 deletions test/embedding/test-embedding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const child_process = require('child_process');
const path = require('path');

common.allowGlobals(global.require);
let binary = process.features.debug ?
'out/Debug/embedtest' : 'out/Release/embedtest';
if (common.isWindows) {
binary += '.exe';
}
binary = path.resolve(__dirname, '..', '..', binary);

assert.strictEqual(
child_process.spawnSync(binary, ['console.log(42)'])
.stdout.toString().trim(),
'42');

assert.strictEqual(
child_process.spawnSync(binary, ['throw new Error()']).status,
1);

assert.strictEqual(
child_process.spawnSync(binary, ['process.exitCode = 8']).status,
8);


const fixturePath = JSON.stringify(fixtures.path('exit.js'));
assert.strictEqual(
child_process.spawnSync(binary, [`require(${fixturePath})`, 92]).status,
92);
19 changes: 0 additions & 19 deletions test/embedding/test.js

This file was deleted.

6 changes: 6 additions & 0 deletions test/embedding/testcfg.py
Original file line number Diff line number Diff line change
@@ -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.SimpleTestConfiguration(context, root, 'embedding')
1 change: 1 addition & 0 deletions tools/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1491,6 +1491,7 @@ def PrintCrashed(code):
'addons',
'benchmark',
'doctool',
'embedding',
'internet',
'js-native-api',
'node-api',
Expand Down

0 comments on commit 2061c33

Please sign in to comment.