first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,256 @@
/*
* 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/json.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <jsinspector-modern/HostTarget.h>
#include <jsinspector-modern/InspectorInterfaces.h>
#include <memory>
#include <source_location>
#include "FollyDynamicMatchers.h"
#include "GmockHelpers.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, typename Executor>
class JsiIntegrationPortableTestBase : public ::testing::Test, private HostTargetDelegate {
protected:
Executor executor_;
JsiIntegrationPortableTestBase(InspectorFlagOverrides overrides = {})
: inspectorFlagsGuard_(overrides), engineAdapter_{executor_}
{
}
void SetUp() override
{
// NOTE: Using SetUp() so we can call virtual methods like
// setupRuntimeBeforeRegistration().
page_ = HostTarget::create(*this, inspectorExecutor_);
instance_ = &page_->registerInstance(instanceTargetDelegate_);
setupRuntimeBeforeRegistration(engineAdapter_->getRuntime());
runtimeTarget_ =
&instance_->registerRuntime(engineAdapter_->getRuntimeTargetDelegate(), engineAdapter_->getRuntimeExecutor());
loadMainBundle();
}
~JsiIntegrationPortableTestBase() override
{
toPage_.reset();
if (runtimeTarget_ != nullptr) {
EXPECT_TRUE(instance_);
instance_->unregisterRuntime(*runtimeTarget_);
runtimeTarget_ = nullptr;
}
if (instance_ != nullptr) {
page_->unregisterInstance(*instance_);
instance_ = nullptr;
}
}
/**
* Noop in JsiIntegrationPortableTest, but can be overridden by derived
* fixture classes to load some code at startup and after each reload.
*/
virtual void loadMainBundle() {}
/**
* Noop in JsiIntegrationPortableTest, but can be overridden by derived
* fixture classes to set up the runtime before registering it with the
* CDP backend.
*/
virtual void setupRuntimeBeforeRegistration(jsi::Runtime & /*runtime*/) {}
void connect(std::source_location location = std::source_location::current())
{
ASSERT_FALSE(toPage_) << "Can only connect once in a JSI integration test.";
toPage_ = page_->connect(remoteConnections_.make_unique());
using namespace ::testing;
// Default to ignoring console messages originating inside the backend.
EXPECT_CALL_WITH_SOURCE_LOCATION(
location,
fromPage(),
onMessage(JsonParsed(AllOf(
AtJsonPtr("/method", "Runtime.consoleAPICalled"), AtJsonPtr("/params/context", "main#InstanceAgent")))))
.Times(AnyNumber());
// We'll always get an onDisconnect call when we tear
// down the test. Expect it in order to satisfy the strict mock.
EXPECT_CALL_WITH_SOURCE_LOCATION(location, *remoteConnections_[0], onDisconnect());
}
void reload()
{
if (runtimeTarget_ != nullptr) {
ASSERT_TRUE(instance_);
instance_->unregisterRuntime(*runtimeTarget_);
runtimeTarget_ = nullptr;
}
if (instance_ != nullptr) {
page_->unregisterInstance(*instance_);
instance_ = nullptr;
}
// Recreate the engine (e.g. to wipe any state in the inner jsi::Runtime)
engineAdapter_.emplace(executor_);
instance_ = &page_->registerInstance(instanceTargetDelegate_);
setupRuntimeBeforeRegistration(engineAdapter_->getRuntime());
runtimeTarget_ =
&instance_->registerRuntime(engineAdapter_->getRuntimeTargetDelegate(), engineAdapter_->getRuntimeExecutor());
loadMainBundle();
}
MockRemoteConnection &fromPage()
{
assert(toPage_);
return *remoteConnections_[0];
}
VoidExecutor inspectorExecutor_ = [this](auto callback) { executor_.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::source_location location = std::source_location::current())
{
using namespace ::testing;
ScopedTrace scope(location.file_name(), location.line(), "");
std::shared_ptr result = std::make_shared<std::optional<folly::dynamic>>(std::nullopt);
EXPECT_CALL_WITH_SOURCE_LOCATION(location, fromPage(), onMessage(matcher))
.WillOnce(([result](auto message) { *result = folly::parseJson(message); }))
.RetiresOnSaturation();
return result;
}
RuntimeTargetDelegate &dangerouslyGetRuntimeTargetDelegate()
{
return engineAdapter_->getRuntimeTargetDelegate();
}
jsi::Runtime &dangerouslyGetRuntime()
{
return engineAdapter_->getRuntime();
}
class SecondaryConnection {
public:
SecondaryConnection(
std::unique_ptr<ILocalConnection> toPage,
JsiIntegrationPortableTestBase<EngineAdapter, Executor> &test,
size_t remoteConnectionIndex)
: toPage_(std::move(toPage)), remoteConnectionIndex_(remoteConnectionIndex), test_(test)
{
}
ILocalConnection &toPage()
{
return *toPage_;
}
MockRemoteConnection &fromPage()
{
return *test_.remoteConnections_[remoteConnectionIndex_];
}
private:
std::unique_ptr<ILocalConnection> toPage_;
size_t remoteConnectionIndex_;
JsiIntegrationPortableTestBase<EngineAdapter, Executor> &test_;
};
SecondaryConnection connectSecondary(std::source_location location = std::source_location::current())
{
auto toPage = page_->connect(remoteConnections_.make_unique());
SecondaryConnection secondary{std::move(toPage), *this, remoteConnections_.objectsVended() - 1};
using namespace ::testing;
// Default to ignoring console messages originating inside the backend.
EXPECT_CALL_WITH_SOURCE_LOCATION(
location,
secondary.fromPage(),
onMessage(JsonParsed(AllOf(
AtJsonPtr("/method", "Runtime.consoleAPICalled"), AtJsonPtr("/params/context", "main#InstanceAgent")))))
.Times(AnyNumber());
// We'll always get an onDisconnect call when we tear
// down the test. Expect it in order to satisfy the strict mock.
EXPECT_CALL_WITH_SOURCE_LOCATION(location, secondary.fromPage(), onDisconnect());
return secondary;
}
std::shared_ptr<HostTarget> page_;
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
HostTargetMetadata getMetadata() override
{
return {.integrationName = "JsiIntegrationTest"};
}
void onReload(const PageReloadRequest &request) override
{
(void)request;
reload();
}
void onSetPausedInDebuggerMessage(const OverlaySetPausedInDebuggerMessageRequest & /*request*/) override {}
};
} // namespace facebook::react::jsinspector_modern