blob: a567d06522887159ca2b069761270b8bdc1214e4 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/ui/scenic/lib/view_tree/view_tree_snapshotter.h"
#include <lib/async-testing/test_loop.h>
#include <lib/async/default.h>
#include <lib/syslog/cpp/macros.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/ui/scenic/lib/view_tree/snapshot_types.h"
namespace view_tree::test {
namespace {
enum : zx_koid_t {
kRoot1A = 1,
kNode2,
kNode3,
kRoot4B,
kNode5,
kRoot6C,
kNode7,
kNode8,
kNode9,
kNode10,
kNode11,
};
// Generates a valid tree out of three subtrees: A, B and C
// ViewTrees: Unconnected nodes:
// ------------- -----------------------
// | A 1 | | A 8 | B 9 | C 10 11 |
// | / \ | -----------------------
// | 2 3 |
// | | | |
// -------------
// |B 4 |C 6 |
// | | | | |
// | 5 | 7 |
// ------ -----
std::vector<SubtreeSnapshotGenerator> BasicTree() {
std::vector<SubtreeSnapshotGenerator> subtree_generators;
// A
subtree_generators.emplace_back([] {
SubtreeSnapshot subtree;
auto& [root, view_tree, unconnected_views, hit_tester, tree_boundaries] = subtree;
root = kRoot1A;
view_tree[kRoot1A] = ViewNode{.parent = ZX_KOID_INVALID, .children = {kNode2, kNode3}};
view_tree[kNode2] = ViewNode{.parent = kRoot1A, .children = {kRoot4B}};
view_tree[kNode3] = ViewNode{.parent = kRoot1A, .children = {kRoot6C}};
unconnected_views = {kNode8};
tree_boundaries.emplace(kNode2, kRoot4B);
tree_boundaries.emplace(kNode3, kRoot6C);
return subtree;
});
// B
subtree_generators.emplace_back([] {
SubtreeSnapshot subtree;
auto& [root, view_tree, unconnected_views, hit_tester, tree_boundaries] = subtree;
root = kRoot4B;
view_tree[kRoot4B] = ViewNode{.parent = ZX_KOID_INVALID, .children = {kNode5}};
view_tree[kNode5] = ViewNode{.parent = kRoot4B, .children = {}};
unconnected_views = {kNode9};
return subtree;
});
// C
subtree_generators.emplace_back([] {
SubtreeSnapshot subtree;
auto& [root, view_tree, unconnected_views, hit_tester, tree_boundaries] = subtree;
root = kRoot6C;
view_tree[kRoot6C] = ViewNode{.parent = ZX_KOID_INVALID, .children = {kNode7}};
view_tree[kNode7] = ViewNode{.parent = kRoot6C, .children = {}};
unconnected_views = {kNode10, kNode11};
return subtree;
});
return subtree_generators;
}
// Expected combined Snapshot from BasicTree() above.
Snapshot BasicTreeSnapshot() {
Snapshot snapshot;
snapshot.root = kRoot1A;
{
auto& view_tree = snapshot.view_tree;
view_tree[kRoot1A] = ViewNode{.parent = ZX_KOID_INVALID, .children = {kNode2, kNode3}};
view_tree[kNode2] = ViewNode{.parent = kRoot1A, .children = {kRoot4B}};
view_tree[kNode3] = ViewNode{.parent = kRoot1A, .children = {kRoot6C}};
view_tree[kRoot4B] = ViewNode{.parent = kNode2, .children = {kNode5}};
view_tree[kNode5] = ViewNode{.parent = kRoot4B};
view_tree[kRoot6C] = ViewNode{.parent = kNode3, .children = {kNode7}};
view_tree[kNode7] = ViewNode{.parent = kRoot6C};
}
snapshot.unconnected_views = {kNode8, kNode9, kNode10, kNode11};
return snapshot;
}
} // namespace
// Checks that BasicTree() gets combined to the correct Snapshot, and that the snapshot is
// correctly delivered to a subscriber.
TEST(ViewTreeSnapshotterTest, BasicTreeTest) {
std::vector<ViewTreeSnapshotter::Subscriber> subscribers;
async::TestLoop loop;
bool callback_fired = false;
subscribers.push_back({.on_new_view_tree =
[&callback_fired](std::shared_ptr<const Snapshot> snapshot) {
callback_fired = true;
const bool conversion_correct = *snapshot == BasicTreeSnapshot();
EXPECT_TRUE(conversion_correct);
if (!conversion_correct) {
FX_LOGS(ERROR) << "Generated snapshot:\n"
<< (*snapshot) << "\ndid not match expected:\n\n"
<< BasicTreeSnapshot();
}
},
.dispatcher = loop.dispatcher()});
ViewTreeSnapshotter tree(BasicTree(), std::move(subscribers));
tree.UpdateSnapshot();
loop.RunUntilIdle();
EXPECT_TRUE(callback_fired);
}
// Check that the subscriber fires on the supplied dispatcher and doesn't rely on the default
// dispatcher.
TEST(ViewTreeSnapshotterTest, Subscriber_RunsOnCorrectDispatcher) {
std::vector<ViewTreeSnapshotter::Subscriber> subscribers;
async::TestLoop loop1;
async::TestLoop loop2;
async_set_default_dispatcher(loop1.dispatcher());
bool callback_fired = false;
subscribers.push_back({.on_new_view_tree = [&callback_fired](auto) { callback_fired = true; },
.dispatcher = loop2.dispatcher()});
ViewTreeSnapshotter tree(BasicTree(), std::move(subscribers));
tree.UpdateSnapshot();
EXPECT_FALSE(callback_fired);
loop1.RunUntilIdle();
EXPECT_FALSE(callback_fired);
loop2.RunUntilIdle();
EXPECT_TRUE(callback_fired);
}
TEST(ViewTreeSnapshotterTest, MultipleSubscribers) {
std::vector<ViewTreeSnapshotter::Subscriber> subscribers;
async::TestLoop loop;
std::shared_ptr<const Snapshot> snapshot1;
subscribers.push_back({.on_new_view_tree = [&snapshot1](auto snapshot) { snapshot1 = snapshot; },
.dispatcher = loop.dispatcher()});
std::shared_ptr<const Snapshot> snapshot2;
subscribers.push_back({.on_new_view_tree = [&snapshot2](auto snapshot) { snapshot2 = snapshot; },
.dispatcher = loop.dispatcher()});
async::TestLoop loop2;
std::shared_ptr<const Snapshot> snapshot3;
subscribers.push_back({.on_new_view_tree = [&snapshot3](auto snapshot) { snapshot3 = snapshot; },
.dispatcher = loop2.dispatcher()});
ViewTreeSnapshotter tree(BasicTree(), std::move(subscribers));
tree.UpdateSnapshot();
loop.RunUntilIdle();
EXPECT_TRUE(snapshot1);
EXPECT_TRUE(snapshot2);
EXPECT_FALSE(snapshot3);
loop2.RunUntilIdle();
EXPECT_TRUE(snapshot3);
// Should all be pointing to the same snapshot.
EXPECT_EQ(snapshot1, snapshot2);
EXPECT_EQ(snapshot1, snapshot3);
}
// Check that multiple calls to UpdateSnapshot() are handled correctly.
TEST(ViewTreeSnapshotterTest, MultipleUpdateSnapshot) {
std::vector<SubtreeSnapshotGenerator> subtrees;
bool first_call = true;
subtrees.emplace_back([&first_call] {
SubtreeSnapshot subtree;
if (first_call) {
subtree.root = kRoot1A;
subtree.view_tree[kRoot1A] = ViewNode{};
} else {
subtree.root = kRoot4B;
subtree.view_tree[kRoot4B] = ViewNode{};
}
first_call = false;
return subtree;
});
std::vector<ViewTreeSnapshotter::Subscriber> subscribers;
async::TestLoop loop;
std::shared_ptr<const Snapshot> snapshot1;
subscribers.push_back({.on_new_view_tree = [&snapshot1](auto snapshot) { snapshot1 = snapshot; },
.dispatcher = loop.dispatcher()});
ViewTreeSnapshotter tree(std::move(subtrees), std::move(subscribers));
tree.UpdateSnapshot();
loop.RunUntilIdle();
ASSERT_TRUE(snapshot1);
EXPECT_EQ(snapshot1->root, kRoot1A);
std::shared_ptr<const Snapshot> snapshot1_copy = snapshot1;
EXPECT_EQ(snapshot1_copy, snapshot1);
tree.UpdateSnapshot();
loop.RunUntilIdle();
EXPECT_NE(snapshot1_copy, snapshot1);
EXPECT_EQ(snapshot1->root, kRoot4B);
}
// Test that a callback queued on a subscriber thread survives the death of ViewTreeSnapshotter.
TEST(ViewTreeSnapshotterTest, SubscriberCallbackLifetime) {
std::vector<SubtreeSnapshotGenerator> subtrees;
subtrees.emplace_back([] {
SubtreeSnapshot subtree;
subtree.root = kRoot1A;
subtree.view_tree[kRoot1A] = ViewNode{};
return subtree;
});
std::vector<ViewTreeSnapshotter::Subscriber> subscribers;
async::TestLoop loop;
std::shared_ptr<const Snapshot> snapshot1;
int called_count = 0;
subscribers.push_back({.on_new_view_tree =
[&snapshot1, &called_count](auto snapshot) {
snapshot1 = snapshot;
++called_count;
},
.dispatcher = loop.dispatcher()});
auto tree = std::make_unique<ViewTreeSnapshotter>(std::move(subtrees), std::move(subscribers));
tree->UpdateSnapshot();
tree->UpdateSnapshot();
tree.reset();
EXPECT_EQ(called_count, 0);
loop.RunUntilIdle();
EXPECT_EQ(called_count, 2);
ASSERT_TRUE(snapshot1);
EXPECT_EQ(snapshot1->root, kRoot1A);
}
} // namespace view_tree::test