blob: ab12364a4f85de14e2be7c7bceaa265c60aa149b [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/gl/os_compositor_tree_base.h"
#include <algorithm>
#include <vector>
#include "base/functional/function_ref.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/traced_value.h"
#include "base/values.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/overlay_layer_id.h"
namespace gl {
namespace {
// Concise alias to create a test layer ID.
gfx::OverlayLayerId LayerId(uint32_t layer_id) {
return gfx::OverlayLayerId::MakeForTesting(layer_id);
}
// A mock OS compositor tree, imitating Direct Composition or Core Animation. It
// minimally implements a DAG with a root node and basic operations to
// manipulate it. It uses `gfx::OverlayLayerId` as the node type.
class MiniTree {
public:
using node_type = gfx::OverlayLayerId;
// This contains information about accesses to the `MiniTree`. It's intended
// to aid debugging test failures.
struct Change {
enum class Type {
kCreate,
kRemoveChild,
kAddChild,
kDestroy,
kCommit,
};
Type type;
node_type node;
// For `kRemoveChild`, `kAddChild`
node_type child;
// For `kAddChild`
std::optional<node_type> below;
base::Value ToValue() const {
base::Value value(base::Value::Type::DICT);
base::Value::Dict& dict = value.GetDict();
switch (type) {
case Type::kCreate:
dict.Set("_type", "Create");
dict.Set("node", node.ToString());
break;
case Type::kRemoveChild:
dict.Set("_type", "RemoveChild");
dict.Set("parent", node.ToString());
dict.Set("child", child.ToString());
break;
case Type::kAddChild:
dict.Set("_type", "AddChild");
dict.Set("parent", node.ToString());
dict.Set("child", child.ToString());
if (below) {
dict.Set("below", below->ToString());
} else {
// Nothing is below the node, so it is added below all siblings.
}
break;
case Type::kDestroy:
dict.Set("_type", "Destroy");
dict.Set("node", node.ToString());
break;
case Type::kCommit:
dict.Set("_type", "Commit");
break;
}
return value;
}
};
MiniTree()
: root_node_(
// Insert the implicit root node.
CreateNode(gfx::OverlayLayerId())) {
// We're not interested in root node changes since they are the same for all
// trees.
changes_.clear();
nodes_touched_this_frame_.clear();
}
node_type CreateNode(const node_type& node) {
CHECK(!hierarchy_.contains(node) || hierarchy_.at(node).empty());
hierarchy_.insert({node, {}});
changes_.push_back(Change{.type = Change::Type::kCreate, .node = node});
nodes_touched_this_frame_.insert(node);
return node;
}
void RemoveChild(const node_type& parent, const node_type& child) {
CHECK(hierarchy_.contains(parent)) << "parent node must exist";
CHECK(hierarchy_.contains(child)) << "child node must exist";
auto& siblings = hierarchy_[parent];
auto it = std::ranges::find(siblings, child);
CHECK(it != siblings.end())
<< "child node must have been previously added under parent";
siblings.erase(it);
changes_.push_back(Change{
.type = Change::Type::kRemoveChild, .node = parent, .child = child});
nodes_touched_this_frame_.insert(child);
}
void AddChild(const node_type& parent,
const node_type& child,
const std::optional<node_type>& below) {
CHECK(hierarchy_.contains(parent)) << "parent node must exist";
CHECK(hierarchy_.contains(child)) << "child node must exist";
CHECK(std::ranges::none_of(hierarchy_, [&](const auto& entry) {
const auto& siblings = entry.second;
return std::ranges::find(siblings, child) != siblings.end();
})) << "child node must not be attached to any node";
auto& siblings = hierarchy_[parent];
if (below) {
auto insert_after = std::ranges::find(siblings, below.value());
CHECK(insert_after != siblings.end()) << "below node must exist";
insert_after++;
siblings.insert(insert_after, child);
} else {
siblings.insert(siblings.begin(), child);
}
changes_.push_back(Change{.type = Change::Type::kAddChild,
.node = parent,
.child = child,
.below = below});
nodes_touched_this_frame_.insert(child);
}
struct CommitStats {
std::vector<Change> changes;
base::flat_set<gfx::OverlayLayerId> nodes_touched_this_frame;
};
CommitStats Commit() {
changes_.push_back(Change{.type = Change::Type::kCommit});
// Trim the tree storage
{
base::flat_set<node_type> referenced_nodes{root_node()};
WalkChildrenRecursive(
root_node(), [&](const node_type& parent, const node_type& node) {
referenced_nodes.insert(node);
});
auto it = hierarchy_.begin();
while (it != hierarchy_.end()) {
if (!referenced_nodes.contains(it->first)) {
changes_.push_back(
Change{.type = Change::Type::kDestroy, .node = it->first});
it = hierarchy_.erase(it);
} else {
it++;
}
}
}
return {
.changes = std::move(changes_),
.nodes_touched_this_frame = std::move(nodes_touched_this_frame_),
};
}
node_type root_node() const { return root_node_; }
base::Value RootNodeToValue() const {
return NodeToValueRecursive(root_node());
}
private:
using RecursiveWalkCallback =
base::FunctionRef<void(const node_type& parent, const node_type& node)>;
void WalkChildrenRecursive(const node_type& node,
RecursiveWalkCallback callback) const {
for (const auto& child : hierarchy_.at(node)) {
callback(node, child);
WalkChildrenRecursive(child, callback);
}
}
base::Value NodeToValueRecursive(node_type node) const {
base::Value value(base::Value::Type::DICT);
base::Value::Dict& dict = value.GetDict();
dict.Set("_id", node == root_node() ? "root" : node.ToString());
if (!hierarchy_.at(node).empty()) {
base::Value children(base::Value::Type::LIST);
for (const auto& child : hierarchy_.at(node)) {
children.GetList().Append(NodeToValueRecursive(child));
}
dict.Set("children", std::move(children));
}
return value;
}
std::vector<Change> changes_;
base::flat_set<node_type> nodes_touched_this_frame_;
// Represents a N-ary tree where each node has ordered (back-to-front)
// children. We expect this to be acyclic.
base::flat_map<node_type, std::vector<node_type>> hierarchy_;
const node_type root_node_;
};
TEST(MiniTreeTest, EmptyTreesAreEqual) {
MiniTree tree1;
MiniTree tree2;
EXPECT_EQ(tree1.RootNodeToValue(), tree2.RootNodeToValue());
}
TEST(MiniTreeTest, SimpleTreesAreEqual) {
MiniTree tree1;
{
tree1.AddChild(tree1.root_node(), tree1.CreateNode(LayerId(1)), {});
tree1.AddChild(tree1.root_node(), tree1.CreateNode(LayerId(2)), {});
}
MiniTree tree2;
{
tree2.AddChild(tree2.root_node(), tree2.CreateNode(LayerId(1)), {});
tree2.AddChild(tree2.root_node(), tree2.CreateNode(LayerId(2)), {});
}
EXPECT_EQ(tree1.RootNodeToValue(), tree2.RootNodeToValue());
}
TEST(MiniTreeTest, SimpleTreesWithDifferentStructureAreNotEqual) {
MiniTree tree1;
{
tree1.AddChild(tree1.root_node(), tree1.CreateNode(LayerId(1)), {});
tree1.AddChild(tree1.root_node(), tree1.CreateNode(LayerId(2)), {});
}
MiniTree tree2;
{
tree2.AddChild(tree2.root_node(), tree2.CreateNode(LayerId(1)), {});
}
EXPECT_NE(tree1.RootNodeToValue(), tree2.RootNodeToValue());
}
TEST(MiniTreeTest, SimpleTreesWithSameStructureButDifferentContentAreNotEqual) {
MiniTree tree1;
{
tree1.AddChild(tree1.root_node(), tree1.CreateNode(LayerId(1)), {});
tree1.AddChild(tree1.root_node(), tree1.CreateNode(LayerId(2)), {});
}
MiniTree tree2;
{
tree2.AddChild(tree2.root_node(), tree2.CreateNode(LayerId(1)), {});
tree2.AddChild(tree2.root_node(), tree2.CreateNode(LayerId(3)), {});
}
EXPECT_NE(tree1.RootNodeToValue(), tree2.RootNodeToValue());
}
// A minimal overlay layer type. This is the minimum required by
// `OsCompositorTreeBase`.
struct TestOverlayParams {
TestOverlayParams() = default;
TestOverlayParams(TestOverlayParams&&) = default;
TestOverlayParams& operator=(TestOverlayParams&&) = default;
int z_order;
gfx::OverlayLayerId layer_id;
gfx::OverlayLayerId parent_layer_id;
base::Value ToValue() const {
base::Value value(base::Value::Type::DICT);
base::Value::Dict& dict = value.GetDict();
dict.Set("z_order", z_order);
dict.Set("layer_id", layer_id.ToString());
dict.Set("parent_layer_id", parent_layer_id.ToString());
return value;
}
};
// Helper functions to build test input (i.e. overlay params list) and expected
// test output (i.e. `MiniTree` generated "from scratch"). This allows test to
// be written with mostly declarative input.
class TestTreeBuilder {
public:
struct Layer {
gfx::OverlayLayerId id;
// Ordered back-to-front.
std::vector<Layer> children;
Layer() = default;
Layer(uint32_t layer_id, std::vector<Layer> child_layers)
: id(gfx::OverlayLayerId::MakeForTesting(layer_id)),
children(child_layers) {}
};
static MiniTree MiniTreeFromRootLayers(std::vector<Layer> root_layers) {
MiniTree tree;
WalkRootTopologically(root_layers,
[&](const gfx::OverlayLayerId& parent,
const std::optional<gfx::OverlayLayerId>& below,
const gfx::OverlayLayerId& id, int z_order) {
tree.AddChild(parent, tree.CreateNode(id), below);
});
return tree;
}
// Returns a topologically sorted list of overlays adding the correct z-index
// relative to siblings.
static std::vector<TestOverlayParams> OverlaysFromRootLayers(
std::vector<Layer> root_layers) {
std::vector<TestOverlayParams> overlays;
WalkRootTopologically(root_layers,
[&](const gfx::OverlayLayerId& parent,
const std::optional<gfx::OverlayLayerId>& below,
const gfx::OverlayLayerId& id, int z_order) {
overlays.emplace_back();
overlays.back().layer_id = id;
overlays.back().parent_layer_id = parent;
overlays.back().z_order = z_order;
});
return overlays;
}
private:
using TopologicalWalkCallback =
base::FunctionRef<void(const gfx::OverlayLayerId& parent,
const std::optional<gfx::OverlayLayerId>& below,
const gfx::OverlayLayerId& id,
int z_order)>;
static void WalkRootTopologically(std::vector<Layer>& root_layers,
TopologicalWalkCallback callback) {
Layer root_layer;
root_layer.id = gfx::OverlayLayerId();
root_layer.children = root_layers;
WalkTopologically(root_layer, std::move(callback));
}
static void WalkTopologically(const Layer& layer,
TopologicalWalkCallback callback) {
int z_order = 1;
std::optional<gfx::OverlayLayerId> below;
for (const Layer& child_layer : layer.children) {
callback(layer.id, below, child_layer.id, z_order++);
WalkTopologically(child_layer, callback);
below = child_layer.id;
}
}
};
// Alias to make tests less verbose.
using Layer = TestTreeBuilder::Layer;
// A minimal layer type. This correctly returns `true` from `Update` if things
// have changed.
class MiniTreeNodeWrapper {
public:
MiniTreeNodeWrapper() = default;
MiniTreeNodeWrapper(const MiniTreeNodeWrapper&) = delete;
MiniTreeNodeWrapper& operator=(const MiniTreeNodeWrapper&) = delete;
void unsafe_set_below_layer(const MiniTreeNodeWrapper* below_layer) {
below_node_ = below_layer ? below_layer->container_node() : std::nullopt;
}
bool Update(MiniTree& tree,
const gfx::OverlayLayerId& layer_id,
MiniTree::node_type parent_node,
std::optional<MiniTree::node_type> below_node) {
bool did_change = false;
std::optional<MiniTree::node_type> old_parent_node = parent_node_;
auto SetField = [&did_change](auto& field, auto& parameter) -> bool {
const bool changed = field != parameter;
if (changed) {
field = std::move(parameter);
did_change = true;
}
return changed;
};
const bool parent_node_changed = SetField(parent_node_, parent_node);
const bool below_node_changed = SetField(below_node_, below_node);
if (!container_node()) {
container_node_ = tree.CreateNode(layer_id);
}
if (parent_node_changed || below_node_changed) {
if (old_parent_node) {
tree.RemoveChild(old_parent_node.value(), container_node().value());
}
tree.AddChild(parent_node_.value(), container_node().value(),
below_node_);
}
return did_change;
}
std::optional<MiniTree::node_type> container_node() const {
return container_node_;
}
std::optional<MiniTree::node_type> parent_node() const {
return parent_node_;
}
private:
std::optional<MiniTree::node_type> container_node_;
std::optional<MiniTree::node_type> parent_node_;
std::optional<MiniTree::node_type> below_node_;
};
class TestCompositorTree
: public OsCompositorTreeBase<TestOverlayParams, MiniTreeNodeWrapper> {
public:
explicit TestCompositorTree(TestCompositorTree::UpdateMode update_mode)
: OsCompositorTreeBase(update_mode) {}
~TestCompositorTree() override = default;
const MiniTree& mini_tree() const { return mini_tree_; }
const MiniTree::CommitStats& last_frame_commit_stats() const {
return last_frame_commit_stats_;
}
protected:
bool UpdateLayer(const TestOverlayParams& overlay,
const MiniTreeNodeWrapper* parent_layer,
const MiniTreeNodeWrapper* below_layer,
MiniTreeNodeWrapper& layer) override {
// This test layer is logically a single node (not a stack of several nodes
// that apply transform, clipping, etc). We can consider the top-level
// container node to be the same node that contains the children.
const MiniTree::node_type parent_node =
parent_layer ? parent_layer->container_node().value()
: mini_tree_.root_node();
return layer.Update(
mini_tree_, overlay.layer_id, parent_node,
below_layer ? below_layer->container_node() : std::nullopt);
}
void DestroyLayer(std::unique_ptr<MiniTreeNodeWrapper> layer) override {
mini_tree_.RemoveChild(layer->parent_node().value(),
layer->container_node().value());
}
bool CommitTree() override {
last_frame_commit_stats_ = mini_tree_.Commit();
return true;
}
private:
MiniTree mini_tree_;
MiniTree::CommitStats last_frame_commit_stats_;
};
class OsCompositorTreeBaseTest
: public testing::TestWithParam<TestCompositorTree::UpdateMode> {
public:
static std::string GetParamName(
const testing::TestParamInfo<ParamType>& info) {
switch (info.param) {
case TestCompositorTree::UpdateMode::kFromScratch:
return "FromScratch";
case TestCompositorTree::UpdateMode::
kIncrementalNoPatchSiblingsOptimization:
return "IncrementalNoPatchSiblingsOptimization";
case TestCompositorTree::UpdateMode::kIncremental:
return "Incremental";
}
NOTREACHED();
}
OsCompositorTreeBaseTest() : tree_(GetParam()) {}
// This function creates an overlay list from `layers`, passes it to `tree_`
// and compares `tree_`'s incremental output with `layers`.
//
// - `layers` defines a tree where they are the children of an implicit root
// layer.
// - `expected_num_layers_modified` is the expected number of layers
// touched in the fully incremental mode. This function will adjust the
// expectation for other modes.
void UpdateTree(std::vector<Layer> layers, int expected_num_layers_modified) {
const base::Value prev_tree = tree_.mini_tree().RootNodeToValue();
std::vector<TestOverlayParams> overlays =
TestTreeBuilder::OverlaysFromRootLayers(layers);
// Dump the generated overlay candidates to help understand the input tree.
base::Value overlays_list(base::Value::Type::LIST);
for (const auto& overlay : overlays) {
overlays_list.GetList().Append(overlay.ToValue());
}
SCOPED_TRACE(base::StringPrintf("Generated overlay candidates = %s",
overlays_list.DebugString()));
EXPECT_TRUE(tree_.UpdateTree(overlays));
const base::Value actual_tree = tree_.mini_tree().RootNodeToValue();
const base::Value expected_tree =
TestTreeBuilder::MiniTreeFromRootLayers(layers).RootNodeToValue();
// Dump the previous tree and the set of changes that happened to help
// understand why.
const auto& commit_stats = tree_.last_frame_commit_stats();
base::Value changes_list(base::Value::Type::LIST);
for (const auto& change : commit_stats.changes) {
changes_list.GetList().Append(change.ToValue());
}
base::Value touched_layer_ids_list(base::Value::Type::LIST);
for (const auto& touched_layer_id : commit_stats.nodes_touched_this_frame) {
touched_layer_ids_list.GetList().Append(touched_layer_id.ToString());
}
SCOPED_TRACE(base::StringPrintf(
"Incremental update info:\n"
"Prev tree: %s\n"
"Changes from this update: %s\n"
"Layers directly touched: %s\n"
"Current tree: %s\n"
"Run with `--vmodule=os_compositor_tree_base=3` for more output.",
prev_tree.DebugString(), changes_list.DebugString(),
touched_layer_ids_list.DebugString(), actual_tree.DebugString()));
// The output tree should always be the same, regardless of how we update.
EXPECT_EQ(actual_tree, expected_tree);
switch (GetParam()) {
case TestCompositorTree::UpdateMode::kFromScratch:
EXPECT_EQ(static_cast<size_t>(
tree_.num_layers_modified_last_frame_for_testing()),
overlays.size());
break;
case TestCompositorTree::UpdateMode::
kIncrementalNoPatchSiblingsOptimization:
// We only really care about the fully optimized incremental case, but
// if the patch optimization is causing us to touch more trees, we
// should know about it.
EXPECT_GE(tree_.num_layers_modified_last_frame_for_testing(),
expected_num_layers_modified);
break;
case TestCompositorTree::UpdateMode::kIncremental:
EXPECT_EQ(tree_.num_layers_modified_last_frame_for_testing(),
expected_num_layers_modified);
break;
}
}
// Same as `UpdateTree(layers, size(layers))`. Expected to be called from the
// default empty initialized state. Intended for initializing a test state,
// but can be skipped if the empty state is desired.
void InitializeTree(std::vector<Layer> layers) {
UpdateTree(layers, TestTreeBuilder::OverlaysFromRootLayers(layers).size());
}
TestCompositorTree tree_;
};
INSTANTIATE_TEST_SUITE_P(
,
OsCompositorTreeBaseTest,
testing::ValuesIn({
TestCompositorTree::UpdateMode::kFromScratch,
TestCompositorTree::UpdateMode::kIncrementalNoPatchSiblingsOptimization,
TestCompositorTree::UpdateMode::kIncremental,
}),
&OsCompositorTreeBaseTest::GetParamName);
TEST_P(OsCompositorTreeBaseTest, CommitEmpty) {
UpdateTree({}, 0);
}
TEST_P(OsCompositorTreeBaseTest, InsertOneIntoEmpty) {
UpdateTree(
{
Layer(1, {}),
},
1);
}
TEST_P(OsCompositorTreeBaseTest, LayerNotReused) {
InitializeTree({
Layer(1, {}),
});
// We expect layer 1 to be removed (and destroyed) and a new layer 2 added.
UpdateTree(
{
Layer(2, {}),
},
2);
}
TEST_P(OsCompositorTreeBaseTest, DeleteOne) {
InitializeTree({
Layer(1, {}),
});
UpdateTree({}, 1);
}
TEST_P(OsCompositorTreeBaseTest, DeleteOneAndReAddIt) {
InitializeTree({
Layer(1, {}),
});
UpdateTree({}, 1);
UpdateTree(
{
Layer(1, {}),
},
1);
}
TEST_P(OsCompositorTreeBaseTest, DeleteTwo) {
InitializeTree({
Layer(1, {}),
Layer(2, {}),
});
UpdateTree({}, 2);
}
TEST_P(OsCompositorTreeBaseTest, DeleteLayer) {
InitializeTree({
Layer(1,
{
Layer(2, {}),
}),
});
UpdateTree({}, 2);
}
TEST_P(OsCompositorTreeBaseTest, InsertTwoIntoEmpty) {
UpdateTree(
{
Layer(1, {}),
Layer(2, {}),
},
2);
}
TEST_P(OsCompositorTreeBaseTest, InsertLayerIntoEmpty) {
UpdateTree(
{
Layer(1,
{
Layer(2, {}),
}),
},
2);
}
TEST_P(OsCompositorTreeBaseTest, SwitchOrderOfTwo) {
InitializeTree({
Layer(1, {}),
Layer(2, {}),
});
UpdateTree(
{
Layer(2, {}),
Layer(1, {}),
},
1);
}
TEST_P(OsCompositorTreeBaseTest, ThreeSwapFirstTwo) {
InitializeTree({
Layer(1, {}),
Layer(2, {}),
Layer(3, {}),
});
// Swap index 0 and 1
UpdateTree(
{
Layer(2, {}),
Layer(1, {}),
Layer(3, {}),
},
1);
}
TEST_P(OsCompositorTreeBaseTest, ThreeSwapLastTwo) {
InitializeTree({
Layer(1, {}),
Layer(2, {}),
Layer(3, {}),
});
// Swap index 1 and 2
UpdateTree(
{
Layer(1, {}),
Layer(3, {}),
Layer(2, {}),
},
1);
}
TEST_P(OsCompositorTreeBaseTest, ThreeSwapOuter) {
InitializeTree({
Layer(1, {}),
Layer(2, {}),
Layer(3, {}),
});
// Swap index 0 and 2
// This requires touching two layers.
UpdateTree(
{
Layer(3, {}),
Layer(2, {}),
Layer(1, {}),
},
2);
}
TEST_P(OsCompositorTreeBaseTest, ThreeRotateToBelow) {
InitializeTree({
Layer(1, {}),
Layer(2, {}),
Layer(3, {}),
});
UpdateTree(
{
Layer(3, {}),
Layer(1, {}),
Layer(2, {}),
},
1);
}
TEST_P(OsCompositorTreeBaseTest, ThreeRotateToAbove) {
InitializeTree({
Layer(1, {}),
Layer(2, {}),
Layer(3, {}),
});
// This requires touching two layers, due to the fact we walk the tree in
// overlay candidate order.
UpdateTree(
{
Layer(2, {}),
Layer(3, {}),
Layer(1, {}),
},
2);
}
TEST_P(OsCompositorTreeBaseTest, MoveChildToSiblingOfParent) {
InitializeTree({
Layer(1,
{
Layer(3, {}),
}),
Layer(2, {}),
});
UpdateTree(
{
Layer(1, {}),
Layer(2, {}),
Layer(3, {}),
},
1);
}
TEST_P(OsCompositorTreeBaseTest, MoveMultipleSiblingsToNewParent) {
InitializeTree({
Layer(1, {}),
Layer(2, {}),
Layer(3, {}),
});
UpdateTree(
{
Layer(1,
{
Layer(2, {}),
Layer(3, {}),
}),
},
2);
}
TEST_P(OsCompositorTreeBaseTest, ReparentSiblingsToChain) {
InitializeTree({
Layer(1, {}),
Layer(2, {}),
Layer(3, {}),
});
UpdateTree(
{
Layer(1,
{
Layer(2,
{
Layer(3, {}),
}),
}),
},
2);
}
TEST_P(OsCompositorTreeBaseTest, ReparentLayer) {
InitializeTree({
Layer(1,
{
Layer(3,
{
Layer(2, {}),
}),
}),
});
// We move layer 3 while layer 2 is a child of it. Layer 2 does not need to be
// updated in this case.
UpdateTree(
{
Layer(3,
{
Layer(2, {}),
}),
Layer(1, {}),
},
1);
}
TEST_P(OsCompositorTreeBaseTest, RemoveAndAddAtStart) {
InitializeTree({
Layer(1, {}),
Layer(2, {}),
Layer(3, {}),
});
UpdateTree(
{
Layer(10, {}),
Layer(2, {}),
Layer(3, {}),
},
2);
}
TEST_P(OsCompositorTreeBaseTest, RemoveAndAddAtMiddle) {
InitializeTree({
Layer(1, {}),
Layer(2, {}),
Layer(3, {}),
});
UpdateTree(
{
Layer(1, {}),
Layer(10, {}),
Layer(3, {}),
},
2);
}
TEST_P(OsCompositorTreeBaseTest, RemoveAndAddAtEnd) {
InitializeTree({
Layer(1, {}),
Layer(2, {}),
Layer(3, {}),
});
UpdateTree(
{
Layer(1, {}),
Layer(2, {}),
Layer(10, {}),
},
2);
}
#if EXPENSIVE_DCHECKS_ARE_ON() && defined(GTEST_HAS_DEATH_TEST)
// Check that `OsCompositorTreeBase` rejects invalid input since invalid input
// can invalidate its assumptions.
class OsCompositorTreeBaseTestInvalidInput : public OsCompositorTreeBaseTest {};
INSTANTIATE_TEST_SUITE_P(
,
OsCompositorTreeBaseTestInvalidInput,
testing::Values(TestCompositorTree::UpdateMode::kIncremental),
&OsCompositorTreeBaseTest::GetParamName);
constexpr std::string_view kZOrderCheckMessage = "Siblings must be sorted.";
constexpr std::string_view kMustHaveLayerIdMessage =
"Overlay must have non-default layer ID.";
constexpr std::string_view kHierarchyCheckMessage =
"Overlays must be topologically sorted.";
constexpr std::string_view kLayerIdUniquenessCheckMessage =
"Overlay layer IDs must all be unique.";
TEST_P(OsCompositorTreeBaseTestInvalidInput, UnsortedSiblings) {
std::vector<TestOverlayParams> overlays;
overlays.emplace_back();
overlays.back().z_order = 2;
overlays.back().layer_id = LayerId(2);
overlays.emplace_back();
overlays.back().z_order = 1;
overlays.back().layer_id = LayerId(1);
EXPECT_DEATH(tree_.UpdateTree(overlays), kZOrderCheckMessage);
}
TEST_P(OsCompositorTreeBaseTestInvalidInput, UnsortedSiblingsInLayer) {
std::vector<TestOverlayParams> overlays;
overlays.emplace_back();
overlays.back().z_order = 1;
overlays.back().layer_id = LayerId(1);
overlays.emplace_back();
overlays.back().z_order = 2;
overlays.back().layer_id = LayerId(2);
overlays.back().parent_layer_id = LayerId(1);
overlays.emplace_back();
overlays.back().z_order = 1;
overlays.back().layer_id = LayerId(3);
overlays.back().parent_layer_id = LayerId(1);
EXPECT_DEATH(tree_.UpdateTree(overlays), kZOrderCheckMessage);
}
TEST_P(OsCompositorTreeBaseTestInvalidInput, NoSameZOrder) {
std::vector<TestOverlayParams> overlays;
overlays.emplace_back();
overlays.back().z_order = 1;
overlays.back().layer_id = LayerId(1);
overlays.emplace_back();
overlays.back().z_order = 1;
overlays.back().layer_id = LayerId(2);
EXPECT_DEATH(tree_.UpdateTree(overlays), kZOrderCheckMessage);
}
TEST_P(OsCompositorTreeBaseTestInvalidInput, NotTopologicallySorted) {
std::vector<TestOverlayParams> overlays;
overlays.emplace_back();
overlays.back().z_order = 1;
overlays.back().layer_id = LayerId(2);
overlays.back().parent_layer_id = LayerId(1),
overlays.emplace_back();
overlays.back().z_order = 1;
overlays.back().layer_id = LayerId(1);
EXPECT_DEATH(tree_.UpdateTree(overlays), kHierarchyCheckMessage);
}
TEST_P(OsCompositorTreeBaseTestInvalidInput, NeedsExplicitLayerId) {
std::vector<TestOverlayParams> overlays;
overlays.emplace_back();
overlays.back().z_order = 1;
EXPECT_DEATH(tree_.UpdateTree(overlays), kMustHaveLayerIdMessage);
}
TEST_P(OsCompositorTreeBaseTestInvalidInput, NoDuplicateLayerId) {
std::vector<TestOverlayParams> overlays;
overlays.emplace_back();
overlays.back().z_order = 1;
overlays.back().layer_id = LayerId(1);
overlays.emplace_back();
overlays.back().z_order = 2;
overlays.back().layer_id = LayerId(1);
EXPECT_DEATH(tree_.UpdateTree(overlays), kLayerIdUniquenessCheckMessage);
}
#endif
class MockTestCompositorTree : public TestCompositorTree {
public:
MockTestCompositorTree()
: TestCompositorTree(TestCompositorTree::UpdateMode::kIncremental) {}
MOCK_METHOD(bool, CommitTree, (), (override));
};
TEST(OsCompositorTreeBaseTestCommit, NoUpdatesSkipCommit) {
MockTestCompositorTree tree;
EXPECT_CALL(tree, CommitTree()).Times(0);
EXPECT_TRUE(tree.UpdateTree({}));
}
TEST(OsCompositorTreeBaseTestCommit, Commit) {
MockTestCompositorTree tree;
EXPECT_CALL(tree, CommitTree()).WillOnce(testing::Return(true));
const auto overlays = TestTreeBuilder::OverlaysFromRootLayers({
Layer(1, {}),
});
EXPECT_TRUE(tree.UpdateTree(overlays));
}
TEST(OsCompositorTreeBaseTestCommit, NoUpdatesOnSubsequentFrameSkipCommit) {
MockTestCompositorTree tree;
{
EXPECT_CALL(tree, CommitTree()).WillOnce(testing::Return(true));
const auto overlays = TestTreeBuilder::OverlaysFromRootLayers({
Layer(1, {}),
});
EXPECT_TRUE(tree.UpdateTree(overlays));
}
{
EXPECT_CALL(tree, CommitTree()).Times(0);
const auto overlays = TestTreeBuilder::OverlaysFromRootLayers({
Layer(1, {}),
});
EXPECT_TRUE(tree.UpdateTree(overlays));
}
}
TEST(OsCompositorTreeBaseTestCommit, FailedCommit) {
MockTestCompositorTree tree;
EXPECT_CALL(tree, CommitTree()).WillOnce(testing::Return(false));
const auto overlays = TestTreeBuilder::OverlaysFromRootLayers({
Layer(1, {}),
});
EXPECT_FALSE(tree.UpdateTree(overlays));
}
} // namespace
} // namespace gl