Skip to content

Commit dc80b2d

Browse files
JoshuaGrossfacebook-github-bot
authored andcommitted
Add mounting layer test that stress-tests differ on (un)flattening
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
1 parent d1b1e8b commit dc80b2d

4 files changed

Lines changed: 231 additions & 7 deletions

File tree

ReactCommon/react/renderer/mounting/StubViewTree.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ void StubViewTree::mutate(ShadowViewMutationList const &mutations) {
7272
auto stubView = registry[tag];
7373
if ((ShadowView)(*stubView) != mutation.oldChildShadowView) {
7474
LOG(ERROR)
75-
<< "StubView: ASSERT FAILURE: DELETE mutation assertion failure: oldChildShadowView doesn't match stubView: ["
75+
<< "StubView: ASSERT FAILURE: DELETE mutation assertion failure: oldChildShadowView does not match stubView: ["
7676
<< mutation.oldChildShadowView.tag << "] stub hash: ##"
7777
<< std::hash<ShadowView>{}((ShadowView)*stubView)
7878
<< " old mutation hash: ##"
@@ -154,7 +154,7 @@ void StubViewTree::mutate(ShadowViewMutationList const &mutations) {
154154
auto childStubView = registry[childTag];
155155
if ((ShadowView)(*childStubView) != mutation.oldChildShadowView) {
156156
LOG(ERROR)
157-
<< "StubView: ASSERT FAILURE: REMOVE mutation assertion failure: oldChildShadowView doesn't match oldStubView: ["
157+
<< "StubView: ASSERT FAILURE: REMOVE mutation assertion failure: oldChildShadowView does not match oldStubView: ["
158158
<< mutation.oldChildShadowView.tag << "] stub hash: ##"
159159
<< std::hash<ShadowView>{}((ShadowView)*childStubView)
160160
<< " old mutation hash: ##"
@@ -214,7 +214,7 @@ void StubViewTree::mutate(ShadowViewMutationList const &mutations) {
214214
react_native_assert(oldStubView->tag != 0);
215215
if ((ShadowView)(*oldStubView) != mutation.oldChildShadowView) {
216216
LOG(ERROR)
217-
<< "StubView: ASSERT FAILURE: UPDATE mutation assertion failure: oldChildShadowView doesn't match oldStubView: ["
217+
<< "StubView: ASSERT FAILURE: UPDATE mutation assertion failure: oldChildShadowView does not match oldStubView: ["
218218
<< mutation.oldChildShadowView.tag << "] old stub hash: ##"
219219
<< std::hash<ShadowView>{}((ShadowView)*oldStubView)
220220
<< " old mutation hash: ##"

ReactCommon/react/renderer/mounting/tests/Entropy.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ class Entropy final {
7575
result = generator() % 10000 < 10000 * ratio;
7676
}
7777

78+
void generateRandomValue(Generator &generator, int &result) const {
79+
result = generator();
80+
}
81+
7882
void generateRandomValue(Generator &generator, int &result, int min, int max)
7983
const {
8084
std::uniform_int_distribution<int> distribution(min, max);

ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp

Lines changed: 180 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ static void testShadowNodeTreeLifeCycle(
8787
{
8888
&messWithChildren,
8989
&messWithYogaStyles,
90-
&messWithLayotableOnlyFlag,
90+
&messWithLayoutableOnlyFlag,
9191
});
9292

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

143143
// There are some issues getting `getDebugDescription` to compile
144144
// under test on Android for now.
145-
#ifndef ANDROID
145+
#ifdef RN_DEBUG_STRING_CONVERTIBLE
146+
LOG(ERROR) << "Shadow Tree before: \n"
147+
<< currentRootNode->getDebugDescription();
148+
LOG(ERROR) << "Shadow Tree after: \n"
149+
<< nextRootNode->getDebugDescription();
150+
151+
LOG(ERROR) << "View Tree before: \n"
152+
<< getDebugDescription(viewTree.getRootStubView(), {});
153+
LOG(ERROR) << "View Tree after: \n"
154+
<< getDebugDescription(
155+
rebuiltViewTree.getRootStubView(), {});
156+
157+
LOG(ERROR) << "Mutations:"
158+
<< "\n"
159+
<< getDebugDescription(mutations, {});
160+
#endif
161+
162+
FAIL();
163+
}
164+
165+
currentRootNode = nextRootNode;
166+
}
167+
}
168+
169+
SUCCEED();
170+
}
171+
172+
static void testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
173+
uint_fast32_t seed,
174+
int treeSize,
175+
int repeats,
176+
int stages) {
177+
auto entropy = seed == 0 ? Entropy() : Entropy(seed);
178+
179+
auto eventDispatcher = EventDispatcher::Shared{};
180+
auto contextContainer = std::make_shared<ContextContainer>();
181+
auto componentDescriptorParameters =
182+
ComponentDescriptorParameters{eventDispatcher, contextContainer, nullptr};
183+
auto viewComponentDescriptor =
184+
ViewComponentDescriptor(componentDescriptorParameters);
185+
auto rootComponentDescriptor =
186+
RootComponentDescriptor(componentDescriptorParameters);
187+
auto noopEventEmitter =
188+
std::make_shared<ViewEventEmitter const>(nullptr, -1, eventDispatcher);
189+
190+
auto allNodes = std::vector<ShadowNode::Shared>{};
191+
192+
for (int i = 0; i < repeats; i++) {
193+
allNodes.clear();
194+
195+
auto family = rootComponentDescriptor.createFamily(
196+
{Tag(1), SurfaceId(1), nullptr}, nullptr);
197+
198+
// Creating an initial root shadow node.
199+
auto emptyRootNode = std::const_pointer_cast<RootShadowNode>(
200+
std::static_pointer_cast<RootShadowNode const>(
201+
rootComponentDescriptor.createShadowNode(
202+
ShadowNodeFragment{RootShadowNode::defaultSharedProps()},
203+
family)));
204+
205+
// Applying size constraints.
206+
emptyRootNode = emptyRootNode->clone(
207+
LayoutConstraints{
208+
Size{512, 0}, Size{512, std::numeric_limits<Float>::infinity()}},
209+
LayoutContext{});
210+
211+
// Generation of a random tree.
212+
auto singleRootChildNode =
213+
generateShadowNodeTree(entropy, viewComponentDescriptor, treeSize);
214+
215+
// Injecting a tree into the root node.
216+
auto currentRootNode = std::static_pointer_cast<RootShadowNode const>(
217+
emptyRootNode->ShadowNode::clone(ShadowNodeFragment{
218+
ShadowNodeFragment::propsPlaceholder(),
219+
std::make_shared<SharedShadowNodeList>(
220+
SharedShadowNodeList{singleRootChildNode})}));
221+
222+
// Building an initial view hierarchy.
223+
auto viewTree = buildStubViewTreeWithoutUsingDifferentiator(*emptyRootNode);
224+
viewTree.mutate(
225+
calculateShadowViewMutations(*emptyRootNode, *currentRootNode, true));
226+
227+
for (int j = 0; j < stages; j++) {
228+
auto nextRootNode = currentRootNode;
229+
230+
// Mutating the tree.
231+
alterShadowTree(
232+
entropy,
233+
nextRootNode,
234+
{
235+
&messWithYogaStyles,
236+
&messWithLayoutableOnlyFlag,
237+
});
238+
alterShadowTree(entropy, nextRootNode, &messWithNodeFlattenednessFlags);
239+
alterShadowTree(entropy, nextRootNode, &messWithChildren);
240+
241+
std::vector<LayoutableShadowNode const *> affectedLayoutableNodes{};
242+
affectedLayoutableNodes.reserve(1024);
243+
244+
// Laying out the tree.
245+
std::const_pointer_cast<RootShadowNode>(nextRootNode)
246+
->layoutIfNeeded(&affectedLayoutableNodes);
247+
248+
nextRootNode->sealRecursive();
249+
allNodes.push_back(nextRootNode);
250+
251+
// Calculating mutations.
252+
auto mutations =
253+
calculateShadowViewMutations(*currentRootNode, *nextRootNode, true);
254+
255+
// Make sure that in a single frame, a DELETE for a
256+
// view is not followed by a CREATE for the same view.
257+
{
258+
std::vector<int> deletedTags{};
259+
for (auto const &mutation : mutations) {
260+
if (mutation.type == ShadowViewMutation::Type::Delete) {
261+
deletedTags.push_back(mutation.oldChildShadowView.tag);
262+
}
263+
}
264+
for (auto const &mutation : mutations) {
265+
if (mutation.type == ShadowViewMutation::Type::Create) {
266+
if (std::find(
267+
deletedTags.begin(),
268+
deletedTags.end(),
269+
mutation.newChildShadowView.tag) != deletedTags.end()) {
270+
LOG(ERROR) << "Deleted tag was recreated in mutations list: ["
271+
<< mutation.newChildShadowView.tag << "]";
272+
FAIL();
273+
}
274+
}
275+
}
276+
}
277+
278+
// Mutating the view tree.
279+
viewTree.mutate(mutations);
280+
281+
// Building a view tree to compare with.
282+
auto rebuiltViewTree =
283+
buildStubViewTreeWithoutUsingDifferentiator(*nextRootNode);
284+
285+
// Comparing the newly built tree with the updated one.
286+
if (rebuiltViewTree != viewTree) {
287+
// Something went wrong.
288+
289+
LOG(ERROR) << "Entropy seed: " << entropy.getSeed() << "\n";
290+
291+
// There are some issues getting `getDebugDescription` to compile
292+
// under test on Android for now.
293+
#ifdef RN_DEBUG_STRING_CONVERTIBLE
146294
LOG(ERROR) << "Shadow Tree before: \n"
147295
<< currentRootNode->getDebugDescription();
148296
LOG(ERROR) << "Shadow Tree after: \n"
@@ -203,3 +351,33 @@ TEST(
203351
/* repeats */ 512,
204352
/* stages */ 32);
205353
}
354+
355+
TEST(
356+
ShadowTreeLifecyleTest,
357+
unstableSmallerTreeFewerIterationsExtensiveFlatteningUnflattening) {
358+
testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
359+
/* seed */ 1337,
360+
/* size */ 32,
361+
/* repeats */ 32,
362+
/* stages */ 32);
363+
}
364+
365+
TEST(
366+
ShadowTreeLifecyleTest,
367+
unstableBiggerTreeFewerIterationsExtensiveFlatteningUnflattening) {
368+
testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
369+
/* seed */ 1337,
370+
/* size */ 256,
371+
/* repeats */ 32,
372+
/* stages */ 32);
373+
}
374+
375+
TEST(
376+
ShadowTreeLifecyleTest,
377+
unstableSmallerTreeMoreIterationsExtensiveFlatteningUnflattening) {
378+
testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening(
379+
/* seed */ 1337,
380+
/* size */ 32,
381+
/* repeats */ 512,
382+
/* stages */ 32);
383+
}

ReactCommon/react/renderer/mounting/tests/shadowTreeGeneration.h

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ static inline ShadowNode::Unshared messWithChildren(
116116
std::make_shared<ShadowNode::ListOfShared const>(children)});
117117
}
118118

119-
static inline ShadowNode::Unshared messWithLayotableOnlyFlag(
119+
static inline ShadowNode::Unshared messWithLayoutableOnlyFlag(
120120
Entropy const &entropy,
121121
ShadowNode const &shadowNode) {
122122
auto oldProps = shadowNode.getProps();
@@ -150,7 +150,7 @@ static inline ShadowNode::Unshared messWithLayotableOnlyFlag(
150150
}
151151

152152
if (entropy.random<bool>(0.1)) {
153-
viewProps.zIndex = entropy.random<bool>() ? 1 : 0;
153+
viewProps.zIndex = entropy.random<int>();
154154
}
155155

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

166+
if (entropy.random<bool>(0.1)) {
167+
viewProps.elevation = entropy.random<bool>() ? 1 : 0;
168+
}
169+
170+
return shadowNode.clone({newProps});
171+
}
172+
173+
// Similar to `messWithLayoutableOnlyFlag` but has a 50/50 chance of flattening
174+
// (or unflattening) a node's children.
175+
static inline ShadowNode::Unshared messWithNodeFlattenednessFlags(
176+
Entropy const &entropy,
177+
ShadowNode const &shadowNode) {
178+
auto oldProps = shadowNode.getProps();
179+
auto newProps = shadowNode.getComponentDescriptor().cloneProps(
180+
oldProps, RawProps(folly::dynamic::object()));
181+
182+
auto &viewProps =
183+
const_cast<ViewProps &>(static_cast<ViewProps const &>(*newProps));
184+
185+
if (entropy.random<bool>(0.5)) {
186+
viewProps.nativeId = "";
187+
viewProps.collapsable = true;
188+
viewProps.backgroundColor = SharedColor();
189+
viewProps.foregroundColor = SharedColor();
190+
viewProps.shadowColor = SharedColor();
191+
viewProps.accessible = false;
192+
viewProps.zIndex = {};
193+
viewProps.pointerEvents = PointerEventsMode::Auto;
194+
viewProps.transform = Transform::Identity();
195+
viewProps.elevation = 0;
196+
} else {
197+
viewProps.nativeId = "42";
198+
viewProps.backgroundColor = whiteColor();
199+
viewProps.foregroundColor = blackColor();
200+
viewProps.shadowColor = blackColor();
201+
viewProps.accessible = true;
202+
viewProps.zIndex = {entropy.random<int>()};
203+
viewProps.pointerEvents = PointerEventsMode::None;
204+
viewProps.transform = Transform::Perspective(entropy.random<int>());
205+
viewProps.elevation = entropy.random<int>();
206+
}
207+
166208
return shadowNode.clone({newProps});
167209
}
168210

0 commit comments

Comments
 (0)