mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
e6f3388541
Summary: Changelog: [Internal] The inspector API doesn't really need a `HermesRuntime`, all it needs is a `jsi::Runtime` and a `Debugger &`. Change the return type of `RuntimeAdapter::getRuntime` to be `jsi::Runtime`. This will allow the inspector to use the tracing runtime instead of the direct hermes runtime. Reviewed By: willholen Differential Revision: D18973867 fbshipit-source-id: 6809e52452a35e62be9ca8143aeaba8964c98eaa
413 lines
11 KiB
C++
413 lines
11 KiB
C++
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#include <hermes/inspector/Inspector.h>
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <memory>
|
|
|
|
#include <folly/futures/Future.h>
|
|
#include <gtest/gtest.h>
|
|
#include <hermes/inspector/RuntimeAdapter.h>
|
|
|
|
namespace facebook {
|
|
namespace hermes {
|
|
namespace inspector {
|
|
|
|
namespace debugger = facebook::hermes::debugger;
|
|
using namespace std::chrono_literals;
|
|
using Unit = folly::Unit;
|
|
|
|
static auto constexpr kDefaultTimeout = 5000ms;
|
|
|
|
namespace {
|
|
|
|
int getCurrentLine(const debugger::ProgramState &state) {
|
|
return state.getStackTrace().callFrameForIndex(0).location.line;
|
|
}
|
|
|
|
debugger::SourceLocation locationForLine(int line) {
|
|
debugger::SourceLocation loc;
|
|
loc.line = line;
|
|
return loc;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/*
|
|
* LambdaInspectorObserver is useful for sequencing calls to the debugger based
|
|
* on the number of onPause() callbacks.
|
|
*/
|
|
|
|
using OnPauseFunction =
|
|
std::function<void(Inspector &, const debugger::ProgramState &, int)>;
|
|
|
|
class LambdaInspectorObserver : public InspectorObserver {
|
|
public:
|
|
LambdaInspectorObserver(OnPauseFunction func)
|
|
: onPauseFunc_(func), pauseCount_(0) {}
|
|
~LambdaInspectorObserver() = default;
|
|
|
|
void onBreakpointResolved(
|
|
Inspector &inspector,
|
|
const debugger::BreakpointInfo &info) override {}
|
|
|
|
void onContextCreated(Inspector &inspector) override {}
|
|
|
|
void onPause(Inspector &inspector, const debugger::ProgramState &state)
|
|
override {
|
|
pauseCount_++;
|
|
onPauseFunc_(inspector, state, pauseCount_);
|
|
}
|
|
|
|
void onResume(Inspector &inspector) override {}
|
|
|
|
void onScriptParsed(Inspector &inspector, const ScriptInfo &info) override {}
|
|
|
|
void onMessageAdded(Inspector &inspector, const ConsoleMessageInfo &info)
|
|
override{};
|
|
|
|
int getPauseCount() {
|
|
return pauseCount_;
|
|
}
|
|
|
|
private:
|
|
OnPauseFunction onPauseFunc_;
|
|
int pauseCount_;
|
|
};
|
|
|
|
/*
|
|
* Helpers for running JS in a separate thread.
|
|
*/
|
|
|
|
struct HermesDebugContext {
|
|
HermesDebugContext(
|
|
InspectorObserver &observer,
|
|
folly::Future<Unit> &&finished)
|
|
: runtime(makeHermesRuntime()),
|
|
inspector(
|
|
std::make_shared<SharedRuntimeAdapter>(
|
|
runtime,
|
|
runtime->getDebugger()),
|
|
observer,
|
|
false),
|
|
stopFlag(false),
|
|
finished(std::move(finished)) {
|
|
runtime->global().setProperty(
|
|
*runtime,
|
|
"shouldStop",
|
|
jsi::Function::createFromHostFunction(
|
|
*runtime,
|
|
jsi::PropNameID::forAscii(*runtime, "shouldStop"),
|
|
0,
|
|
[this](
|
|
jsi::Runtime &,
|
|
const jsi::Value &,
|
|
const jsi::Value *args,
|
|
size_t count) {
|
|
return stopFlag.load() ? jsi::Value(true) : jsi::Value(false);
|
|
}));
|
|
}
|
|
~HermesDebugContext() = default;
|
|
|
|
void setStopFlag() {
|
|
stopFlag.store(true);
|
|
}
|
|
|
|
void wait(std::chrono::milliseconds timeout = kDefaultTimeout) {
|
|
std::move(finished).get(timeout);
|
|
}
|
|
|
|
std::shared_ptr<HermesRuntime> runtime;
|
|
Inspector inspector;
|
|
std::atomic<bool> stopFlag{};
|
|
folly::Future<Unit> finished;
|
|
};
|
|
|
|
static std::shared_ptr<HermesDebugContext> runScriptAsync(
|
|
InspectorObserver &observer,
|
|
const std::string &script) {
|
|
auto promise = std::make_shared<folly::Promise<Unit>>();
|
|
auto future = promise->getFuture();
|
|
auto context =
|
|
std::make_shared<HermesDebugContext>(observer, std::move(future));
|
|
|
|
std::thread t([=]() {
|
|
HermesRuntime::DebugFlags flags{};
|
|
context->runtime->debugJavaScript(script, "url", flags);
|
|
promise->setValue();
|
|
});
|
|
t.detach();
|
|
|
|
return context;
|
|
}
|
|
|
|
/*
|
|
* Tests
|
|
*/
|
|
|
|
TEST(InspectorTests, testStepOver) {
|
|
std::string script = R"(
|
|
var a = 1 + 2;
|
|
debugger;
|
|
var b = a / 2;
|
|
var c = a + b;
|
|
var d = b - c;
|
|
var e = c * d;
|
|
var f = 10;
|
|
)";
|
|
|
|
// TODO: move this vector into lambdaInspectorObserver
|
|
std::vector<folly::Future<Unit>> futures;
|
|
|
|
OnPauseFunction onPauseFunc = [&futures](
|
|
Inspector &inspector,
|
|
const debugger::ProgramState &state,
|
|
int pauseCount) {
|
|
switch (pauseCount) {
|
|
case 1: {
|
|
EXPECT_EQ(
|
|
state.getPauseReason(), debugger::PauseReason::DebuggerStatement);
|
|
EXPECT_EQ(getCurrentLine(state), 3);
|
|
|
|
futures.emplace_back(inspector.stepOver());
|
|
|
|
break;
|
|
}
|
|
case 2: {
|
|
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::StepFinish);
|
|
EXPECT_EQ(getCurrentLine(state), 4);
|
|
|
|
futures.emplace_back(inspector.stepOver());
|
|
|
|
break;
|
|
}
|
|
case 3: {
|
|
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::StepFinish);
|
|
EXPECT_EQ(getCurrentLine(state), 5);
|
|
|
|
futures.emplace_back(inspector.resume());
|
|
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
LambdaInspectorObserver observer(onPauseFunc);
|
|
std::shared_ptr<HermesDebugContext> context =
|
|
runScriptAsync(observer, script);
|
|
|
|
// TODO: temporarily do this to ensure i hit failure case
|
|
std::this_thread::sleep_for(1000ms);
|
|
|
|
futures.emplace_back(context->inspector.enable());
|
|
|
|
context->wait();
|
|
folly::collectAll(futures).get(kDefaultTimeout);
|
|
|
|
EXPECT_EQ(observer.getPauseCount(), 3);
|
|
}
|
|
|
|
TEST(InspectorTests, testSetBreakpoint) {
|
|
std::string script = R"(
|
|
var a = 1 + 2;
|
|
debugger;
|
|
var b = a / 2;
|
|
var c = a + b;
|
|
var d = b - c;
|
|
var e = c * d;
|
|
var f = 10;
|
|
)";
|
|
|
|
std::vector<folly::Future<Unit>> futures;
|
|
|
|
OnPauseFunction onPauseFunc = [&futures](
|
|
Inspector &inspector,
|
|
const debugger::ProgramState &state,
|
|
int pauseCount) {
|
|
switch (pauseCount) {
|
|
case 1: {
|
|
EXPECT_EQ(
|
|
state.getPauseReason(), debugger::PauseReason::DebuggerStatement);
|
|
EXPECT_EQ(getCurrentLine(state), 3);
|
|
|
|
auto stepFuture = inspector.stepOver();
|
|
futures.emplace_back(std::move(stepFuture));
|
|
|
|
break;
|
|
}
|
|
case 2: {
|
|
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::StepFinish);
|
|
EXPECT_EQ(getCurrentLine(state), 4);
|
|
|
|
auto breakpointFuture = inspector.setBreakpoint(locationForLine(6));
|
|
futures.emplace_back(std::move(breakpointFuture)
|
|
.thenValue([](debugger::BreakpointInfo info) {
|
|
EXPECT_EQ(info.resolvedLocation.line, 6);
|
|
}));
|
|
|
|
auto resumeFuture = inspector.resume();
|
|
futures.emplace_back(std::move(resumeFuture));
|
|
|
|
break;
|
|
}
|
|
case 3: {
|
|
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::Breakpoint);
|
|
EXPECT_EQ(getCurrentLine(state), 6);
|
|
|
|
auto resumeFuture = inspector.resume();
|
|
futures.emplace_back(std::move(resumeFuture));
|
|
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
LambdaInspectorObserver observer(onPauseFunc);
|
|
std::shared_ptr<HermesDebugContext> context =
|
|
runScriptAsync(observer, script);
|
|
|
|
auto enablePromise = context->inspector.enable();
|
|
futures.emplace_back(std::move(enablePromise));
|
|
|
|
context->wait();
|
|
folly::collectAll(futures).get(kDefaultTimeout);
|
|
|
|
EXPECT_EQ(observer.getPauseCount(), 3);
|
|
}
|
|
|
|
TEST(InspectorTests, testAsyncSetBreakpoint) {
|
|
std::string script = R"(
|
|
while (!shouldStop()) {
|
|
var a = 1;
|
|
var b = 2;
|
|
var c = a + b;
|
|
var d = 10;
|
|
}
|
|
)";
|
|
|
|
std::vector<folly::Future<Unit>> futures;
|
|
folly::Func stopFunc;
|
|
|
|
OnPauseFunction onPauseFunc = [&futures, &stopFunc](
|
|
Inspector &inspector,
|
|
const debugger::ProgramState &state,
|
|
int pauseCount) {
|
|
switch (pauseCount) {
|
|
case 1: {
|
|
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::Breakpoint);
|
|
EXPECT_EQ(getCurrentLine(state), 4);
|
|
|
|
auto stepFuture = inspector.stepOver();
|
|
futures.emplace_back(std::move(stepFuture));
|
|
|
|
break;
|
|
}
|
|
case 2: {
|
|
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::StepFinish);
|
|
EXPECT_EQ(getCurrentLine(state), 5);
|
|
|
|
stopFunc();
|
|
|
|
auto resumeFuture = inspector.resume();
|
|
futures.emplace_back(std::move(resumeFuture));
|
|
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
LambdaInspectorObserver observer(onPauseFunc);
|
|
std::shared_ptr<HermesDebugContext> context =
|
|
runScriptAsync(observer, script);
|
|
|
|
stopFunc = [context]() { context->setStopFlag(); };
|
|
|
|
context->inspector.enable();
|
|
|
|
auto breakpointPromise = context->inspector.setBreakpoint(locationForLine(4))
|
|
.thenValue([](debugger::BreakpointInfo info) {
|
|
EXPECT_EQ(info.resolvedLocation.line, 4);
|
|
});
|
|
|
|
context->wait();
|
|
|
|
futures.emplace_back(std::move(breakpointPromise));
|
|
folly::collectAll(futures).get(kDefaultTimeout);
|
|
|
|
EXPECT_EQ(observer.getPauseCount(), 2);
|
|
}
|
|
|
|
TEST(InspectorTests, testDisable) {
|
|
std::string script = R"(
|
|
var a = 1 + 2;
|
|
debugger;
|
|
var b = a / 2;
|
|
var c = a + b;
|
|
var d = b - c;
|
|
var e = c * d;
|
|
var f = 10;
|
|
)";
|
|
|
|
std::vector<folly::Future<Unit>> futures;
|
|
|
|
OnPauseFunction onPauseFunc = [&futures](
|
|
Inspector &inspector,
|
|
const debugger::ProgramState &state,
|
|
int pauseCount) {
|
|
switch (pauseCount) {
|
|
case 1: {
|
|
EXPECT_EQ(
|
|
state.getPauseReason(), debugger::PauseReason::DebuggerStatement);
|
|
EXPECT_EQ(getCurrentLine(state), 3);
|
|
|
|
auto stepFuture = inspector.stepOver();
|
|
futures.emplace_back(std::move(stepFuture));
|
|
|
|
break;
|
|
}
|
|
case 2: {
|
|
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::StepFinish);
|
|
EXPECT_EQ(getCurrentLine(state), 4);
|
|
|
|
futures.emplace_back(inspector.setBreakpoint(locationForLine(6))
|
|
.thenValue([](debugger::BreakpointInfo info) {
|
|
EXPECT_EQ(info.resolvedLocation.line, 6);
|
|
}));
|
|
futures.emplace_back(inspector.setBreakpoint(locationForLine(7))
|
|
.thenValue([](debugger::BreakpointInfo info) {
|
|
EXPECT_EQ(info.resolvedLocation.line, 7);
|
|
}));
|
|
|
|
auto detachFuture = inspector.disable();
|
|
futures.emplace_back(std::move(detachFuture));
|
|
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
LambdaInspectorObserver observer(onPauseFunc);
|
|
std::shared_ptr<HermesDebugContext> context =
|
|
runScriptAsync(observer, script);
|
|
|
|
auto enablePromise = context->inspector.enable();
|
|
futures.emplace_back(std::move(enablePromise));
|
|
|
|
context->wait();
|
|
folly::collectAll(futures).get(kDefaultTimeout);
|
|
|
|
EXPECT_EQ(observer.getPauseCount(), 2);
|
|
}
|
|
|
|
} // namespace inspector
|
|
} // namespace hermes
|
|
} // namespace facebook
|