ax_generated_tree_unittest.cc 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include <memory>
  5. #include <numeric>
  6. #include <vector>
  7. #include "base/strings/string_number_conversions.h"
  8. #include "build/build_config.h"
  9. #include "testing/gtest/include/gtest/gtest-param-test.h"
  10. #include "testing/gtest/include/gtest/gtest.h"
  11. #include "third_party/abseil-cpp/absl/types/optional.h"
  12. #include "ui/accessibility/ax_event_generator.h"
  13. #include "ui/accessibility/ax_node.h"
  14. #include "ui/accessibility/ax_serializable_tree.h"
  15. #include "ui/accessibility/ax_tree.h"
  16. #include "ui/accessibility/ax_tree_serializer.h"
  17. #include "ui/accessibility/tree_generator.h"
  18. namespace ui {
  19. namespace {
  20. // Do a more exhaustive test in release mode. If you're modifying
  21. // the algorithm you may want to try even larger tree sizes if you
  22. // can afford the time.
  23. #ifdef NDEBUG
  24. constexpr int kMax_tree_size = 4;
  25. #else
  26. constexpr int kMax_tree_size = 3;
  27. #endif
  28. // We split the test into four by splitting the two nested loops that builds the
  29. // trees. To do so, we need to know the maximum number of (permuted) trees.
  30. constexpr int kMax_number_of_trees0 =
  31. TreeGenerator::ComputeUniqueTreeCount(kMax_tree_size,
  32. /* permutations */ false);
  33. constexpr int kMax_number_of_trees1 =
  34. TreeGenerator::ComputeUniqueTreeCount(kMax_tree_size,
  35. /* permutations */ true);
  36. // A function to turn a tree into a string, capturing only the node ids
  37. // and their relationship to one another.
  38. //
  39. // The string format is kind of like an S-expression, with each expression
  40. // being either a node id, or a node id followed by a subexpression
  41. // representing its children.
  42. //
  43. // Examples:
  44. //
  45. // (1) is a tree with a single node with id 1.
  46. // (1 (2 3)) is a tree with 1 as the root, and 2 and 3 as its children.
  47. // (1 (2 (3))) has 1 as the root, 2 as its child, and then 3 as the child of 2.
  48. // (1 (2 (3x))) is the same with node 3 ignored.
  49. std::string TreeToStringHelper(const AXNode* node) {
  50. std::string result = base::NumberToString(node->id());
  51. if (node->IsIgnored())
  52. result += "x";
  53. if (node->children().empty())
  54. return result;
  55. const auto add_children = [](const std::string& str, const auto* node) {
  56. return str + " " + TreeToStringHelper(node);
  57. };
  58. return result + " (" +
  59. std::accumulate(node->children().cbegin() + 1, node->children().cend(),
  60. TreeToStringHelper(node->children().front()),
  61. add_children) +
  62. ")";
  63. }
  64. std::string TreeToString(const AXTree& tree) {
  65. return "(" + TreeToStringHelper(tree.root()) + ")";
  66. }
  67. AXTreeUpdate SerializeEntireTree(AXSerializableTree& tree) {
  68. std::unique_ptr<AXTreeSource<const AXNode*>> tree_source(
  69. tree.CreateTreeSource());
  70. AXTreeSerializer<const AXNode*> serializer(tree_source.get());
  71. AXTreeUpdate update;
  72. CHECK(serializer.SerializeChanges(tree.root(), &update));
  73. return update;
  74. }
  75. // Create an AXTreeUpdate consisting of only those nodes from
  76. // |tree0| that changed their ignored status in |tree1|.
  77. AXTreeUpdate MakeTreeUpdateFromIgnoredChanges(AXSerializableTree& tree0,
  78. AXSerializableTree& tree1) {
  79. AXTreeUpdate update = SerializeEntireTree(tree1);
  80. AXTreeUpdate result;
  81. for (size_t i = 0; i < update.nodes.size(); i++) {
  82. AXNode* tree0_node = tree0.GetFromId(update.nodes[i].id);
  83. AXNode* tree1_node = tree1.GetFromId(update.nodes[i].id);
  84. if (tree0_node->IsIgnored() != tree1_node->IsIgnored())
  85. result.nodes.push_back(update.nodes[i]);
  86. }
  87. return result;
  88. }
  89. void SerializeUnignoredNodes(AXNode* node, AXTreeUpdate* update) {
  90. AXNodeData data = node->data();
  91. data.child_ids.clear();
  92. for (size_t i = 0; i < node->GetUnignoredChildCount(); i++) {
  93. AXNode* child = node->GetUnignoredChildAtIndex(i);
  94. data.child_ids.push_back(child->id());
  95. }
  96. update->nodes.push_back(data);
  97. for (size_t i = 0; i < node->GetUnignoredChildCount(); i++) {
  98. AXNode* child = node->GetUnignoredChildAtIndex(i);
  99. SerializeUnignoredNodes(child, update);
  100. }
  101. }
  102. void MakeTreeOfUnignoredNodesOnly(AXSerializableTree& src,
  103. AXSerializableTree* dst) {
  104. AXTreeUpdate update;
  105. update.root_id = src.root()->id();
  106. SerializeUnignoredNodes(src.root(), &update);
  107. CHECK(dst->Unserialize(update));
  108. }
  109. } // namespace
  110. // Test the TreeGenerator class by building all possible trees with
  111. // 3 nodes and the ids [1...3], with no permutations of ids.
  112. TEST(AXGeneratedTreeTest, TestTreeGeneratorNoPermutations) {
  113. int tree_size = 3;
  114. TreeGenerator generator(tree_size, false);
  115. // clang-format off
  116. const char* EXPECTED_TREES[] = {
  117. "(1)",
  118. "(1 (2))",
  119. "(1 (2 3))",
  120. "(1 (2 (3)))",
  121. };
  122. // clang-format on
  123. int n = generator.UniqueTreeCount();
  124. ASSERT_EQ(static_cast<int>(std::size(EXPECTED_TREES)), n);
  125. for (int i = 0; i < n; ++i) {
  126. AXTree tree;
  127. generator.BuildUniqueTree(i, &tree);
  128. std::string str = TreeToString(tree);
  129. EXPECT_EQ(EXPECTED_TREES[i], str);
  130. }
  131. }
  132. // Test generating trees with permutations of ignored nodes.
  133. TEST(AXGeneratedTreeTest, TestGeneratingTreesWithIgnoredNodes) {
  134. int tree_size = 3;
  135. TreeGenerator generator(tree_size, false);
  136. // clang-format off
  137. const char* EXPECTED_TREES[] = {
  138. "(1)",
  139. "(1 (2))",
  140. "(1 (2x))",
  141. "(1 (2 3))",
  142. "(1 (2x 3))",
  143. "(1 (2 3x))",
  144. "(1 (2x 3x))",
  145. "(1 (2 (3)))",
  146. "(1 (2x (3)))",
  147. "(1 (2 (3x)))",
  148. "(1 (2x (3x)))",
  149. };
  150. // clang-format on
  151. int n = generator.UniqueTreeCount();
  152. int expected_index = 0;
  153. for (int i = 0; i < n; ++i) {
  154. int ignored_permutation_count =
  155. generator.IgnoredPermutationCountPerUniqueTree(i);
  156. for (int j = 0; j < ignored_permutation_count; j++) {
  157. AXTree tree;
  158. generator.BuildUniqueTreeWithIgnoredNodes(
  159. i, j, /* focused_node */ absl::nullopt, &tree);
  160. std::string str = TreeToString(tree);
  161. EXPECT_EQ(EXPECTED_TREES[expected_index++], str);
  162. }
  163. }
  164. EXPECT_EQ(11, expected_index);
  165. }
  166. // Test the TreeGenerator class by building all possible trees with
  167. // 3 nodes and the ids [1...3] permuted in any order.
  168. TEST(AXGeneratedTreeTest, TestTreeGeneratorWithPermutations) {
  169. int tree_size = 3;
  170. TreeGenerator generator(tree_size, true);
  171. // clang-format off
  172. const char* EXPECTED_TREES[] = {
  173. "(1)",
  174. "(1 (2))",
  175. "(2 (1))",
  176. "(1 (2 3))",
  177. "(2 (1 3))",
  178. "(3 (1 2))",
  179. "(1 (3 2))",
  180. "(2 (3 1))",
  181. "(3 (2 1))",
  182. "(1 (2 (3)))",
  183. "(2 (1 (3)))",
  184. "(3 (1 (2)))",
  185. "(1 (3 (2)))",
  186. "(2 (3 (1)))",
  187. "(3 (2 (1)))",
  188. };
  189. // clang-format on
  190. int n = generator.UniqueTreeCount();
  191. ASSERT_EQ(static_cast<int>(std::size(EXPECTED_TREES)), n);
  192. for (int i = 0; i < n; i++) {
  193. AXTree tree;
  194. generator.BuildUniqueTree(i, &tree);
  195. std::string str = TreeToString(tree);
  196. EXPECT_EQ(EXPECTED_TREES[i], str);
  197. }
  198. }
  199. struct PermutationBlock {
  200. PermutationBlock(int first_unique_tree0,
  201. int last_unique_tree0,
  202. int first_unique_tree1,
  203. int last_unique_tree1)
  204. : first_unique_tree0(first_unique_tree0),
  205. last_unique_tree0(last_unique_tree0),
  206. first_unique_tree1(first_unique_tree1),
  207. last_unique_tree1(last_unique_tree1) {}
  208. int first_unique_tree0;
  209. int last_unique_tree0;
  210. int first_unique_tree1;
  211. int last_unique_tree1;
  212. };
  213. class SerializeGeneratedTreesTest
  214. : public testing::TestWithParam<PermutationBlock> {};
  215. TEST_P(SerializeGeneratedTreesTest, SerializeGeneratedTrees) {
  216. const int first_tree0_ = GetParam().first_unique_tree0;
  217. const int last_tree0_ = GetParam().last_unique_tree0;
  218. const int first_tree1_ = GetParam().first_unique_tree1;
  219. const int last_tree1_ = GetParam().last_unique_tree1;
  220. TreeGenerator generator0(kMax_tree_size, /* permutations */ false);
  221. TreeGenerator generator1(kMax_tree_size, /* permutations */ true);
  222. for (int i = first_tree0_; i < last_tree0_; i++) {
  223. // Build the first tree, tree0.
  224. AXSerializableTree tree0;
  225. generator0.BuildUniqueTree(i, &tree0);
  226. SCOPED_TRACE("tree0 is " + TreeToString(tree0));
  227. for (int j = first_tree1_; j < last_tree1_; j++) {
  228. // Build the second tree, tree1.
  229. AXSerializableTree tree1;
  230. generator1.BuildUniqueTree(j, &tree1);
  231. SCOPED_TRACE("tree1 is " + TreeToString(tree1));
  232. int tree_size = tree1.size();
  233. // Now iterate over which node to update first, |k|.
  234. for (int k = 0; k < tree_size; k++) {
  235. // Iterate over a node to invalidate, |l| (zero means no invalidation).
  236. for (int l = 0; l <= tree_size; l++) {
  237. SCOPED_TRACE("i=" + base::NumberToString(i) +
  238. " j=" + base::NumberToString(j) +
  239. " k=" + base::NumberToString(k) +
  240. " l=" + base::NumberToString(l));
  241. // Start by serializing tree0 and unserializing it into a new
  242. // empty tree |dst_tree|.
  243. std::unique_ptr<AXTreeSource<const AXNode*>> tree0_source(
  244. tree0.CreateTreeSource());
  245. AXTreeSerializer<const AXNode*> serializer(tree0_source.get());
  246. AXTreeUpdate update0;
  247. ASSERT_TRUE(serializer.SerializeChanges(tree0.root(), &update0));
  248. AXTree dst_tree;
  249. ASSERT_TRUE(dst_tree.Unserialize(update0))
  250. << dst_tree.error() << "\n"
  251. << TreeToString(dst_tree)
  252. << "\nTree update: " << update0.ToString();
  253. // At this point, |dst_tree| should now be identical to |tree0|.
  254. EXPECT_EQ(TreeToString(tree0), TreeToString(dst_tree));
  255. // Next, pretend that tree0 turned into tree1.
  256. std::unique_ptr<AXTreeSource<const AXNode*>> tree1_source(
  257. tree1.CreateTreeSource());
  258. serializer.ChangeTreeSourceForTesting(tree1_source.get());
  259. // Invalidate a subtree rooted at one of the nodes.
  260. if (l > 0)
  261. serializer.InvalidateSubtree(tree1.GetFromId(l));
  262. // Serialize a sequence of updates to |dst_tree| to match.
  263. for (int k_index = 0; k_index < tree_size; ++k_index) {
  264. int id = 1 + (k + k_index) % tree_size;
  265. AXTreeUpdate update;
  266. ASSERT_TRUE(
  267. serializer.SerializeChanges(tree1.GetFromId(id), &update));
  268. std::string tree_before_str = TreeToString(dst_tree);
  269. ASSERT_TRUE(dst_tree.Unserialize(update))
  270. << dst_tree.error() << "\nTree before : " << tree_before_str
  271. << "\nTree after : " << TreeToString(dst_tree)
  272. << "\nExpected after: " << TreeToString(tree1)
  273. << "\nTree update : " << update.ToString();
  274. }
  275. // After the sequence of updates, |dst_tree| should now be
  276. // identical to |tree1|.
  277. EXPECT_EQ(TreeToString(tree1), TreeToString(dst_tree));
  278. }
  279. }
  280. }
  281. }
  282. }
  283. // Test mutating every possible tree with <n> nodes to every other possible
  284. // tree with <n> nodes, where <n> is 4 in release mode and 3 in debug mode
  285. // (for speed). For each possible combination of trees, we also vary which
  286. // node we serialize first.
  287. //
  288. // For every possible scenario, we check that the AXTreeUpdate is valid,
  289. // that the destination tree can unserialize it and create a valid tree,
  290. // and that after updating all nodes the resulting tree now matches the
  291. // intended tree.
  292. //
  293. // Sheriffs: this test is actually very stable and reliable, but it's
  294. // cpu-bound so under extremely heavy load it sometimes times out even
  295. // though it only takes 1 - 2 seconds to run under normal load.
  296. // Please don't disable unless it's actually flaking frequently (e.g.,
  297. // every day). Check Flake Portal first.
  298. INSTANTIATE_TEST_SUITE_P(
  299. AXGeneratedTreeTest0,
  300. SerializeGeneratedTreesTest,
  301. testing::Values(PermutationBlock(0,
  302. kMax_number_of_trees0 / 2,
  303. 0,
  304. kMax_number_of_trees1 / 2),
  305. PermutationBlock(0,
  306. kMax_number_of_trees0 / 2,
  307. kMax_number_of_trees1 / 2,
  308. kMax_number_of_trees1),
  309. PermutationBlock(kMax_number_of_trees0 / 2,
  310. kMax_number_of_trees0,
  311. 0,
  312. kMax_number_of_trees1 / 2),
  313. PermutationBlock(kMax_number_of_trees0 / 2,
  314. kMax_number_of_trees0,
  315. kMax_number_of_trees1 / 2,
  316. kMax_number_of_trees1)));
  317. // Sheriffs: this test is actually very stable and reliable, but it's
  318. // cpu-bound so under extremely heavy load it sometimes times out even
  319. // though it only takes 1 - 2 seconds to run under normal load.
  320. // Please don't disable unless it's actually flaking frequently (e.g.,
  321. // every day). Check Flake Portal first.
  322. TEST(AXGeneratedTreeTest, GeneratedTreesWithIgnoredNodes) {
  323. // Do a more exhaustive test in release mode. If you're modifying
  324. // the algorithm you may want to try even larger tree sizes if you
  325. // can afford the time.
  326. #ifdef NDEBUG
  327. int max_tree_size = 5;
  328. #else
  329. LOG(WARNING) << "Debug build, only testing trees with 4 nodes and not 5.";
  330. int max_tree_size = 4;
  331. #endif
  332. TreeGenerator generator(max_tree_size, false);
  333. int unique_tree_count = generator.UniqueTreeCount();
  334. // Loop over every possible tree up to a certain size.
  335. for (int tree_index = 0; tree_index < unique_tree_count; tree_index++) {
  336. // Try each permutation of nodes other than the root being ignored.
  337. // We'll call this tree the "fat" tree because it has redundant
  338. // ignored nodes. Also try permuting the focused node, because focus affects
  339. // the ignored state of a node by removing it.
  340. int ignored_permutation_count =
  341. generator.IgnoredPermutationCountPerUniqueTree(tree_index);
  342. for (int perm_index0 = 0; perm_index0 < ignored_permutation_count;
  343. perm_index0++) {
  344. AXSerializableTree fat_tree;
  345. generator.BuildUniqueTreeWithIgnoredNodes(
  346. tree_index, perm_index0, /* focused_node */ absl::nullopt, &fat_tree);
  347. SCOPED_TRACE("fat_tree is " + TreeToString(fat_tree));
  348. // Create a second tree, also with each permutations of nodes other than
  349. // the root being ignored.
  350. for (int perm_index1 = 1; perm_index1 < ignored_permutation_count;
  351. perm_index1++) {
  352. AXSerializableTree fat_tree1;
  353. generator.BuildUniqueTreeWithIgnoredNodes(
  354. tree_index, perm_index1, /* focused_node */ absl::nullopt,
  355. &fat_tree1);
  356. SCOPED_TRACE("fat_tree1 is " + TreeToString(fat_tree1));
  357. // Make a source and destination tree using only the unignored nodes.
  358. // We call this one the "skinny" tree.
  359. AXSerializableTree skinny_tree;
  360. MakeTreeOfUnignoredNodesOnly(fat_tree, &skinny_tree);
  361. AXSerializableTree skinny_tree1;
  362. MakeTreeOfUnignoredNodesOnly(fat_tree1, &skinny_tree1);
  363. // Now, turn fat_tree into fat_tree1, and record the generated events.
  364. AXEventGenerator event_generator(&fat_tree);
  365. AXTreeUpdate update =
  366. MakeTreeUpdateFromIgnoredChanges(fat_tree, fat_tree1);
  367. std::string fat_tree_before_str = TreeToString(fat_tree);
  368. ASSERT_TRUE(fat_tree.Unserialize(update))
  369. << fat_tree.error() << "\nTree before : " << fat_tree_before_str
  370. << "\nTree after :" << TreeToString(fat_tree)
  371. << "\nExpected after: " << TreeToString(fat_tree1)
  372. << "\nTree update : " << update.ToString();
  373. EXPECT_EQ(TreeToString(fat_tree), TreeToString(fat_tree1));
  374. // Capture the events generated.
  375. std::map<AXNodeID, std::set<AXEventGenerator::Event>> actual_events;
  376. for (const AXEventGenerator::TargetedEvent& event : event_generator) {
  377. const AXNode* node = fat_tree.GetFromId(event.node_id);
  378. ASSERT_NE(nullptr, node);
  379. if (node->IsIgnored() ||
  380. event.event_params.event ==
  381. AXEventGenerator::Event::IGNORED_CHANGED) {
  382. continue;
  383. }
  384. actual_events[event.node_id].insert(event.event_params.event);
  385. }
  386. // Now, turn skinny_tree into skinny_tree1 and compare
  387. // the generated events.
  388. AXEventGenerator skinny_event_generator(&skinny_tree);
  389. AXTreeUpdate skinny_update = SerializeEntireTree(skinny_tree1);
  390. std::string skinny_tree_before_str = TreeToString(skinny_tree);
  391. ASSERT_TRUE(skinny_tree.Unserialize(skinny_update))
  392. << skinny_tree.error()
  393. << "\nTree before : " << skinny_tree_before_str
  394. << "\nTree after :" << TreeToString(skinny_tree)
  395. << "\nExpected after: " << TreeToString(skinny_tree1)
  396. << "\nTree update : " << skinny_update.ToString();
  397. EXPECT_EQ(TreeToString(skinny_tree), TreeToString(skinny_tree1));
  398. std::map<AXNodeID, std::set<AXEventGenerator::Event>> expected_events;
  399. for (const AXEventGenerator::TargetedEvent& event :
  400. skinny_event_generator)
  401. expected_events[event.node_id].insert(event.event_params.event);
  402. for (auto& entry : expected_events) {
  403. AXNodeID node_id = entry.first;
  404. for (auto& event_type : entry.second) {
  405. EXPECT_TRUE(actual_events[node_id].find(event_type) !=
  406. actual_events[node_id].end())
  407. << "Expected " << event_type << " on node " << node_id;
  408. }
  409. }
  410. for (auto& entry : actual_events) {
  411. AXNodeID node_id = entry.first;
  412. for (auto& event_type : entry.second) {
  413. EXPECT_TRUE(expected_events[node_id].find(event_type) !=
  414. expected_events[node_id].end())
  415. << "Unexpected " << event_type << " on node " << node_id;
  416. }
  417. }
  418. // For each node in skinny_tree (the tree with only the unignored
  419. // nodes), check the node in fat_tree (the tree with ignored nodes).
  420. // Make sure that the parents, children, and siblings are all computed
  421. // correctly.
  422. AXTreeUpdate skinny_tree_serialized = SerializeEntireTree(skinny_tree);
  423. for (const AXNodeData& skinny_tree_node_data :
  424. skinny_tree_serialized.nodes) {
  425. AXNodeID id = skinny_tree_node_data.id;
  426. AXNode* skinny_tree_node = skinny_tree.GetFromId(id);
  427. AXNode* fat_tree_node = fat_tree.GetFromId(id);
  428. SCOPED_TRACE("Testing node ID " + base::NumberToString(id));
  429. // Check children.
  430. EXPECT_EQ(skinny_tree_node->children().size(),
  431. fat_tree_node->GetUnignoredChildCount());
  432. // Check child IDs.
  433. for (size_t j = 0; j < skinny_tree_node->children().size(); j++) {
  434. AXNode* skinny_tree_child = skinny_tree_node->children()[j];
  435. AXNode* fat_tree_child = fat_tree_node->GetUnignoredChildAtIndex(j);
  436. EXPECT_TRUE(skinny_tree_child);
  437. EXPECT_TRUE(fat_tree_child);
  438. if (fat_tree_child)
  439. EXPECT_EQ(skinny_tree_child->id(), fat_tree_child->id());
  440. }
  441. // Check parent.
  442. if (skinny_tree_node->parent()) {
  443. EXPECT_EQ(skinny_tree_node->parent()->id(),
  444. fat_tree_node->GetUnignoredParent()->id());
  445. } else {
  446. EXPECT_FALSE(fat_tree_node->GetUnignoredParent());
  447. }
  448. // Check index in parent.
  449. EXPECT_EQ(skinny_tree_node->index_in_parent(),
  450. fat_tree_node->GetUnignoredIndexInParent());
  451. // Unignored previous sibling.
  452. size_t index_in_parent = skinny_tree_node->index_in_parent();
  453. size_t num_siblings =
  454. skinny_tree_node->parent()
  455. ? skinny_tree_node->parent()->children().size()
  456. : 1;
  457. if (index_in_parent > 0) {
  458. AXNode* skinny_tree_previous_sibling =
  459. skinny_tree_node->parent()->children()[index_in_parent - 1];
  460. AXNode* fat_tree_previous_sibling =
  461. fat_tree_node->GetPreviousUnignoredSibling();
  462. EXPECT_TRUE(fat_tree_previous_sibling);
  463. if (fat_tree_previous_sibling) {
  464. EXPECT_EQ(skinny_tree_previous_sibling->id(),
  465. fat_tree_previous_sibling->id());
  466. }
  467. }
  468. // Unignored next sibling.
  469. if (index_in_parent < num_siblings - 1) {
  470. AXNode* skinny_tree_next_sibling =
  471. skinny_tree_node->parent()->children()[index_in_parent + 1];
  472. AXNode* fat_tree_next_sibling =
  473. fat_tree_node->GetNextUnignoredSibling();
  474. EXPECT_TRUE(fat_tree_next_sibling);
  475. if (fat_tree_next_sibling) {
  476. EXPECT_EQ(skinny_tree_next_sibling->id(),
  477. fat_tree_next_sibling->id());
  478. }
  479. }
  480. }
  481. }
  482. }
  483. }
  484. }
  485. } // namespace ui