Skip to content

Commit

Permalink
Add mounting layer test that stress-tests differ on (un)flattening
Browse files Browse the repository at this point in the history
Summary:
Add mounting layer test that stress-tests differ on (un)flattening.

This fails before D27759380 and D27730952, and passes after.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D27767219

fbshipit-source-id: a7e186e510f95792da6f98f80fcae5ff8ac74775
  • Loading branch information
JoshuaGross authored and facebook-github-bot committed Apr 15, 2021
1 parent d1b1e8b commit dc80b2d
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 7 deletions.
6 changes: 3 additions & 3 deletions ReactCommon/react/renderer/mounting/StubViewTree.cpp
Expand Up @@ -72,7 +72,7 @@ void StubViewTree::mutate(ShadowViewMutationList const &mutations) {
auto stubView = registry[tag];
if ((ShadowView)(*stubView) != mutation.oldChildShadowView) {
LOG(ERROR)
<< "StubView: ASSERT FAILURE: DELETE mutation assertion failure: oldChildShadowView doesn't match stubView: ["
<< "StubView: ASSERT FAILURE: DELETE mutation assertion failure: oldChildShadowView does not match stubView: ["
<< mutation.oldChildShadowView.tag << "] stub hash: ##"
<< std::hash<ShadowView>{}((ShadowView)*stubView)
<< " old mutation hash: ##"
Expand Down Expand Up @@ -154,7 +154,7 @@ void StubViewTree::mutate(ShadowViewMutationList const &mutations) {
auto childStubView = registry[childTag];
if ((ShadowView)(*childStubView) != mutation.oldChildShadowView) {
LOG(ERROR)
<< "StubView: ASSERT FAILURE: REMOVE mutation assertion failure: oldChildShadowView doesn't match oldStubView: ["
<< "StubView: ASSERT FAILURE: REMOVE mutation assertion failure: oldChildShadowView does not match oldStubView: ["
<< mutation.oldChildShadowView.tag << "] stub hash: ##"
<< std::hash<ShadowView>{}((ShadowView)*childStubView)
<< " old mutation hash: ##"
Expand Down Expand Up @@ -214,7 +214,7 @@ void StubViewTree::mutate(ShadowViewMutationList const &mutations) {
react_native_assert(oldStubView->tag != 0);
if ((ShadowView)(*oldStubView) != mutation.oldChildShadowView) {
LOG(ERROR)
<< "StubView: ASSERT FAILURE: UPDATE mutation assertion failure: oldChildShadowView doesn't match oldStubView: ["
<< "StubView: ASSERT FAILURE: UPDATE mutation assertion failure: oldChildShadowView does not match oldStubView: ["
<< mutation.oldChildShadowView.tag << "] old stub hash: ##"
<< std::hash<ShadowView>{}((ShadowView)*oldStubView)
<< " old mutation hash: ##"
Expand Down
4 changes: 4 additions & 0 deletions ReactCommon/react/renderer/mounting/tests/Entropy.h
Expand Up @@ -75,6 +75,10 @@ class Entropy final {
result = generator() % 10000 < 10000 * ratio;
}

void generateRandomValue(Generator &generator, int &result) const {
result = generator();
}

void generateRandomValue(Generator &generator, int &result, int min, int max)
const {
std::uniform_int_distribution<int> distribution(min, max);
Expand Down
182 changes: 180 additions & 2 deletions ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp
Expand Up @@ -87,7 +87,7 @@ static void testShadowNodeTreeLifeCycle(
{
&messWithChildren,
&messWithYogaStyles,
&messWithLayotableOnlyFlag,
&messWithLayoutableOnlyFlag,
});

std::vector<LayoutableShadowNode const *> affectedLayoutableNodes{};
Expand Down Expand Up @@ -142,7 +142,155 @@ static void testShadowNodeTreeLifeCycle(

// There are some issues getting `getDebugDescription` to compile
// under test on Android for now.
#ifndef ANDROID
#ifdef RN_DEBUG_STRING_CONVERTIBLE
LOG(ERROR) << "Shadow Tree before: \n"
<< currentRootNode->getDebugDescription();
LOG(ERROR) << "Shadow Tree after: \n"
<< nextRootNode->getDebugDescription();

LOG(ERROR) << "View Tree before: \n"
<< getDebugDescription(viewTree.getRootStubView(), {});
LOG(ERROR) << "View Tree after: \n"
<< getDebugDescription(
rebuiltViewTree.getRootStubView(), {});

LOG(ERROR) << "Mutations:"
<< "\n"
<< getDebugDescription(mutations, {});
#endif

FAIL();
}

currentRootNode = nextRootNode;
}
}

SUCCEED();
}

static void testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
uint_fast32_t seed,
int treeSize,
int repeats,
int stages) {
auto entropy = seed == 0 ? Entropy() : Entropy(seed);

auto eventDispatcher = EventDispatcher::Shared{};
auto contextContainer = std::make_shared<ContextContainer>();
auto componentDescriptorParameters =
ComponentDescriptorParameters{eventDispatcher, contextContainer, nullptr};
auto viewComponentDescriptor =
ViewComponentDescriptor(componentDescriptorParameters);
auto rootComponentDescriptor =
RootComponentDescriptor(componentDescriptorParameters);
auto noopEventEmitter =
std::make_shared<ViewEventEmitter const>(nullptr, -1, eventDispatcher);

auto allNodes = std::vector<ShadowNode::Shared>{};

for (int i = 0; i < repeats; i++) {
allNodes.clear();

auto family = rootComponentDescriptor.createFamily(
{Tag(1), SurfaceId(1), nullptr}, nullptr);

// Creating an initial root shadow node.
auto emptyRootNode = std::const_pointer_cast<RootShadowNode>(
std::static_pointer_cast<RootShadowNode const>(
rootComponentDescriptor.createShadowNode(
ShadowNodeFragment{RootShadowNode::defaultSharedProps()},
family)));

// Applying size constraints.
emptyRootNode = emptyRootNode->clone(
LayoutConstraints{
Size{512, 0}, Size{512, std::numeric_limits<Float>::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<RootShadowNode const>(
emptyRootNode->ShadowNode::clone(ShadowNodeFragment{
ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
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,
{
&messWithYogaStyles,
&messWithLayoutableOnlyFlag,
});
alterShadowTree(entropy, nextRootNode, &messWithNodeFlattenednessFlags);
alterShadowTree(entropy, nextRootNode, &messWithChildren);

std::vector<LayoutableShadowNode const *> affectedLayoutableNodes{};
affectedLayoutableNodes.reserve(1024);

// Laying out the tree.
std::const_pointer_cast<RootShadowNode>(nextRootNode)
->layoutIfNeeded(&affectedLayoutableNodes);

nextRootNode->sealRecursive();
allNodes.push_back(nextRootNode);

// Calculating mutations.
auto mutations =
calculateShadowViewMutations(*currentRootNode, *nextRootNode, true);

// Make sure that in a single frame, a DELETE for a
// view is not followed by a CREATE for the same view.
{
std::vector<int> deletedTags{};
for (auto const &mutation : mutations) {
if (mutation.type == ShadowViewMutation::Type::Delete) {
deletedTags.push_back(mutation.oldChildShadowView.tag);
}
}
for (auto const &mutation : mutations) {
if (mutation.type == ShadowViewMutation::Type::Create) {
if (std::find(
deletedTags.begin(),
deletedTags.end(),
mutation.newChildShadowView.tag) != deletedTags.end()) {
LOG(ERROR) << "Deleted tag was recreated in mutations list: ["
<< mutation.newChildShadowView.tag << "]";
FAIL();
}
}
}
}

// Mutating the view tree.
viewTree.mutate(mutations);

// Building a view tree to compare with.
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() << "\n";

// There are some issues getting `getDebugDescription` to compile
// under test on Android for now.
#ifdef RN_DEBUG_STRING_CONVERTIBLE
LOG(ERROR) << "Shadow Tree before: \n"
<< currentRootNode->getDebugDescription();
LOG(ERROR) << "Shadow Tree after: \n"
Expand Down Expand Up @@ -203,3 +351,33 @@ TEST(
/* repeats */ 512,
/* stages */ 32);
}

TEST(
ShadowTreeLifecyleTest,
unstableSmallerTreeFewerIterationsExtensiveFlatteningUnflattening) {
testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
/* seed */ 1337,
/* size */ 32,
/* repeats */ 32,
/* stages */ 32);
}

TEST(
ShadowTreeLifecyleTest,
unstableBiggerTreeFewerIterationsExtensiveFlatteningUnflattening) {
testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
/* seed */ 1337,
/* size */ 256,
/* repeats */ 32,
/* stages */ 32);
}

TEST(
ShadowTreeLifecyleTest,
unstableSmallerTreeMoreIterationsExtensiveFlatteningUnflattening) {
testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
/* seed */ 1337,
/* size */ 32,
/* repeats */ 512,
/* stages */ 32);
}
46 changes: 44 additions & 2 deletions ReactCommon/react/renderer/mounting/tests/shadowTreeGeneration.h
Expand Up @@ -116,7 +116,7 @@ static inline ShadowNode::Unshared messWithChildren(
std::make_shared<ShadowNode::ListOfShared const>(children)});
}

static inline ShadowNode::Unshared messWithLayotableOnlyFlag(
static inline ShadowNode::Unshared messWithLayoutableOnlyFlag(
Entropy const &entropy,
ShadowNode const &shadowNode) {
auto oldProps = shadowNode.getProps();
Expand Down Expand Up @@ -150,7 +150,7 @@ static inline ShadowNode::Unshared messWithLayotableOnlyFlag(
}

if (entropy.random<bool>(0.1)) {
viewProps.zIndex = entropy.random<bool>() ? 1 : 0;
viewProps.zIndex = entropy.random<int>();
}

if (entropy.random<bool>(0.1)) {
Expand All @@ -163,6 +163,48 @@ static inline ShadowNode::Unshared messWithLayotableOnlyFlag(
: Transform::Perspective(42);
}

if (entropy.random<bool>(0.1)) {
viewProps.elevation = entropy.random<bool>() ? 1 : 0;
}

return shadowNode.clone({newProps});
}

// Similar to `messWithLayoutableOnlyFlag` but has a 50/50 chance of flattening
// (or unflattening) a node's children.
static inline ShadowNode::Unshared messWithNodeFlattenednessFlags(
Entropy const &entropy,
ShadowNode const &shadowNode) {
auto oldProps = shadowNode.getProps();
auto newProps = shadowNode.getComponentDescriptor().cloneProps(
oldProps, RawProps(folly::dynamic::object()));

auto &viewProps =
const_cast<ViewProps &>(static_cast<ViewProps const &>(*newProps));

if (entropy.random<bool>(0.5)) {
viewProps.nativeId = "";
viewProps.collapsable = true;
viewProps.backgroundColor = SharedColor();
viewProps.foregroundColor = SharedColor();
viewProps.shadowColor = SharedColor();
viewProps.accessible = false;
viewProps.zIndex = {};
viewProps.pointerEvents = PointerEventsMode::Auto;
viewProps.transform = Transform::Identity();
viewProps.elevation = 0;
} else {
viewProps.nativeId = "42";
viewProps.backgroundColor = whiteColor();
viewProps.foregroundColor = blackColor();
viewProps.shadowColor = blackColor();
viewProps.accessible = true;
viewProps.zIndex = {entropy.random<int>()};
viewProps.pointerEvents = PointerEventsMode::None;
viewProps.transform = Transform::Perspective(entropy.random<int>());
viewProps.elevation = entropy.random<int>();
}

return shadowNode.clone({newProps});
}

Expand Down

0 comments on commit dc80b2d

Please sign in to comment.