From cb635c2dc07e893dfe6fcf73edcc8ded9548d91d Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Thu, 12 Mar 2020 13:11:48 +0100 Subject: [PATCH] test: add extended embedder cctest Add an embedder cctest that also covers a multi-Environment situation, including worker_threads-style inspector support. Co-authored-by: Joyee Cheung Backport-PR-URL: https://github.com/nodejs/node/pull/35241 PR-URL: https://github.com/nodejs/node/pull/30467 Reviewed-By: James M Snell Reviewed-By: Gireesh Punathil --- Makefile | 4 +- test/cctest/test_environment.cc | 162 ++++++++++++++++++++++++++----- test/embedding/test-embedding.js | 33 +++++++ test/embedding/test.js | 19 ---- test/embedding/testcfg.py | 6 ++ tools/test.py | 1 + 6 files changed, 182 insertions(+), 43 deletions(-) create mode 100644 test/embedding/test-embedding.js delete mode 100644 test/embedding/test.js create mode 100644 test/embedding/testcfg.py diff --git a/Makefile b/Makefile index 35eeeb618b7097..c8731b5f4da534 100644 --- a/Makefile +++ b/Makefile @@ -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: @@ -534,7 +534,7 @@ test-ci: | clear-stalled bench-addons-build build-addons build-js-native-api-tes $(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}'`; \ diff --git a/test/cctest/test_environment.cc b/test/cctest/test_environment.cc index 7f6dbb8d9d9cfd..b0603ce35734f3 100644 --- a/test/cctest/test_environment.cc +++ b/test/cctest/test_environment.cc @@ -266,10 +266,10 @@ TEST_F(EnvironmentTest, SetImmediateCleanup) { EXPECT_EQ(env_arg, *env); called++; }); - (*env)->SetUnrefImmediate([&](node::Environment* env_arg) { + (*env)->SetImmediate([&](node::Environment* env_arg) { EXPECT_EQ(env_arg, *env); called_unref++; - }); + }, node::CallbackFlags::kUnrefed); } EXPECT_EQ(called, 1); @@ -307,25 +307,143 @@ TEST_F(EnvironmentTest, BufferWithFreeCallbackIsDetached) { CHECK_EQ(ab->ByteLength(), 0); } -TEST_F(EnvironmentTest, SetImmediateCleanup) { - int called = 0; - int called_unref = 0; - - { - const v8::HandleScope handle_scope(isolate_); - const Argv argv; - Env env {handle_scope, argv}; - - (*env)->SetImmediate([&](node::Environment* env_arg) { - EXPECT_EQ(env_arg, *env); - called++; - }); - (*env)->SetImmediate([&](node::Environment* env_arg) { - EXPECT_EQ(env_arg, *env); - called_unref++; - }, node::CallbackFlags::kUnrefed); - } +#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}; - EXPECT_EQ(called, 1); - EXPECT_EQ(called_unref, 0); + v8::Local 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 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( + ¤t_loop, &data.thread_stopped_async, [](uv_async_t* async) { + *static_cast(async->data) = true; + uv_close(reinterpret_cast(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(arg); + std::shared_ptr aba = + node::ArrayBufferAllocator::Create(); + uv_loop_t loop; + uv_loop_init(&loop); + v8::Isolate* isolate = NewIsolate(aba.get(), &loop, data->platform); + CHECK_NOT_NULL(isolate); + + { + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope(isolate); + + v8::Local 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 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()->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(¤t_loop, UV_RUN_DEFAULT); + data.platform->DrainTasks(isolate_); + more = uv_loop_alive(¤t_loop); + } while (!thread_stopped || more); + + uv_thread_join(&thread); + + v8::Local from_inspector = + context->Global()->Get( + context, + v8::String::NewFromOneByte( + isolate_, + reinterpret_cast("messageFromWorker"), + v8::NewStringType::kNormal).ToLocalChecked()) + .ToLocalChecked(); + CHECK_EQ(data.extracted_value, 42); + CHECK_EQ(from_inspector->IntegerValue(context).FromJust(), 42); } +#endif // HAVE_INSPECTOR diff --git a/test/embedding/test-embedding.js b/test/embedding/test-embedding.js new file mode 100644 index 00000000000000..43dab0ab24ea53 --- /dev/null +++ b/test/embedding/test-embedding.js @@ -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); diff --git a/test/embedding/test.js b/test/embedding/test.js deleted file mode 100644 index a802de1849021f..00000000000000 --- a/test/embedding/test.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; -const common = require('../common'); -const assert = require('assert'); -const child_process = require('child_process'); - -common.allowGlobals(global.require); - -assert.strictEqual( - child_process.spawnSync(process.execPath, ['console.log(42)']) - .stdout.toString().trim(), - '42'); - -assert.strictEqual( - child_process.spawnSync(process.execPath, ['throw new Error()']).status, - 1); - -assert.strictEqual( - child_process.spawnSync(process.execPath, ['process.exitCode = 8']).status, - 8); diff --git a/test/embedding/testcfg.py b/test/embedding/testcfg.py new file mode 100644 index 00000000000000..a4b90f490c670f --- /dev/null +++ b/test/embedding/testcfg.py @@ -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') diff --git a/tools/test.py b/tools/test.py index 811db910608812..532b855479b7bb 100755 --- a/tools/test.py +++ b/tools/test.py @@ -1486,6 +1486,7 @@ def PrintCrashed(code): 'addons', 'benchmark', 'doctool', + 'embedding', 'internet', 'js-native-api', 'node-api',