[debugger] basic test infrastructure for new debugger test api.
This introduces: - a way in d8 to send messages to the inspector and receive responses. - a new test suite where existing debugger tests should migrate to. R=jgruber@chromium.org, kozyatinskiy@chromium.org, machenbach@chromium.org BUG=v8:5530 Review-Url: https://chromiumcodereview.appspot.com/2425973002 Cr-Commit-Position: refs/heads/master@{#40487}
This commit is contained in:
parent
6e89606756
commit
2f135d464c
5
BUILD.gn
5
BUILD.gn
@ -2560,6 +2560,11 @@ v8_executable("d8") {
|
||||
if (v8_enable_i18n_support) {
|
||||
deps += [ "//third_party/icu" ]
|
||||
}
|
||||
|
||||
defines = []
|
||||
if (v8_enable_inspector_override) {
|
||||
defines += [ "V8_INSPECTOR_ENABLED" ]
|
||||
}
|
||||
}
|
||||
|
||||
v8_isolate_run("d8") {
|
||||
|
@ -27,6 +27,7 @@
|
||||
}],
|
||||
['v8_enable_inspector==1', {
|
||||
'dependencies': [
|
||||
'../test/debugger/debugger.gyp:*',
|
||||
'../test/inspector/inspector.gyp:*',
|
||||
],
|
||||
}],
|
||||
|
138
src/d8.cc
138
src/d8.cc
@ -34,6 +34,10 @@
|
||||
#include "src/utils.h"
|
||||
#include "src/v8.h"
|
||||
|
||||
#ifdef V8_INSPECTOR_ENABLED
|
||||
#include "include/v8-inspector.h"
|
||||
#endif // V8_INSPECTOR_ENABLED
|
||||
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
#include <unistd.h> // NOLINT
|
||||
#else
|
||||
@ -596,7 +600,8 @@ class ModuleEmbedderData {
|
||||
enum {
|
||||
// The debugger reserves the first slot in the Context embedder data.
|
||||
kDebugIdIndex = Context::kDebugIdIndex,
|
||||
kModuleEmbedderDataIndex
|
||||
kModuleEmbedderDataIndex,
|
||||
kInspectorClientIndex
|
||||
};
|
||||
|
||||
void InitializeModuleEmbedderData(Local<Context> context) {
|
||||
@ -1759,6 +1764,130 @@ void Shell::RunShell(Isolate* isolate) {
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
#ifdef V8_INSPECTOR_ENABLED
|
||||
class InspectorFrontend final : public v8_inspector::V8Inspector::Channel {
|
||||
public:
|
||||
explicit InspectorFrontend(Local<Context> context) {
|
||||
isolate_ = context->GetIsolate();
|
||||
context_.Reset(isolate_, context);
|
||||
}
|
||||
virtual ~InspectorFrontend() = default;
|
||||
|
||||
private:
|
||||
void sendProtocolResponse(int callId,
|
||||
const v8_inspector::StringView& message) override {
|
||||
Send(message);
|
||||
}
|
||||
void sendProtocolNotification(
|
||||
const v8_inspector::StringView& message) override {
|
||||
Send(message);
|
||||
}
|
||||
void flushProtocolNotifications() override {}
|
||||
|
||||
void Send(const v8_inspector::StringView& string) {
|
||||
int length = static_cast<int>(string.length());
|
||||
DCHECK(length < v8::String::kMaxLength);
|
||||
Local<String> message =
|
||||
(string.is8Bit()
|
||||
? v8::String::NewFromOneByte(
|
||||
isolate_,
|
||||
reinterpret_cast<const uint8_t*>(string.characters8()),
|
||||
v8::NewStringType::kNormal, length)
|
||||
: v8::String::NewFromTwoByte(
|
||||
isolate_,
|
||||
reinterpret_cast<const uint16_t*>(string.characters16()),
|
||||
v8::NewStringType::kNormal, length))
|
||||
.ToLocalChecked();
|
||||
Local<String> callback_name =
|
||||
v8::String::NewFromUtf8(isolate_, "receive", v8::NewStringType::kNormal)
|
||||
.ToLocalChecked();
|
||||
Local<Context> context = context_.Get(isolate_);
|
||||
Local<Value> callback =
|
||||
context->Global()->Get(context, callback_name).ToLocalChecked();
|
||||
if (callback->IsFunction()) {
|
||||
v8::TryCatch try_catch(isolate_);
|
||||
Local<Value> args[] = {message};
|
||||
if (Local<Function>::Cast(callback)
|
||||
->Call(context, Undefined(isolate_), 1, args)
|
||||
.IsEmpty()) {
|
||||
try_catch.ReThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Isolate* isolate_;
|
||||
Global<Context> context_;
|
||||
};
|
||||
|
||||
class InspectorClient : public v8_inspector::V8InspectorClient {
|
||||
public:
|
||||
InspectorClient(Local<Context> context, bool connect) {
|
||||
if (!connect) return;
|
||||
isolate_ = context->GetIsolate();
|
||||
channel_.reset(new InspectorFrontend(context));
|
||||
inspector_ = v8_inspector::V8Inspector::create(isolate_, this);
|
||||
session_ =
|
||||
inspector_->connect(1, channel_.get(), v8_inspector::StringView());
|
||||
context->SetAlignedPointerInEmbedderData(kInspectorClientIndex, this);
|
||||
inspector_->contextCreated(v8_inspector::V8ContextInfo(
|
||||
context, kContextGroupId, v8_inspector::StringView()));
|
||||
|
||||
Local<Value> function =
|
||||
FunctionTemplate::New(isolate_, SendInspectorMessage)
|
||||
->GetFunction(context)
|
||||
.ToLocalChecked();
|
||||
Local<String> function_name =
|
||||
String::NewFromUtf8(isolate_, "send", NewStringType::kNormal)
|
||||
.ToLocalChecked();
|
||||
CHECK(context->Global()->Set(context, function_name, function).FromJust());
|
||||
|
||||
context_.Reset(isolate_, context);
|
||||
}
|
||||
|
||||
private:
|
||||
static v8_inspector::V8InspectorSession* GetSession(Local<Context> context) {
|
||||
InspectorClient* inspector_client = static_cast<InspectorClient*>(
|
||||
context->GetAlignedPointerFromEmbedderData(kInspectorClientIndex));
|
||||
return inspector_client->session_.get();
|
||||
}
|
||||
|
||||
Local<Context> ensureDefaultContextInGroup(int group_id) override {
|
||||
DCHECK(isolate_);
|
||||
DCHECK_EQ(kContextGroupId, group_id);
|
||||
return context_.Get(isolate_);
|
||||
}
|
||||
|
||||
static void SendInspectorMessage(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
args.GetReturnValue().Set(Undefined(isolate));
|
||||
Local<String> message = args[0]->ToString(context).ToLocalChecked();
|
||||
v8_inspector::V8InspectorSession* session =
|
||||
InspectorClient::GetSession(context);
|
||||
int length = message->Length();
|
||||
std::unique_ptr<uint16_t> buffer(new uint16_t[length]);
|
||||
message->Write(buffer.get(), 0, length);
|
||||
v8_inspector::StringView message_view(buffer.get(), length);
|
||||
session->dispatchProtocolMessage(message_view);
|
||||
args.GetReturnValue().Set(True(isolate));
|
||||
}
|
||||
|
||||
static const int kContextGroupId = 1;
|
||||
|
||||
std::unique_ptr<v8_inspector::V8Inspector> inspector_;
|
||||
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
|
||||
std::unique_ptr<v8_inspector::V8Inspector::Channel> channel_;
|
||||
Global<Context> context_;
|
||||
Isolate* isolate_;
|
||||
};
|
||||
#else // V8_INSPECTOR_ENABLED
|
||||
class InspectorClient {
|
||||
public:
|
||||
InspectorClient(Local<Context> context, bool connect) { CHECK(!connect); }
|
||||
};
|
||||
#endif // V8_INSPECTOR_ENABLED
|
||||
|
||||
SourceGroup::~SourceGroup() {
|
||||
delete thread_;
|
||||
@ -1842,7 +1971,6 @@ base::Thread::Options SourceGroup::GetThreadOptions() {
|
||||
return base::Thread::Options("IsolateThread", 2 * MB);
|
||||
}
|
||||
|
||||
|
||||
void SourceGroup::ExecuteInThread() {
|
||||
Isolate::CreateParams create_params;
|
||||
create_params.array_buffer_allocator = Shell::array_buffer_allocator;
|
||||
@ -1857,6 +1985,8 @@ void SourceGroup::ExecuteInThread() {
|
||||
Local<Context> context = Shell::CreateEvaluationContext(isolate);
|
||||
{
|
||||
Context::Scope cscope(context);
|
||||
InspectorClient inspector_client(context,
|
||||
Shell::options.enable_inspector);
|
||||
PerIsolateData::RealmScope realm_scope(PerIsolateData::Get(isolate));
|
||||
Execute(isolate);
|
||||
}
|
||||
@ -2252,6 +2382,9 @@ bool Shell::SetOptions(int argc, char* argv[]) {
|
||||
} else if (strncmp(argv[i], "--trace-config=", 15) == 0) {
|
||||
options.trace_config = argv[i] + 15;
|
||||
argv[i] = NULL;
|
||||
} else if (strcmp(argv[i], "--enable-inspector") == 0) {
|
||||
options.enable_inspector = true;
|
||||
argv[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2301,6 +2434,7 @@ int Shell::RunMain(Isolate* isolate, int argc, char* argv[], bool last_run) {
|
||||
}
|
||||
{
|
||||
Context::Scope cscope(context);
|
||||
InspectorClient inspector_client(context, options.enable_inspector);
|
||||
PerIsolateData::RealmScope realm_scope(PerIsolateData::Get(isolate));
|
||||
options.isolate_sources[0].Execute(isolate);
|
||||
}
|
||||
|
2
src/d8.h
2
src/d8.h
@ -274,6 +274,7 @@ class ShellOptions {
|
||||
dump_heap_constants(false),
|
||||
expected_to_throw(false),
|
||||
mock_arraybuffer_allocator(false),
|
||||
enable_inspector(false),
|
||||
num_isolates(1),
|
||||
compile_options(v8::ScriptCompiler::kNoCompileOptions),
|
||||
isolate_sources(NULL),
|
||||
@ -303,6 +304,7 @@ class ShellOptions {
|
||||
bool dump_heap_constants;
|
||||
bool expected_to_throw;
|
||||
bool mock_arraybuffer_allocator;
|
||||
bool enable_inspector;
|
||||
int num_isolates;
|
||||
v8::ScriptCompiler::CompileOptions compile_options;
|
||||
SourceGroup* isolate_sources;
|
||||
|
@ -9,6 +9,7 @@
|
||||
},
|
||||
'includes': [
|
||||
'cctest/cctest.isolate',
|
||||
'debugger/debugger.isolate',
|
||||
'fuzzer/fuzzer.isolate',
|
||||
'inspector/inspector.isolate',
|
||||
'intl/intl.isolate',
|
||||
|
26
test/debugger/debugger.gyp
Normal file
26
test/debugger/debugger.gyp
Normal file
@ -0,0 +1,26 @@
|
||||
# Copyright 2016 the V8 project authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
{
|
||||
'conditions': [
|
||||
['test_isolation_mode != "noop"', {
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'debugger_run',
|
||||
'type': 'none',
|
||||
'dependencies': [
|
||||
'../../src/d8.gyp:d8_run',
|
||||
],
|
||||
'includes': [
|
||||
'../../gypfiles/features.gypi',
|
||||
'../../gypfiles/isolate.gypi',
|
||||
],
|
||||
'sources': [
|
||||
'debugger.isolate',
|
||||
],
|
||||
},
|
||||
],
|
||||
}],
|
||||
],
|
||||
}
|
22
test/debugger/debugger.isolate
Normal file
22
test/debugger/debugger.isolate
Normal file
@ -0,0 +1,22 @@
|
||||
# Copyright 2016 the V8 project authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
{
|
||||
'conditions': [
|
||||
['v8_enable_inspector==1', {
|
||||
'variables': {
|
||||
'files': [
|
||||
'./debugger.status',
|
||||
'./protocol/',
|
||||
'./test-api.js',
|
||||
'./testcfg.py',
|
||||
],
|
||||
},
|
||||
}],
|
||||
],
|
||||
'includes': [
|
||||
'../../src/d8.isolate',
|
||||
'../../tools/testrunner/testrunner.isolate',
|
||||
],
|
||||
}
|
6
test/debugger/debugger.status
Normal file
6
test/debugger/debugger.status
Normal file
@ -0,0 +1,6 @@
|
||||
# Copyright 2016 the V8 project authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
[
|
||||
]
|
24
test/debugger/protocol/basic-round-trip.js
Normal file
24
test/debugger/protocol/basic-round-trip.js
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2016 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
var received = 0;
|
||||
|
||||
function receive(message) {
|
||||
var message_obj = JSON.parse(message);
|
||||
assertEquals(3, message_obj.result.result.value);
|
||||
received++;
|
||||
}
|
||||
|
||||
var message = JSON.stringify({
|
||||
id : 1,
|
||||
method : "Runtime.evaluate",
|
||||
params : {
|
||||
expression: "function f() { return 2 }; 3"
|
||||
}
|
||||
});
|
||||
|
||||
send(message);
|
||||
|
||||
assertEquals(1, received);
|
||||
assertEquals(2, f());
|
5
test/debugger/test-api.js
Normal file
5
test/debugger/test-api.js
Normal file
@ -0,0 +1,5 @@
|
||||
// Copyright 2016 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// TODO: add test API implementation.
|
59
test/debugger/testcfg.py
Normal file
59
test/debugger/testcfg.py
Normal file
@ -0,0 +1,59 @@
|
||||
# Copyright 2016 the V8 project authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from testrunner.local import testsuite
|
||||
from testrunner.objects import testcase
|
||||
|
||||
FLAGS_PATTERN = re.compile(r"//\s+Flags:(.*)")
|
||||
|
||||
class DebuggerTestSuite(testsuite.TestSuite):
|
||||
|
||||
def __init__(self, name, root):
|
||||
super(DebuggerTestSuite, self).__init__(name, root)
|
||||
|
||||
def ListTests(self, context):
|
||||
tests = []
|
||||
for dirname, dirs, files in os.walk(self.root):
|
||||
for dotted in [x for x in dirs if x.startswith('.')]:
|
||||
dirs.remove(dotted)
|
||||
dirs.sort()
|
||||
files.sort()
|
||||
for filename in files:
|
||||
if (filename.endswith(".js") and filename != "test-api.js"):
|
||||
fullpath = os.path.join(dirname, filename)
|
||||
relpath = fullpath[len(self.root) + 1 : -3]
|
||||
testname = relpath.replace(os.path.sep, "/")
|
||||
test = testcase.TestCase(self, testname)
|
||||
tests.append(test)
|
||||
return tests
|
||||
|
||||
def GetFlagsForTestCase(self, testcase, context):
|
||||
source = self.GetSourceForTest(testcase)
|
||||
flags = ["--enable-inspector"] + context.mode_flags
|
||||
flags_match = re.findall(FLAGS_PATTERN, source)
|
||||
for match in flags_match:
|
||||
flags += match.strip().split()
|
||||
|
||||
files = []
|
||||
files.append(os.path.normpath(os.path.join(self.root, "..", "mjsunit", "mjsunit.js")))
|
||||
files.append(os.path.join(self.root, "test-api.js"))
|
||||
files.append(os.path.join(self.root, testcase.path + self.suffix()))
|
||||
|
||||
flags += files
|
||||
if context.isolates:
|
||||
flags.append("--isolate")
|
||||
flags += files
|
||||
|
||||
return testcase.flags + flags
|
||||
|
||||
def GetSourceForTest(self, testcase):
|
||||
filename = os.path.join(self.root, testcase.path + self.suffix())
|
||||
with open(filename) as f:
|
||||
return f.read()
|
||||
|
||||
def GetSuite(name, root):
|
||||
return DebuggerTestSuite(name, root)
|
@ -9,6 +9,7 @@
|
||||
},
|
||||
'includes': [
|
||||
'cctest/cctest.isolate',
|
||||
'debugger/debugger.isolate',
|
||||
'fuzzer/fuzzer.isolate',
|
||||
'intl/intl.isolate',
|
||||
'message/message.isolate',
|
||||
|
@ -9,6 +9,7 @@
|
||||
},
|
||||
'includes': [
|
||||
'cctest/cctest.isolate',
|
||||
'debugger/debugger.isolate',
|
||||
'inspector/inspector.isolate',
|
||||
'intl/intl.isolate',
|
||||
'mjsunit/mjsunit.isolate',
|
||||
|
@ -66,6 +66,7 @@ TEST_MAP = {
|
||||
# This needs to stay in sync with test/bot_default.isolate.
|
||||
"bot_default": [
|
||||
"mjsunit",
|
||||
"debugger",
|
||||
"cctest",
|
||||
"webkit",
|
||||
"inspector",
|
||||
@ -78,6 +79,7 @@ TEST_MAP = {
|
||||
# This needs to stay in sync with test/default.isolate.
|
||||
"default": [
|
||||
"mjsunit",
|
||||
"debugger",
|
||||
"cctest",
|
||||
"fuzzer",
|
||||
"message",
|
||||
@ -88,6 +90,7 @@ TEST_MAP = {
|
||||
# This needs to stay in sync with test/optimize_for_size.isolate.
|
||||
"optimize_for_size": [
|
||||
"mjsunit",
|
||||
"debugger",
|
||||
"cctest",
|
||||
"webkit",
|
||||
"inspector",
|
||||
@ -610,6 +613,9 @@ def ProcessOptions(options):
|
||||
if not options.enable_inspector:
|
||||
TEST_MAP["bot_default"].remove("inspector")
|
||||
TEST_MAP["optimize_for_size"].remove("inspector")
|
||||
TEST_MAP["default"].remove("debugger")
|
||||
TEST_MAP["bot_default"].remove("debugger")
|
||||
TEST_MAP["optimize_for_size"].remove("debugger")
|
||||
return True
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user