Fabric: Introspection (self testing in debug mode) in ShadowTree

Summary:
We suspect that we have some error in diffing algorithm that cause some crashes in mounting layer, so we decided to write a comprehensive unit tests for that.

Writing them we realized that it would be cool to also enable that for normal app run in the debug more, so we can catch the problem in real environment when/if it happens.

Reviewed By: mdvacca

Differential Revision: D14587123

fbshipit-source-id: 6dcdf451b39489dec751cd6787de33f3b8ffb3fd
This commit is contained in:
Valentin Shergin
2019-04-01 10:47:05 -07:00
committed by Facebook Github Bot
parent 3e4a8e35fe
commit b10da79fb4
9 changed files with 284 additions and 0 deletions

View File

@@ -27,6 +27,7 @@ rn_xplat_cxx_library(
exported_headers = subdir_glob(
[
("", "*.h"),
("stubs", "*.h"),
],
prefix = "react/mounting",
),

View File

@@ -5,6 +5,8 @@
#include "ShadowTree.h"
#include <glog/logging.h>
#include <react/components/root/RootComponentDescriptor.h>
#include <react/core/LayoutContext.h>
#include <react/core/LayoutPrimitives.h>
@@ -194,6 +196,20 @@ bool ShadowTree::tryCommit(
if (revision) {
*revision = revision_;
}
#ifdef RN_SHADOW_TREE_INTROSPECTION
stubViewTree_.mutate(mutations);
auto stubViewTree = stubViewTreeFromShadowNode(*rootShadowNode_);
if (stubViewTree_ != stubViewTree) {
LOG(ERROR) << "Old tree:"
<< "\n"
<< oldRootShadowNode->getDebugDescription() << "\n";
LOG(ERROR) << "New tree:"
<< "\n"
<< newRootShadowNode->getDebugDescription() << "\n";
assert(false);
}
#endif
}
emitLayoutEvents(mutations);

View File

@@ -5,6 +5,10 @@
#pragma once
#ifdef DEBUG
#define RN_SHADOW_TREE_INTROSPECTION
#endif
#include <better/mutex.h>
#include <memory>
@@ -16,6 +20,10 @@
#include <react/mounting/ShadowTreeDelegate.h>
#include <react/mounting/ShadowViewMutation.h>
#ifdef RN_SHADOW_TREE_INTROSPECTION
#include <react/mounting/stubs.h>
#endif
namespace facebook {
namespace react {
@@ -87,6 +95,10 @@ class ShadowTree final {
mutable SharedRootShadowNode rootShadowNode_; // Protected by `commitMutex_`.
mutable int revision_{1}; // Protected by `commitMutex_`.
ShadowTreeDelegate const *delegate_;
#ifdef RN_SHADOW_TREE_INTROSPECTION
mutable StubViewTree stubViewTree_; // Protected by `commitMutex_`.
#endif
};
} // namespace react

View File

@@ -0,0 +1,31 @@
// 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 "StubView.h"
namespace facebook {
namespace react {
void StubView::update(ShadowView const &shadowView) {
componentName = shadowView.componentName;
componentHandle = shadowView.componentHandle;
tag = shadowView.tag;
props = shadowView.props;
eventEmitter = shadowView.eventEmitter;
layoutMetrics = shadowView.layoutMetrics;
state = shadowView.state;
}
bool operator==(StubView const &lhs, StubView const &rhs) {
return std::tie(lhs.props, lhs.layoutMetrics) ==
std::tie(rhs.props, rhs.layoutMetrics);
}
bool operator!=(StubView const &lhs, StubView const &rhs) {
return !(lhs == rhs);
}
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,41 @@
// 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 <memory>
#include <vector>
#include <react/core/LayoutMetrics.h>
#include <react/core/State.h>
#include <react/mounting/ShadowView.h>
namespace facebook {
namespace react {
class StubView final {
public:
using Shared = std::shared_ptr<StubView>;
StubView() = default;
StubView(StubView const &stubView) = default;
void update(ShadowView const &shadowView);
ComponentName componentName;
ComponentHandle componentHandle;
Tag tag;
SharedProps props;
SharedEventEmitter eventEmitter;
LayoutMetrics layoutMetrics;
State::Shared state;
std::vector<StubView::Shared> children;
};
bool operator==(StubView const &lhs, StubView const &rhs);
bool operator!=(StubView const &lhs, StubView const &rhs);
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,104 @@
// 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 "StubViewTree.h"
namespace facebook {
namespace react {
StubViewTree::StubViewTree(ShadowView const &shadowView) {
auto view = std::make_shared<StubView>();
view->update(shadowView);
registry[shadowView.tag] = view;
}
void StubViewTree::mutate(ShadowViewMutationList const &mutations) {
for (auto const &mutation : mutations) {
switch (mutation.type) {
case ShadowViewMutation::Create: {
assert(mutation.parentShadowView == ShadowView{});
assert(mutation.oldChildShadowView == ShadowView{});
auto stubView = std::make_shared<StubView>();
auto tag = mutation.newChildShadowView.tag;
assert(registry.find(tag) == registry.end());
registry[tag] = stubView;
break;
}
case ShadowViewMutation::Delete: {
assert(mutation.parentShadowView == ShadowView{});
assert(mutation.newChildShadowView == ShadowView{});
auto tag = mutation.oldChildShadowView.tag;
assert(registry.find(tag) != registry.end());
registry.erase(tag);
break;
}
case ShadowViewMutation::Insert: {
assert(mutation.oldChildShadowView == ShadowView{});
auto parentTag = mutation.parentShadowView.tag;
assert(registry.find(parentTag) != registry.end());
auto parentStubView = registry[parentTag];
auto childTag = mutation.newChildShadowView.tag;
assert(registry.find(childTag) != registry.end());
auto childStubView = registry[childTag];
childStubView->update(mutation.newChildShadowView);
parentStubView->children.insert(
parentStubView->children.begin() + mutation.index, childStubView);
break;
}
case ShadowViewMutation::Remove: {
assert(mutation.newChildShadowView == ShadowView{});
auto parentTag = mutation.parentShadowView.tag;
assert(registry.find(parentTag) != registry.end());
auto parentStubView = registry[parentTag];
auto childTag = mutation.oldChildShadowView.tag;
assert(registry.find(childTag) != registry.end());
auto childStubView = registry[childTag];
assert(
parentStubView->children[mutation.index]->tag ==
childStubView->tag);
parentStubView->children.erase(
parentStubView->children.begin() + mutation.index);
break;
}
case ShadowViewMutation::Update: {
assert(
mutation.newChildShadowView.tag == mutation.oldChildShadowView.tag);
assert(
registry.find(mutation.newChildShadowView.tag) != registry.end());
auto stubView = registry[mutation.newChildShadowView.tag];
stubView->update(mutation.newChildShadowView);
break;
}
}
}
}
bool operator==(StubViewTree const &lhs, StubViewTree const &rhs) {
if (lhs.registry.size() != rhs.registry.size()) {
return false;
}
for (auto const &pair : lhs.registry) {
auto &lhsStubView = *lhs.registry.at(pair.first);
auto &rhsStubView = *rhs.registry.at(pair.first);
if (lhsStubView != rhsStubView) {
return false;
}
}
return true;
}
bool operator!=(StubViewTree const &lhs, StubViewTree const &rhs) {
return !(lhs == rhs);
}
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,31 @@
// 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 <memory>
#include <unordered_map>
#include <react/mounting/ShadowViewMutation.h>
#include <react/mounting/StubView.h>
namespace facebook {
namespace react {
class StubViewTree {
public:
StubViewTree() = default;
StubViewTree(ShadowView const &shadowView);
void mutate(ShadowViewMutationList const &mutations);
std::unordered_map<Tag, StubView::Shared> registry{};
};
bool operator==(StubViewTree const &lhs, StubViewTree const &rhs);
bool operator!=(StubViewTree const &lhs, StubViewTree const &rhs);
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,30 @@
// 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 "stubs.h"
#include <react/core/LayoutableShadowNode.h>
#include <react/core/ShadowNodeFragment.h>
#include <react/mounting/Differentiator.h>
namespace facebook {
namespace react {
StubViewTree stubViewTreeFromShadowNode(ShadowNode const &rootShadowNode) {
auto emptyRootShadowNode = rootShadowNode.clone(
ShadowNodeFragment{ShadowNodeFragment::tagPlaceholder(),
ShadowNodeFragment::surfaceIdPlaceholder(),
ShadowNodeFragment::propsPlaceholder(),
ShadowNodeFragment::eventEmitterPlaceholder(),
ShadowNode::emptySharedShadowNodeSharedList()});
auto stubViewTree = StubViewTree(ShadowView(*emptyRootShadowNode));
stubViewTree.mutate(
calculateShadowViewMutations(*emptyRootShadowNode, rootShadowNode));
return stubViewTree;
}
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,18 @@
// 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 <react/core/ShadowNode.h>
#include "StubView.h"
#include "StubViewTree.h"
namespace facebook {
namespace react {
StubViewTree stubViewTreeFromShadowNode(ShadowNode const &rootShadowNode);
} // namespace react
} // namespace facebook