Skip to content

Commit

Permalink
src,test: add full-featured embedder API test
Browse files Browse the repository at this point in the history
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 a8cf886 commit 43d32b0
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 2 deletions.
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ coverage-clean:
$(RM) out/$(BUILDTYPE)/obj.target/node/src/tracing/*.gcno
$(RM) out/$(BUILDTYPE)/obj.target/cctest/src/*.gcno
$(RM) out/$(BUILDTYPE)/obj.target/cctest/test/cctest/*.gcno
$(RM) out/$(BUILDTYPE)/obj.target/embedtest/src/*.gcno
$(RM) out/$(BUILDTYPE)/obj.target/embedtest/test/embedding/*.gcno

.PHONY: coverage
# Build and test with code coverage reporting. Leave the lib directory
Expand Down Expand Up @@ -250,8 +252,8 @@ coverage-test: coverage-build
TEST_CI_ARGS="$(TEST_CI_ARGS) --type=coverage" $(MAKE) $(COVTESTS)
$(MAKE) coverage-report-js
-(cd out && "../gcovr/scripts/gcovr" \
--gcov-exclude='.*\b(deps|usr|out|cctest)\b' -v -r Release/obj.target \
--html --html-detail -o ../coverage/cxxcoverage.html \
--gcov-exclude='.*\b(deps|usr|out|cctest|embedding)\b' -v \
-r Release/obj.target --html --html-detail -o ../coverage/cxxcoverage.html \
--gcov-executable="$(GCOV)")
@echo -n "Javascript coverage %: "
@grep -B1 Lines coverage/index.html | head -n1 \
Expand All @@ -276,6 +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')"

.PHONY: list-gtests
list-gtests:
Expand Down Expand Up @@ -529,6 +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")'
@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 Expand Up @@ -1258,6 +1262,8 @@ LINT_CPP_FILES = $(filter-out $(LINT_CPP_EXCLUDE), $(wildcard \
test/addons/*/*.h \
test/cctest/*.cc \
test/cctest/*.h \
test/embedding/*.cc \
test/embedding/*.h \
test/js-native-api/*/*.cc \
test/js-native-api/*/*.h \
test/node-api/*/*.cc \
Expand Down
56 changes: 56 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,62 @@
],
}, # cctest

{
'target_name': 'embedtest',
'type': 'executable',

'dependencies': [
'<(node_lib_target_name)',
'deps/histogram/histogram.gyp:histogram',
'deps/uvwasi/uvwasi.gyp:uvwasi',
'node_dtrace_header',
'node_dtrace_ustack',
'node_dtrace_provider',
],

'includes': [
'node.gypi'
],

'include_dirs': [
'src',
'tools/msvs/genfiles',
'deps/v8/include',
'deps/cares/include',
'deps/uv/include',
'deps/uvwasi/include',
'test/embedding',
],

'sources': [
'src/node_snapshot_stub.cc',
'src/node_code_cache_stub.cc',
'test/embedding/embedtest.cc',
],

'conditions': [
['OS=="solaris"', {
'ldflags': [ '-I<(SHARED_INTERMEDIATE_DIR)' ]
}],
# Skip cctest while building shared lib node for Windows
[ 'OS=="win" and node_shared=="true"', {
'type': 'none',
}],
[ 'node_shared=="true"', {
'xcode_settings': {
'OTHER_LDFLAGS': [ '-Wl,-rpath,@loader_path', ],
},
}],
['OS=="win"', {
'libraries': [
'Dbghelp.lib',
'winmm.lib',
'Ws2_32.lib',
],
}],
],
}, # embedtest

# TODO(joyeecheung): do not depend on node_lib,
# instead create a smaller static library node_lib_base that does
# just enough for node_native_module.cc and the cache builder to
Expand Down
4 changes: 4 additions & 0 deletions src/node_code_cache_stub.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// This file is part of the embedder test, which is intentionally built without
// NODE_WANT_INTERNALS, so we define it here manually.
#define NODE_WANT_INTERNALS 1

#include "node_native_module_env.h"

// This is supposed to be generated by tools/generate_code_cache.js
Expand Down
4 changes: 4 additions & 0 deletions src/node_snapshot_stub.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// This file is part of the embedder test, which is intentionally built without
// NODE_WANT_INTERNALS, so we define it here manually.
#define NODE_WANT_INTERNALS 1

#include "node_main_instance.h"

namespace node {
Expand Down
135 changes: 135 additions & 0 deletions test/embedding/embedtest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#include "node.h"
#include "uv.h"
#include <assert.h>

// Note: This file is being referred to from doc/api/embedding.md, and excerpts
// from it are included in the documentation. Try to keep these in sync.

using node::ArrayBufferAllocator;
using node::Environment;
using node::IsolateData;
using node::MultiIsolatePlatform;
using v8::Context;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Locker;
using v8::MaybeLocal;
using v8::SealHandleScope;
using v8::Value;
using v8::V8;

static int RunNodeInstance(MultiIsolatePlatform* platform,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args);

int main(int argc, char** argv) {
std::vector<std::string> args(argv, argv + argc);
std::vector<std::string> exec_args;
std::vector<std::string> errors;
int exit_code = node::InitializeNodeWithArgs(&args, &exec_args, &errors);
for (const std::string& error : errors)
fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str());
if (exit_code != 0) {
return exit_code;
}

std::unique_ptr<MultiIsolatePlatform> platform =
MultiIsolatePlatform::Create(4);
V8::InitializePlatform(platform.get());
V8::Initialize();

int ret = RunNodeInstance(platform.get(), args, exec_args);

V8::Dispose();
V8::ShutdownPlatform();
return ret;
}

int RunNodeInstance(MultiIsolatePlatform* platform,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args) {
int exit_code = 0;
uv_loop_t loop;
int ret = uv_loop_init(&loop);
if (ret != 0) {
fprintf(stderr, "%s: Failed to initialize loop: %s\n",
args[0].c_str(),
uv_err_name(ret));
return 1;
}

std::shared_ptr<ArrayBufferAllocator> allocator =
ArrayBufferAllocator::Create();

Isolate* isolate = NewIsolate(allocator, &loop, platform);
if (isolate == nullptr) {
fprintf(stderr, "%s: Failed to initialize V8 Isolate\n", args[0].c_str());
return 1;
}

{
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);

std::unique_ptr<IsolateData, decltype(&node::FreeIsolateData)> isolate_data(
node::CreateIsolateData(isolate, &loop, platform, allocator.get()),
node::FreeIsolateData);

HandleScope handle_scope(isolate);
Local<Context> context = node::NewContext(isolate);
if (context.IsEmpty()) {
fprintf(stderr, "%s: Failed to initialize V8 Context\n", args[0].c_str());
return 1;
}

Context::Scope context_scope(context);
std::unique_ptr<Environment, decltype(&node::FreeEnvironment)> env(
node::CreateEnvironment(isolate_data.get(), context, args, exec_args),
node::FreeEnvironment);

MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(
env.get(),
"const publicRequire ="
" require('module').createRequire(process.cwd() + '/');"
"globalThis.require = publicRequire;"
"require('vm').runInThisContext(process.argv[1]);");

if (loadenv_ret.IsEmpty()) // There has been a JS exception.
return 1;

{
SealHandleScope seal(isolate);
bool more;
do {
uv_run(&loop, UV_RUN_DEFAULT);

platform->DrainTasks(isolate);
more = uv_loop_alive(&loop);
if (more) continue;

node::EmitBeforeExit(env.get());
more = uv_loop_alive(&loop);
} while (more == true);
}

exit_code = node::EmitExit(env.get());

node::Stop(env.get());
}

bool platform_finished = false;
platform->AddIsolateFinishedCallback(isolate, [](void* data) {
*static_cast<bool*>(data) = true;
}, &platform_finished);
platform->UnregisterIsolate(isolate);
isolate->Dispose();

// Wait until the platform has cleaned up all relevant resources.
while (!platform_finished)
uv_run(&loop, UV_RUN_ONCE);
int err = uv_loop_close(&loop);
assert(err == 0);

return exit_code;
}
19 changes: 19 additions & 0 deletions test/embedding/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'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);

0 comments on commit 43d32b0

Please sign in to comment.