diff --git a/ReactCommon/react/renderer/animations/BUCK b/ReactCommon/react/renderer/animations/BUCK index a90d9eda97c..fbf1c6c6139 100644 --- a/ReactCommon/react/renderer/animations/BUCK +++ b/ReactCommon/react/renderer/animations/BUCK @@ -91,6 +91,7 @@ fb_xplat_cxx_test( react_native_xplat_target("react/renderer/components/root:root"), react_native_xplat_target("react/renderer/components/scrollview:scrollview"), react_native_xplat_target("react/renderer/components/view:view"), + react_native_xplat_target("react/test_utils:test_utils"), "//xplat/js/react-native-github:generated_components-rncore", ], ) diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp index 3e39886dae9..6d8d229fa8c 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp +++ b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp @@ -307,14 +307,14 @@ void LayoutAnimationKeyFrameManager::uiManagerDidConfigureNextLayoutAnimation( if (layoutAnimationConfig) { std::lock_guard lock(currentAnimationMutex_); - currentAnimation_ = better::optional{LayoutAnimation{ + uiManagerDidConfigureNextLayoutAnimation(LayoutAnimation{ -1, 0, false, *layoutAnimationConfig, successCallback, failureCallback, - {}}}; + {}}); } else { LOG(ERROR) << "Parsing LayoutAnimationConfig failed: " << (folly::dynamic)config; @@ -323,6 +323,11 @@ void LayoutAnimationKeyFrameManager::uiManagerDidConfigureNextLayoutAnimation( } } +void LayoutAnimationKeyFrameManager::uiManagerDidConfigureNextLayoutAnimation( + LayoutAnimation layoutAnimation) const { + currentAnimation_ = better::optional{layoutAnimation}; +} + void LayoutAnimationKeyFrameManager::setLayoutAnimationStatusDelegate( LayoutAnimationStatusDelegate *delegate) const { std::lock_guard lock(layoutAnimationStatusDelegateMutex_); @@ -733,6 +738,11 @@ void LayoutAnimationKeyFrameManager::getAndEraseConflictingAnimations( } } +void LayoutAnimationKeyFrameManager::setClockNow( + std::function now) { + now_ = now; +} + better::optional LayoutAnimationKeyFrameManager::pullTransaction( SurfaceId surfaceId, @@ -740,10 +750,7 @@ LayoutAnimationKeyFrameManager::pullTransaction( TransactionTelemetry const &telemetry, ShadowViewMutationList mutations) const { // Current time in milliseconds - uint64_t now = - std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()) - .count(); + uint64_t now = now_(); bool inflightAnimationsExistInitially = !inflightAnimations_.empty(); @@ -906,7 +913,7 @@ LayoutAnimationKeyFrameManager::pullTransaction( bool haveComponentDescriptor = hasComponentDescriptorForShadowView(baselineShadowView); - bool executeMutationImmediately = false; + better::optional executeMutationImmediately{}; bool isRemoveReinserted = mutation.type == ShadowViewMutation::Type::Remove && @@ -972,7 +979,7 @@ LayoutAnimationKeyFrameManager::pullTransaction( if (isRemoveReinserted || !haveConfiguration || isReparented || mutation.type == ShadowViewMutation::Type::Create || mutation.type == ShadowViewMutation::Type::Insert) { - executeMutationImmediately = true; + executeMutationImmediately = mutation; // It is possible, especially in the case of "moves", that we have a // sequence of operations like: @@ -1007,6 +1014,36 @@ LayoutAnimationKeyFrameManager::pullTransaction( } } } + } else if (mutation.type == ShadowViewMutation::Type::Remove) { + for (auto &keyframe : keyFramesToAnimate) { + if (keyframe.tag == baselineShadowView.tag) { + // If there's already an animation queued up, followed by this + // Insert, it *must* be an Update mutation animation. Other + // sequences should not be possible. + react_native_assert( + keyframe.type == AnimationConfigurationType::Update); + + // The mutation is a "remove", so it must have a + // "oldChildShadowView" + react_native_assert(mutation.oldChildShadowView.tag > 0); + + // Those asserts don't run in prod. If there's some edge-case + // that we haven't caught yet, we'd crash in debug; make sure we + // don't mutate the prevView in prod. + // Since normally the UPDATE would have been executed first and + // now it's deferred, we need to change the `oldChildShadowView` + // that is being referenced by the REMOVE mutation. + if (keyframe.type == AnimationConfigurationType::Update && + mutation.oldChildShadowView.tag > 0) { + executeMutationImmediately = ShadowViewMutation{ + mutation.type, + mutation.parentShadowView, + keyframe.viewPrev, + {}, + mutation.index}; + } + } + } } } @@ -1282,10 +1319,10 @@ LayoutAnimationKeyFrameManager::pullTransaction( keyFramesToAnimate.push_back(keyFrame); } - if (executeMutationImmediately) { + if (executeMutationImmediately.hasValue()) { PrintMutationInstruction( - "Queue Up Animation For Immediate Execution", mutation); - immediateMutations.push_back(mutation); + "Queue Up For Immediate Execution", *executeMutationImmediately); + immediateMutations.push_back(*executeMutationImmediately); } } diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h index 7b88bd985e6..eaad96b3abb 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h +++ b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h @@ -166,24 +166,35 @@ class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate, RuntimeExecutor runtimeExecutor, LayoutAnimationStatusDelegate *delegate) : runtimeExecutor_(runtimeExecutor), - layoutAnimationStatusDelegate_(delegate) {} + layoutAnimationStatusDelegate_(delegate), + now_([]() { + return std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() + .time_since_epoch()) + .count(); + }) {} ~LayoutAnimationKeyFrameManager() {} +#pragma mark UIManagerAnimationDelegate methods + void uiManagerDidConfigureNextLayoutAnimation( jsi::Runtime &runtime, RawValue const &config, const jsi::Value &successCallbackValue, const jsi::Value &failureCallbackValue) const override; + void setComponentDescriptorRegistry(SharedComponentDescriptorRegistry const & componentDescriptorRegistry) override; // TODO: add SurfaceId to this API as well bool shouldAnimateFrame() const override; - bool shouldOverridePullTransaction() const override; - void stopSurface(SurfaceId surfaceId) override; +#pragma mark MountingOverrideDelegate methods + + bool shouldOverridePullTransaction() const override; + // This is used to "hijack" the diffing process to figure out which mutations // should be animated. The mutations returned by this function will be // executed immediately. @@ -193,6 +204,11 @@ class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate, TransactionTelemetry const &telemetry, ShadowViewMutationList mutations) const override; + // Exposed for testing. + public: + void uiManagerDidConfigureNextLayoutAnimation( + LayoutAnimation layoutAnimation) const; + // LayoutAnimationStatusDelegate - this is for the platform to get // signal when animations start and complete. Setting and resetting this // delegate is protected by a mutex; ALL method calls into this delegate are @@ -207,6 +223,9 @@ class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate, mutable std::mutex layoutAnimationStatusDelegateMutex_; mutable LayoutAnimationStatusDelegate *layoutAnimationStatusDelegate_{}; + // Function that returns current time in milliseconds + std::function now_; + void adjustImmediateMutationIndicesForDelayedMutations( SurfaceId surfaceId, ShadowViewMutation &mutation, @@ -275,6 +294,9 @@ class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate, mutable std::mutex callbackWrappersPendingMutex_; mutable std::vector> callbackWrappersPending_{}; + + public: + void setClockNow(std::function now); }; static inline bool shouldFirstComeBeforeSecondRemovesOnly( diff --git a/ReactCommon/react/renderer/animations/tests/LayoutAnimationTest.cpp b/ReactCommon/react/renderer/animations/tests/LayoutAnimationTest.cpp new file mode 100644 index 00000000000..32bc24c44f7 --- /dev/null +++ b/ReactCommon/react/renderer/animations/tests/LayoutAnimationTest.cpp @@ -0,0 +1,384 @@ +/* + * 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 + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// Uncomment when random test blocks are uncommented below. +// #include +// #include + +#include "LayoutAnimationDriver.h" + +MockClock::time_point MockClock::time_ = {}; + +namespace facebook { +namespace react { + +static void testShadowNodeTreeLifeCycleLayoutAnimations( + uint_fast32_t seed, + int treeSize, + int repeats, + int stages, + int animation_duration, + int animation_frames, + int delay_ms_between_frames, + int delay_ms_between_stages, + int delay_ms_between_repeats) { + auto entropy = seed == 0 ? Entropy() : Entropy(seed); + + auto eventDispatcher = EventDispatcher::Shared{}; + auto contextContainer = std::make_shared(); + auto componentDescriptorParameters = + ComponentDescriptorParameters{eventDispatcher, contextContainer, nullptr}; + auto viewComponentDescriptor = + ViewComponentDescriptor(componentDescriptorParameters); + auto rootComponentDescriptor = + RootComponentDescriptor(componentDescriptorParameters); + auto noopEventEmitter = + std::make_shared(nullptr, -1, eventDispatcher); + + // Create a RuntimeExecutor + RuntimeExecutor runtimeExecutor = + [](std::function fn) {}; + + // Create component descriptor registry for animation driver + auto providerRegistry = + std::make_shared(); + auto componentDescriptorRegistry = + providerRegistry->createComponentDescriptorRegistry( + componentDescriptorParameters); + providerRegistry->add( + concreteComponentDescriptorProvider()); + providerRegistry->add( + concreteComponentDescriptorProvider()); + + // Create Animation Driver + auto animationDriver = + std::make_shared(runtimeExecutor, nullptr); + animationDriver->setComponentDescriptorRegistry(componentDescriptorRegistry); + + // Mock animation timers + animationDriver->setClockNow([]() { + return std::chrono::duration_cast( + MockClock::now().time_since_epoch()) + .count(); + }); + + auto allNodes = std::vector{}; + + for (int i = 0; i < repeats; i++) { + allNodes.clear(); + + int surfaceIdInt = 1; + auto surfaceId = SurfaceId(surfaceIdInt); + + auto family = rootComponentDescriptor.createFamily( + {Tag(surfaceIdInt), surfaceId, nullptr}, nullptr); + + // Creating an initial root shadow node. + auto emptyRootNode = std::const_pointer_cast( + std::static_pointer_cast( + rootComponentDescriptor.createShadowNode( + ShadowNodeFragment{RootShadowNode::defaultSharedProps()}, + family))); + + // Applying size constraints. + emptyRootNode = emptyRootNode->clone( + LayoutConstraints{ + Size{512, 0}, Size{512, std::numeric_limits::infinity()}}, + LayoutContext{}); + + // Generation of a random tree. + auto singleRootChildNode = + generateShadowNodeTree(entropy, viewComponentDescriptor, treeSize); + + // Injecting a tree into the root node. + auto currentRootNode = std::static_pointer_cast( + emptyRootNode->ShadowNode::clone(ShadowNodeFragment{ + ShadowNodeFragment::propsPlaceholder(), + std::make_shared( + SharedShadowNodeList{singleRootChildNode})})); + + // Building an initial view hierarchy. + auto viewTree = buildStubViewTreeWithoutUsingDifferentiator(*emptyRootNode); + viewTree.mutate( + calculateShadowViewMutations(*emptyRootNode, *currentRootNode, true)); + + for (int j = 0; j < stages; j++) { + auto nextRootNode = currentRootNode; + + // Mutating the tree. + alterShadowTree( + entropy, + nextRootNode, + { + &messWithChildren, + &messWithYogaStyles, + &messWithLayoutableOnlyFlag, + }); + + std::vector affectedLayoutableNodes{}; + affectedLayoutableNodes.reserve(1024); + + // Laying out the tree. + std::const_pointer_cast(nextRootNode) + ->layoutIfNeeded(&affectedLayoutableNodes); + + nextRootNode->sealRecursive(); + allNodes.push_back(nextRootNode); + + // Calculating mutations. + auto originalMutations = + calculateShadowViewMutations(*currentRootNode, *nextRootNode, true); + + // If tree randomization produced no changes in the form of mutations, + // don't bother trying to animate because this violates a bunch of our + // assumptions in this test + if (originalMutations.size() == 0) { + continue; + } + + // Configure animation + animationDriver->uiManagerDidConfigureNextLayoutAnimation( + {surfaceId, + 0, + false, + {(double)animation_duration, + {/* Create */ AnimationType::EaseInEaseOut, + AnimationProperty::Opacity, + (double)animation_duration, + 0, + 0, + 0}, + {/* Update */ AnimationType::EaseInEaseOut, + AnimationProperty::ScaleXY, + (double)animation_duration, + 0, + 0, + 0}, + {/* Delete */ AnimationType::EaseInEaseOut, + AnimationProperty::Opacity, + (double)animation_duration, + 0, + 0, + 0}}, + {}, + {}, + {}}); + + // Get mutations for each frame + for (int k = 0; k < animation_frames + 2; k++) { + auto mutationsInput = ShadowViewMutation::List{}; + if (k == 0) { + mutationsInput = originalMutations; + } + + if (k != (animation_frames + 1)) { + EXPECT_TRUE(animationDriver->shouldOverridePullTransaction()); + } else { + EXPECT_FALSE(animationDriver->shouldOverridePullTransaction()); + } + + auto telemetry = TransactionTelemetry{}; + telemetry.willLayout(); + telemetry.willCommit(); + telemetry.willDiff(); + + auto transaction = animationDriver->pullTransaction( + surfaceId, 0, telemetry, mutationsInput); + + EXPECT_TRUE(transaction.has_value() || k == animation_frames); + + // We have something to validate. + if (transaction.has_value()) { + auto mutations = transaction->getMutations(); + + // Mutating the view tree. + viewTree.mutate(mutations); + + // We don't do any validation on this until all animations are + // finished! + } + + MockClock::advance_by( + std::chrono::milliseconds(delay_ms_between_frames)); + } + + // After the animation is completed... + // Build a view tree to compare with. + // After all the synthetic mutations, at the end of the animation, + // the mutated and newly-constructed trees should be identical. + auto rebuiltViewTree = + buildStubViewTreeWithoutUsingDifferentiator(*nextRootNode); + + // Comparing the newly built tree with the updated one. + if (rebuiltViewTree != viewTree) { + // Something went wrong. + + LOG(ERROR) + << "Entropy seed: " << entropy.getSeed() + << ". To see why trees are different, define STUB_VIEW_TREE_VERBOSE and see logging in StubViewTree.cpp.\n"; + + EXPECT_TRUE(false); + } + + currentRootNode = nextRootNode; + + MockClock::advance_by(std::chrono::milliseconds(delay_ms_between_stages)); + } + + MockClock::advance_by(std::chrono::milliseconds(delay_ms_between_repeats)); + } + + SUCCEED(); +} + +} // namespace react +} // namespace facebook + +using namespace facebook::react; + +TEST( + LayoutAnimationTest, + stableSmallerTreeFewRepeatsFewStages_NonOverlapping_2029343357) { + testShadowNodeTreeLifeCycleLayoutAnimations( + /* seed */ 2029343357, /* working seed found 5-10-2021 */ + /* size */ 128, + /* repeats */ 128, + /* stages */ 10, + /* animation_duration */ 1000, + /* animation_frames*/ 10, + /* delay_ms_between_frames */ 100, + /* delay_ms_between_stages */ 100, + /* delay_ms_between_repeats */ 2000); +} + +TEST( + LayoutAnimationTest, + stableSmallerTreeFewRepeatsFewStages_NonOverlapping_3619914559) { + testShadowNodeTreeLifeCycleLayoutAnimations( + /* seed */ 3619914559, /* working seed found 5-10-2021 */ + /* size */ 128, + /* repeats */ 128, + /* stages */ 10, + /* animation_duration */ 1000, + /* animation_frames*/ 10, + /* delay_ms_between_frames */ 100, + /* delay_ms_between_stages */ 100, + /* delay_ms_between_repeats */ 2000); +} + +TEST( + LayoutAnimationTest, + stableSmallerTreeFewRepeatsFewStages_NonOverlapping_597132284) { + testShadowNodeTreeLifeCycleLayoutAnimations( + /* seed */ 597132284, /* failing seed found 5-10-2021 */ + /* size */ 128, + /* repeats */ 128, + /* stages */ 10, + /* animation_duration */ 1000, + /* animation_frames*/ 10, + /* delay_ms_between_frames */ 100, + /* delay_ms_between_stages */ 100, + /* delay_ms_between_repeats */ 2000); +} + +TEST( + LayoutAnimationTest, + stableSmallerTreeFewRepeatsFewStages_NonOverlapping_774986518) { + testShadowNodeTreeLifeCycleLayoutAnimations( + /* seed */ 774986518, /* failing seed found 5-10-2021 */ + /* size */ 128, + /* repeats */ 128, + /* stages */ 10, + /* animation_duration */ 1000, + /* animation_frames*/ 10, + /* delay_ms_between_frames */ 100, + /* delay_ms_between_stages */ 100, + /* delay_ms_between_repeats */ 2000); +} + +TEST( + LayoutAnimationTest, + stableSmallerTreeFewRepeatsFewStages_NonOverlapping_1450614414) { + testShadowNodeTreeLifeCycleLayoutAnimations( + /* seed */ 1450614414, /* failing seed found 5-10-2021 */ + /* size */ 128, + /* repeats */ 128, + /* stages */ 10, + /* animation_duration */ 1000, + /* animation_frames*/ 10, + /* delay_ms_between_frames */ 100, + /* delay_ms_between_stages */ 100, + /* delay_ms_between_repeats */ 2000); +} + +TEST(LayoutAnimationTest, stableBiggerTreeFewRepeatsFewStages_NonOverlapping) { + testShadowNodeTreeLifeCycleLayoutAnimations( + /* seed */ 2029343357, + /* size */ 512, + /* repeats */ 32, + /* stages */ 10, + /* animation_duration */ 1000, + /* animation_frames*/ 10, + /* delay_ms_between_frames */ 100, + /* delay_ms_between_stages */ 100, + /* delay_ms_between_repeats */ 2000); +} + +TEST(LayoutAnimationTest, stableBiggerTreeFewRepeatsManyStages_NonOverlapping) { + testShadowNodeTreeLifeCycleLayoutAnimations( + /* seed */ 2029343357, + /* size */ 512, + /* repeats */ 32, + /* stages */ 128, + /* animation_duration */ 1000, + /* animation_frames*/ 10, + /* delay_ms_between_frames */ 100, + /* delay_ms_between_stages */ 100, + /* delay_ms_between_repeats */ 2000); +} + +// You may uncomment this - locally only! - to generate failing seeds. +// TEST(LayoutAnimationTest, stableSmallerTreeFewRepeatsFewStages_Random) { +// std::random_device device; +// for (int i = 0; i < 10; i++) { +// uint_fast32_t seed = device(); +// LOG(ERROR) << "Seed: " << seed; +// testShadowNodeTreeLifeCycleLayoutAnimations( +// /* seed */ seed, +// /* size */ 128, +// /* repeats */ 128, +// /* stages */ 10, +// /* animation_duration */ 1000, +// /* animation_frames*/ 10, +// /* delay_ms_between_frames */ 100, +// /* delay_ms_between_stages */ 100, +// /* delay_ms_between_repeats */ 2000); +// } +// // Fail if you want output to get seeds +// LOG(ERROR) << "ALL RUNS SUCCESSFUL"; +// // react_native_assert(false); +// } diff --git a/ReactCommon/react/renderer/mounting/BUCK b/ReactCommon/react/renderer/mounting/BUCK index 368aa113fdb..2d0d42e5855 100644 --- a/ReactCommon/react/renderer/mounting/BUCK +++ b/ReactCommon/react/renderer/mounting/BUCK @@ -88,5 +88,6 @@ fb_xplat_cxx_test( react_native_xplat_target("react/renderer/components/root:root"), react_native_xplat_target("react/renderer/components/view:view"), react_native_xplat_target("react/renderer/components/scrollview:scrollview"), + react_native_xplat_target("react/test_utils:test_utils"), ], ) diff --git a/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp b/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp index 0cb2fb47409..6c407f15f42 100644 --- a/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp +++ b/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp @@ -12,7 +12,7 @@ #include #include -#include "shadowTreeGeneration.h" +#include #include #include diff --git a/ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp b/ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp index e949f1357ef..813dadb1e17 100644 --- a/ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp +++ b/ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp @@ -16,13 +16,13 @@ #include #include +#include +#include + // Uncomment when random test blocks are uncommented below. // #include // #include -#include "Entropy.h" -#include "shadowTreeGeneration.h" - namespace facebook { namespace react { diff --git a/ReactCommon/react/renderer/telemetry/BUCK b/ReactCommon/react/renderer/telemetry/BUCK index f19ae71f0c3..3ec301b4839 100644 --- a/ReactCommon/react/renderer/telemetry/BUCK +++ b/ReactCommon/react/renderer/telemetry/BUCK @@ -79,5 +79,6 @@ fb_xplat_cxx_test( ":telemetry", "//xplat/folly:molly", "//xplat/third-party/gmock:gtest", + react_native_xplat_target("react/test_utils:test_utils"), ], ) diff --git a/ReactCommon/react/renderer/telemetry/tests/TransactionTelemetryTest.cpp b/ReactCommon/react/renderer/telemetry/tests/TransactionTelemetryTest.cpp index f5e2ef488e1..95c6dfcf1b0 100644 --- a/ReactCommon/react/renderer/telemetry/tests/TransactionTelemetryTest.cpp +++ b/ReactCommon/react/renderer/telemetry/tests/TransactionTelemetryTest.cpp @@ -11,29 +11,11 @@ #include #include +#include #include using namespace facebook::react; -class MockClock { - public: - typedef std::chrono:: - time_point - time_point; - - static time_point now() noexcept { - return time_; - } - - template - static void advance_by(const TDuration duration) { - time_ += duration; - } - - private: - static time_point time_; -}; - MockClock::time_point MockClock::time_ = {}; /** diff --git a/ReactCommon/react/test_utils/BUCK b/ReactCommon/react/test_utils/BUCK new file mode 100644 index 00000000000..52bae03fe6c --- /dev/null +++ b/ReactCommon/react/test_utils/BUCK @@ -0,0 +1,54 @@ +load( + "//tools/build_defs/oss:rn_defs.bzl", + "ANDROID", + "APPLE", + "CXX", + "get_apple_compiler_flags", + "get_apple_inspector_flags", + "get_preprocessor_flags_for_build_mode", + "react_native_xplat_target", + "rn_xplat_cxx_library", + "subdir_glob", +) + +APPLE_COMPILER_FLAGS = get_apple_compiler_flags() + +rn_xplat_cxx_library( + name = "test_utils", + srcs = [], + headers = glob( + ["**/*.h"], + exclude = glob(["tests/**/*.h"]), + ), + header_namespace = "", + exported_headers = subdir_glob( + [ + ("", "*.h"), + ], + prefix = "react/test_utils", + ), + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++17", + "-Wall", + ], + fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, + fbobjc_frameworks = ["Foundation"], + fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), + force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], + macosx_tests_override = [], + platforms = (ANDROID, APPLE, CXX), + preprocessor_flags = [ + "-DLOG_TAG=\"ReactNative\"", + "-DWITH_FBSYSTRACE=1", + ], + tests = [], + visibility = ["PUBLIC"], + deps = [ + "//xplat/jsi:jsi", + react_native_xplat_target("better:better"), + react_native_xplat_target("react/debug:debug"), + ], +) diff --git a/ReactCommon/react/renderer/mounting/tests/Entropy.h b/ReactCommon/react/test_utils/Entropy.h similarity index 100% rename from ReactCommon/react/renderer/mounting/tests/Entropy.h rename to ReactCommon/react/test_utils/Entropy.h diff --git a/ReactCommon/react/test_utils/MockClock.h b/ReactCommon/react/test_utils/MockClock.h new file mode 100644 index 00000000000..a5155207b20 --- /dev/null +++ b/ReactCommon/react/test_utils/MockClock.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#pragma once + +#include + +class MockClock { + public: + typedef std::chrono:: + time_point + time_point; + + static time_point now() noexcept { + return time_; + } + + template + static void advance_by(const TDuration duration) { + time_ += duration; + } + + private: + static time_point time_; +}; diff --git a/ReactCommon/react/renderer/mounting/tests/shadowTreeGeneration.h b/ReactCommon/react/test_utils/shadowTreeGeneration.h similarity index 100% rename from ReactCommon/react/renderer/mounting/tests/shadowTreeGeneration.h rename to ReactCommon/react/test_utils/shadowTreeGeneration.h