Make JsiIntegrationPortableTest reusable in other test suites

Summary:
Changelog: [Internal]

TSIA - useful for the `console` test suite I'm currently writing (D54846940) which would be awkward to fit into `JsiIntegrationTest.cpp`.

bypass-github-export-checks

Reviewed By: huntie

Differential Revision: D54846938

fbshipit-source-id: 9e20bfae7c518b3822da468adf484e51cdc4d0b9
This commit is contained in:
Moti Zilberman
2024-03-15 04:23:57 -07:00
committed by Facebook GitHub Bot
parent 949296571b
commit 121b26184a
2 changed files with 159 additions and 145 deletions
@@ -6,20 +6,8 @@
*/
#include <folly/Format.h>
#include <folly/dynamic.h>
#include <folly/executors/QueuedImmediateExecutor.h>
#include <folly/json.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <jsinspector-modern/HostTarget.h>
#include <jsinspector-modern/InspectorInterfaces.h>
#include <memory>
#include "FollyDynamicMatchers.h"
#include "InspectorMocks.h"
#include "UniquePtrFactory.h"
#include "JsiIntegrationTest.h"
#include "engines/JsiIntegrationTestGenericEngineAdapter.h"
#include "engines/JsiIntegrationTestHermesEngineAdapter.h"
#include "engines/JsiIntegrationTestHermesWithCDPAgentEngineAdapter.h"
@@ -29,138 +17,6 @@ using folly::sformat;
namespace facebook::react::jsinspector_modern {
namespace {
/**
* A text fixture class for the integration between the modern RN CDP backend
* and a JSI engine, mocking out the rest of RN. For simplicity, everything is
* single-threaded and "async" work is actually done through a queued immediate
* executor ( = run immediately and finish all queued sub-tasks before
* returning).
*
* The main limitation of the simpler threading model is that we can't cover
* breakpoints etc - since pausing during JS execution would prevent the test
* from making progress. Such functionality is better suited for a full RN+CDP
* integration test (using RN's own thread management) as well as for each
* engine's unit tests.
*
* \tparam EngineAdapter An adapter class that implements RuntimeTargetDelegate
* for a particular engine, plus exposes access to a RuntimeExecutor (based on
* the provided folly::Executor) and the corresponding jsi::Runtime.
*/
template <typename EngineAdapter>
class JsiIntegrationPortableTest : public Test, private HostTargetDelegate {
folly::QueuedImmediateExecutor immediateExecutor_;
protected:
JsiIntegrationPortableTest()
: inspectorFlagsGuard_{EngineAdapter::getInspectorFlagOverrides()},
engineAdapter_{immediateExecutor_} {
instance_ = &page_->registerInstance(instanceTargetDelegate_);
runtimeTarget_ = &instance_->registerRuntime(
engineAdapter_->getRuntimeTargetDelegate(),
engineAdapter_->getRuntimeExecutor());
}
~JsiIntegrationPortableTest() override {
toPage_.reset();
if (runtimeTarget_) {
EXPECT_TRUE(instance_);
instance_->unregisterRuntime(*runtimeTarget_);
runtimeTarget_ = nullptr;
}
if (instance_) {
page_->unregisterInstance(*instance_);
instance_ = nullptr;
}
}
void connect() {
ASSERT_FALSE(toPage_) << "Can only connect once in a JSI integration test.";
toPage_ = page_->connect(
remoteConnections_.make_unique(),
{.integrationName = "JsiIntegrationTest"});
// We'll always get an onDisconnect call when we tear
// down the test. Expect it in order to satisfy the strict mock.
EXPECT_CALL(*remoteConnections_[0], onDisconnect());
}
void reload() {
if (runtimeTarget_) {
ASSERT_TRUE(instance_);
instance_->unregisterRuntime(*runtimeTarget_);
runtimeTarget_ = nullptr;
}
if (instance_) {
page_->unregisterInstance(*instance_);
instance_ = nullptr;
}
// Recreate the engine (e.g. to wipe any state in the inner jsi::Runtime)
engineAdapter_.emplace(immediateExecutor_);
instance_ = &page_->registerInstance(instanceTargetDelegate_);
runtimeTarget_ = &instance_->registerRuntime(
engineAdapter_->getRuntimeTargetDelegate(),
engineAdapter_->getRuntimeExecutor());
}
MockRemoteConnection& fromPage() {
assert(toPage_);
return *remoteConnections_[0];
}
VoidExecutor inspectorExecutor_ = [this](auto callback) {
immediateExecutor_.add(callback);
};
jsi::Value eval(std::string_view code) {
return engineAdapter_->getRuntime().evaluateJavaScript(
std::make_shared<jsi::StringBuffer>(std::string(code)), "<eval>");
}
/**
* Expect a message matching the provided gmock \c matcher and return a holder
* that will eventually contain the parsed JSON payload.
*/
template <typename Matcher>
std::shared_ptr<const std::optional<folly::dynamic>> expectMessageFromPage(
Matcher&& matcher) {
std::shared_ptr result =
std::make_shared<std::optional<folly::dynamic>>(std::nullopt);
EXPECT_CALL(fromPage(), onMessage(matcher))
.WillOnce(
([result](auto message) { *result = folly::parseJson(message); }))
.RetiresOnSaturation();
return result;
}
std::shared_ptr<HostTarget> page_ =
HostTarget::create(*this, inspectorExecutor_);
InstanceTarget* instance_{};
RuntimeTarget* runtimeTarget_{};
InspectorFlagOverridesGuard inspectorFlagsGuard_;
MockInstanceTargetDelegate instanceTargetDelegate_;
std::optional<EngineAdapter> engineAdapter_;
private:
UniquePtrFactory<StrictMock<MockRemoteConnection>> remoteConnections_;
protected:
// NOTE: Needs to be destroyed before page_.
std::unique_ptr<ILocalConnection> toPage_;
private:
// HostTargetDelegate methods
void onReload(const PageReloadRequest& request) override {
(void)request;
reload();
}
};
} // namespace
////////////////////////////////////////////////////////////////////////////////
// Some tests are specific to Hermes's CDP capabilities and some are not.
@@ -0,0 +1,158 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <folly/dynamic.h>
#include <folly/executors/QueuedImmediateExecutor.h>
#include <folly/json.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <jsinspector-modern/HostTarget.h>
#include <jsinspector-modern/InspectorInterfaces.h>
#include <memory>
#include "FollyDynamicMatchers.h"
#include "InspectorMocks.h"
#include "UniquePtrFactory.h"
#include "utils/InspectorFlagOverridesGuard.h"
namespace facebook::react::jsinspector_modern {
/**
* A text fixture class for the integration between the modern RN CDP backend
* and a JSI engine, mocking out the rest of RN. For simplicity, everything is
* single-threaded and "async" work is actually done through a queued immediate
* executor ( = run immediately and finish all queued sub-tasks before
* returning).
*
* The main limitation of the simpler threading model is that we can't cover
* breakpoints etc - since pausing during JS execution would prevent the test
* from making progress. Such functionality is better suited for a full RN+CDP
* integration test (using RN's own thread management) as well as for each
* engine's unit tests.
*
* \tparam EngineAdapter An adapter class that implements RuntimeTargetDelegate
* for a particular engine, plus exposes access to a RuntimeExecutor (based on
* the provided folly::Executor) and the corresponding jsi::Runtime.
*/
template <typename EngineAdapter>
class JsiIntegrationPortableTest : public ::testing::Test,
private HostTargetDelegate {
folly::QueuedImmediateExecutor immediateExecutor_;
protected:
JsiIntegrationPortableTest()
: inspectorFlagsGuard_{EngineAdapter::getInspectorFlagOverrides()},
engineAdapter_{immediateExecutor_} {
instance_ = &page_->registerInstance(instanceTargetDelegate_);
runtimeTarget_ = &instance_->registerRuntime(
engineAdapter_->getRuntimeTargetDelegate(),
engineAdapter_->getRuntimeExecutor());
}
~JsiIntegrationPortableTest() override {
toPage_.reset();
if (runtimeTarget_) {
EXPECT_TRUE(instance_);
instance_->unregisterRuntime(*runtimeTarget_);
runtimeTarget_ = nullptr;
}
if (instance_) {
page_->unregisterInstance(*instance_);
instance_ = nullptr;
}
}
void connect() {
ASSERT_FALSE(toPage_) << "Can only connect once in a JSI integration test.";
toPage_ = page_->connect(
remoteConnections_.make_unique(),
{.integrationName = "JsiIntegrationTest"});
// We'll always get an onDisconnect call when we tear
// down the test. Expect it in order to satisfy the strict mock.
EXPECT_CALL(*remoteConnections_[0], onDisconnect());
}
void reload() {
if (runtimeTarget_) {
ASSERT_TRUE(instance_);
instance_->unregisterRuntime(*runtimeTarget_);
runtimeTarget_ = nullptr;
}
if (instance_) {
page_->unregisterInstance(*instance_);
instance_ = nullptr;
}
// Recreate the engine (e.g. to wipe any state in the inner jsi::Runtime)
engineAdapter_.emplace(immediateExecutor_);
instance_ = &page_->registerInstance(instanceTargetDelegate_);
runtimeTarget_ = &instance_->registerRuntime(
engineAdapter_->getRuntimeTargetDelegate(),
engineAdapter_->getRuntimeExecutor());
}
MockRemoteConnection& fromPage() {
assert(toPage_);
return *remoteConnections_[0];
}
VoidExecutor inspectorExecutor_ = [this](auto callback) {
immediateExecutor_.add(callback);
};
jsi::Value eval(std::string_view code) {
return engineAdapter_->getRuntime().evaluateJavaScript(
std::make_shared<jsi::StringBuffer>(std::string(code)), "<eval>");
}
/**
* Expect a message matching the provided gmock \c matcher and return a holder
* that will eventually contain the parsed JSON payload.
*/
template <typename Matcher>
std::shared_ptr<const std::optional<folly::dynamic>> expectMessageFromPage(
Matcher&& matcher) {
std::shared_ptr result =
std::make_shared<std::optional<folly::dynamic>>(std::nullopt);
EXPECT_CALL(fromPage(), onMessage(matcher))
.WillOnce(
([result](auto message) { *result = folly::parseJson(message); }))
.RetiresOnSaturation();
return result;
}
std::shared_ptr<HostTarget> page_ =
HostTarget::create(*this, inspectorExecutor_);
InstanceTarget* instance_{};
RuntimeTarget* runtimeTarget_{};
InspectorFlagOverridesGuard inspectorFlagsGuard_;
MockInstanceTargetDelegate instanceTargetDelegate_;
std::optional<EngineAdapter> engineAdapter_;
private:
UniquePtrFactory<::testing::StrictMock<MockRemoteConnection>>
remoteConnections_;
protected:
// NOTE: Needs to be destroyed before page_.
std::unique_ptr<ILocalConnection> toPage_;
private:
// HostTargetDelegate methods
void onReload(const PageReloadRequest& request) override {
(void)request;
reload();
}
};
} // namespace facebook::react::jsinspector_modern