This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

View File

@ -0,0 +1,412 @@
/*
* 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