[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:
yangguo 2016-10-20 23:37:29 -07:00 committed by Commit bot
parent 6e89606756
commit 2f135d464c
14 changed files with 295 additions and 2 deletions

View File

@ -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") {

View File

@ -27,6 +27,7 @@
}],
['v8_enable_inspector==1', {
'dependencies': [
'../test/debugger/debugger.gyp:*',
'../test/inspector/inspector.gyp:*',
],
}],

138
src/d8.cc
View File

@ -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);
}

View File

@ -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;

View File

@ -9,6 +9,7 @@
},
'includes': [
'cctest/cctest.isolate',
'debugger/debugger.isolate',
'fuzzer/fuzzer.isolate',
'inspector/inspector.isolate',
'intl/intl.isolate',

View 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',
],
},
],
}],
],
}

View 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',
],
}

View 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.
[
]

View 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());

View 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
View 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)

View File

@ -9,6 +9,7 @@
},
'includes': [
'cctest/cctest.isolate',
'debugger/debugger.isolate',
'fuzzer/fuzzer.isolate',
'intl/intl.isolate',
'message/message.isolate',

View File

@ -9,6 +9,7 @@
},
'includes': [
'cctest/cctest.isolate',
'debugger/debugger.isolate',
'inspector/inspector.isolate',
'intl/intl.isolate',
'mjsunit/mjsunit.isolate',

View File

@ -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