Commit 2b632491 authored by Dominic Mazzoni's avatar Dominic Mazzoni Committed by Commit Bot

Fix bug in serializing accessibility trees with invalidation.

In http://crrev.com/c/1152180, we introduced
AXTreeSerializer::InvalidateSubtree. This replaced
AXTreeSerializer::DeleteClientSubtree, which was problematic and led
to subtle, hard-to-catch unserialization errors.

This patch adds comprehensive tests for InvalidateSubtree by introducing it
into the "generated tree" tests, which try every permutation of trees with
up to a certain number of nodes and every possible change to those trees.
Now those tests also try every possible way to invalidate a subtree before
serializing.

This uncovered one subtle bug: the code to detect reparenting also needed
to check the 'invalid' flag and recursively check invalid children.

Bug: 870661, 866293
Change-Id: I45313b712e50db4fba16a5d55f72ecd440f04335
Reviewed-on: https://chromium-review.googlesource.com/1180091Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Commit-Queue: Dominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#584643}
parent bd6bbb6e
...@@ -148,9 +148,11 @@ TEST(AXGeneratedTreeTest, SerializeGeneratedTrees) { ...@@ -148,9 +148,11 @@ TEST(AXGeneratedTreeTest, SerializeGeneratedTrees) {
// Now iterate over which node to update first, |k|. // Now iterate over which node to update first, |k|.
for (int k = 0; k < tree_size; k++) { for (int k = 0; k < tree_size; k++) {
SCOPED_TRACE("i=" + base::IntToString(i) + // Iterate over a node to invalidate, |l| (zero means no invalidation).
" j=" + base::IntToString(j) + for (int l = 0; l <= tree_size; l++) {
" k=" + base::IntToString(k)); SCOPED_TRACE(
"i=" + base::IntToString(i) + " j=" + base::IntToString(j) +
" k=" + base::IntToString(k) + " l=" + base::IntToString(l));
// Start by serializing tree0 and unserializing it into a new // Start by serializing tree0 and unserializing it into a new
// empty tree |dst_tree|. // empty tree |dst_tree|.
...@@ -167,12 +169,16 @@ TEST(AXGeneratedTreeTest, SerializeGeneratedTrees) { ...@@ -167,12 +169,16 @@ TEST(AXGeneratedTreeTest, SerializeGeneratedTrees) {
// At this point, |dst_tree| should now be identical to |tree0|. // At this point, |dst_tree| should now be identical to |tree0|.
EXPECT_EQ(TreeToString(tree0), TreeToString(dst_tree)); EXPECT_EQ(TreeToString(tree0), TreeToString(dst_tree));
// Next, pretend that tree0 turned into tree1, and serialize // Next, pretend that tree0 turned into tree1.
// a sequence of updates to |dst_tree| to match.
std::unique_ptr<AXTreeSource<const AXNode*, AXNodeData, AXTreeData>> std::unique_ptr<AXTreeSource<const AXNode*, AXNodeData, AXTreeData>>
tree1_source(tree1.CreateTreeSource()); tree1_source(tree1.CreateTreeSource());
serializer.ChangeTreeSourceForTesting(tree1_source.get()); serializer.ChangeTreeSourceForTesting(tree1_source.get());
// Invalidate a subtree rooted at one of the nodes.
if (l > 0)
serializer.InvalidateSubtree(tree1.GetFromId(l));
// Serialize a sequence of updates to |dst_tree| to match.
for (int k_index = 0; k_index < tree_size; ++k_index) { for (int k_index = 0; k_index < tree_size; ++k_index) {
int id = 1 + (k + k_index) % tree_size; int id = 1 + (k + k_index) % tree_size;
AXTreeUpdate update; AXTreeUpdate update;
...@@ -187,6 +193,7 @@ TEST(AXGeneratedTreeTest, SerializeGeneratedTrees) { ...@@ -187,6 +193,7 @@ TEST(AXGeneratedTreeTest, SerializeGeneratedTrees) {
} }
} }
} }
}
} }
} // namespace ui } // namespace ui
...@@ -306,8 +306,8 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>:: ...@@ -306,8 +306,8 @@ bool AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::
// and return true (reparenting was found). // and return true (reparenting was found).
*out_lca = LeastCommonAncestor(*out_lca, client_child); *out_lca = LeastCommonAncestor(*out_lca, client_child);
result = true; result = true;
} else { } else if (!client_child->invalid) {
// This child is already in the client tree, we won't // This child is already in the client tree and valid, we won't
// recursively serialize it so we don't need to check this // recursively serialize it so we don't need to check this
// subtree recursively for reparenting. // subtree recursively for reparenting.
continue; continue;
......
...@@ -180,6 +180,10 @@ TEST_F(AXTreeSerializerTest, ReparentingUpdatesSubtree) { ...@@ -180,6 +180,10 @@ TEST_F(AXTreeSerializerTest, ReparentingUpdatesSubtree) {
AXTreeUpdate update; AXTreeUpdate update;
ASSERT_TRUE(serializer_->SerializeChanges(tree1_->GetFromId(4), &update)); ASSERT_TRUE(serializer_->SerializeChanges(tree1_->GetFromId(4), &update));
// The update should unserialize without errors.
AXTree dst_tree(treedata0_);
EXPECT_TRUE(dst_tree.Unserialize(update)) << dst_tree.error();
// The update should delete the subtree rooted at node id=2, and // The update should delete the subtree rooted at node id=2, and
// then include nodes 2...5. // then include nodes 2...5.
EXPECT_EQ(2, update.node_id_to_clear); EXPECT_EQ(2, update.node_id_to_clear);
...@@ -190,6 +194,48 @@ TEST_F(AXTreeSerializerTest, ReparentingUpdatesSubtree) { ...@@ -190,6 +194,48 @@ TEST_F(AXTreeSerializerTest, ReparentingUpdatesSubtree) {
EXPECT_EQ(5, update.nodes[3].id); EXPECT_EQ(5, update.nodes[3].id);
} }
// Similar to ReparentingUpdatesSubtree, except that InvalidateSubtree is
// called on id=1 - we need to make sure that the reparenting is still
// detected.
TEST_F(AXTreeSerializerTest, ReparentingWithInvalidationUpdatesSubtree) {
// (1 (2 (3 (4 (5)))))
treedata0_.root_id = 1;
treedata0_.nodes.resize(5);
treedata0_.nodes[0].id = 1;
treedata0_.nodes[0].child_ids.push_back(2);
treedata0_.nodes[1].id = 2;
treedata0_.nodes[1].child_ids.push_back(3);
treedata0_.nodes[2].id = 3;
treedata0_.nodes[2].child_ids.push_back(4);
treedata0_.nodes[3].id = 4;
treedata0_.nodes[3].child_ids.push_back(5);
treedata0_.nodes[4].id = 5;
// Node 5 has been reparented from being a child of node 4,
// to a child of node 2.
// (1 (2 (3 (4) 5)))
treedata1_.root_id = 1;
treedata1_.nodes.resize(5);
treedata1_.nodes[0].id = 1;
treedata1_.nodes[0].child_ids.push_back(2);
treedata1_.nodes[1].id = 2;
treedata1_.nodes[1].child_ids.push_back(3);
treedata1_.nodes[1].child_ids.push_back(5);
treedata1_.nodes[2].id = 3;
treedata1_.nodes[2].child_ids.push_back(4);
treedata1_.nodes[3].id = 4;
treedata1_.nodes[4].id = 5;
CreateTreeSerializer();
AXTreeUpdate update;
serializer_->InvalidateSubtree(tree1_->GetFromId(1));
ASSERT_TRUE(serializer_->SerializeChanges(tree1_->GetFromId(4), &update));
// The update should unserialize without errors.
AXTree dst_tree(treedata0_);
EXPECT_TRUE(dst_tree.Unserialize(update)) << dst_tree.error();
}
// A variant of AXTreeSource that returns true for IsValid() for one // A variant of AXTreeSource that returns true for IsValid() for one
// particular id. // particular id.
class AXTreeSourceWithInvalidId class AXTreeSourceWithInvalidId
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment