blob: 43846d98c8b47545e82b75fb4ddbb24104609173 [file] [log] [blame]
// Copyright 2019 The Chromium 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 "components/performance_manager/public/graph/node_attached_data.h"
#include <utility>
#include "base/test/gtest_util.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/graph/graph_impl.h"
#include "components/performance_manager/graph/node_attached_data_impl.h"
#include "components/performance_manager/graph/node_base.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/graph/system_node_impl.h"
#include "components/performance_manager/public/graph/node.h"
#include "components/performance_manager/test_support/graph_test_harness.h"
#include "components/performance_manager/test_support/mock_graphs.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace performance_manager {
namespace {
// Note that a FooData basically has pointer size, because its an empty class
// with a single vtable pointer.
constexpr size_t kFooDataSize = sizeof(uintptr_t);
// A dummy node type so that we can exercise node attached storage code paths.
class DummyNode : public NodeBase, public Node {
public:
explicit DummyNode(GraphImpl* graph)
: NodeBase(NodeTypeEnum::kInvalidType, graph) {}
~DummyNode() override = default;
// NodeBase implementation:
const Node* ToNode() const override { return static_cast<const Node*>(this); }
// Node implementation:
Graph* GetGraph() const override { return graph(); }
uintptr_t GetImplType() const override {
static const uintptr_t kImplType = reinterpret_cast<uintptr_t>(&kImplType);
return kImplType;
}
const void* GetImpl() const override {
return static_cast<const NodeBase*>(this);
}
static constexpr NodeTypeEnum Type() { return NodeTypeEnum::kInvalidType; }
// Internal storage for DummyData and FooData types. These would normally be
// protected and the data classes friended, but we also want to access these
// in the tests.
std::unique_ptr<NodeAttachedData> dummy_data_;
InternalNodeAttachedDataStorage<kFooDataSize> foo_data_;
private:
DISALLOW_COPY_AND_ASSIGN(DummyNode);
};
// A NodeAttachedData class that can only be attached to page and process nodes
// using the map, and to DummyNodes using node owned storage.
class DummyData : public NodeAttachedDataImpl<DummyData> {
public:
struct Traits : public NodeAttachedDataInMap<PageNodeImpl>,
public NodeAttachedDataInMap<ProcessNodeImpl>,
public NodeAttachedDataOwnedByNodeType<DummyNode> {};
DummyData() = default;
explicit DummyData(const PageNodeImpl* page_node) {}
explicit DummyData(const ProcessNodeImpl* process_node) {}
explicit DummyData(const DummyNode* dummy_node) {}
~DummyData() override = default;
// Provides access to storage on DummyNodes.
static std::unique_ptr<NodeAttachedData>* GetUniquePtrStorage(
DummyNode* dummy_node) {
return &dummy_node->dummy_data_;
}
private:
DISALLOW_COPY_AND_ASSIGN(DummyData);
};
// Another NodeAttachedData class that can only be attached to page nodes in the
// map, and to DummyNodes using node internal storage.
class FooData : public NodeAttachedDataImpl<FooData> {
public:
struct Traits : public NodeAttachedDataInMap<PageNodeImpl>,
public NodeAttachedDataInternalOnNodeType<DummyNode> {};
FooData() = default;
explicit FooData(const PageNodeImpl* page_node) {}
explicit FooData(const DummyNode* dummy_node) {}
~FooData() override = default;
// Provides access to storage on DummyNodes.
static InternalNodeAttachedDataStorage<kFooDataSize>* GetInternalStorage(
DummyNode* dummy_node) {
return &dummy_node->foo_data_;
}
private:
DISALLOW_COPY_AND_ASSIGN(FooData);
};
// An implementation of map-stored user-data using the public interface.
class BarData : public ExternalNodeAttachedDataImpl<BarData> {
public:
explicit BarData(const PageNode* page_node) : page_node_(page_node) {}
~BarData() override = default;
const PageNode* page_node_ = nullptr;
};
} // namespace
class NodeAttachedDataTest : public GraphTestHarness {
public:
NodeAttachedDataTest() = default;
~NodeAttachedDataTest() override = default;
static void AttachInMap(const Node* node,
std::unique_ptr<NodeAttachedData> data) {
return NodeAttachedDataMapHelper::AttachInMap(node, std::move(data));
}
// Retrieves the data associated with the provided |node| and |key|. This
// returns nullptr if no data exists.
static NodeAttachedData* GetFromMap(const Node* node, const void* key) {
return NodeAttachedDataMapHelper::GetFromMap(node, key);
}
// Detaches the data associated with the provided |node| and |key|. It is
// harmless to call this when no data exists.
static std::unique_ptr<NodeAttachedData> DetachFromMap(const Node* node,
const void* key) {
return NodeAttachedDataMapHelper::DetachFromMap(node, key);
}
};
TEST_F(NodeAttachedDataTest, UserDataKey) {
std::unique_ptr<NodeAttachedData> data = std::make_unique<DummyData>();
EXPECT_EQ(data->GetKey(), DummyData::UserDataKey());
}
TEST_F(NodeAttachedDataTest, RawAttachDetach) {
MockSinglePageInSingleProcessGraph mock_graph(graph());
auto* page_node = mock_graph.page.get();
static constexpr NodeAttachedData* kNull = nullptr;
std::unique_ptr<DummyData> data = std::make_unique<DummyData>();
auto* raw_data = data.get();
auto* raw_base = static_cast<NodeAttachedData*>(raw_data);
// No data yet exists.
EXPECT_EQ(0u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
EXPECT_EQ(kNull, GetFromMap(page_node, raw_data->GetKey()));
// Attach the data and look it up again.
AttachInMap(page_node, std::move(data));
EXPECT_EQ(1u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
EXPECT_EQ(raw_base, GetFromMap(page_node, raw_data->GetKey()));
// Detach the data.
std::unique_ptr<NodeAttachedData> base_data =
DetachFromMap(page_node, raw_data->GetKey());
EXPECT_EQ(0u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
EXPECT_EQ(raw_base, base_data.get());
EXPECT_EQ(kNull, GetFromMap(page_node, raw_data->GetKey()));
}
TEST_F(NodeAttachedDataTest, TypedAttachDetach) {
MockSinglePageInSingleProcessGraph mock_graph(graph());
auto* page_node = mock_graph.page.get();
static constexpr DummyData* kNull = nullptr;
// Lookup non-existent data.
EXPECT_EQ(0u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
EXPECT_EQ(kNull, DummyData::Get(page_node));
// Create the data and look it up again.
auto* data = DummyData::GetOrCreate(page_node);
EXPECT_EQ(1u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
EXPECT_NE(kNull, data);
EXPECT_EQ(data, DummyData::Get(page_node));
// Destroy the data and expect it to no longer exist.
EXPECT_TRUE(DummyData::Destroy(page_node));
EXPECT_EQ(0u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
EXPECT_EQ(kNull, DummyData::Get(page_node));
}
TEST_F(NodeAttachedDataTest, NodeDeathDestroysData) {
MockSinglePageInSingleProcessGraph mock_graph(graph());
auto* page_node = mock_graph.page.get();
ProcessNodeImpl* proc_node = mock_graph.process.get();
EXPECT_EQ(0u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
EXPECT_EQ(0u, graph()->GetNodeAttachedDataCountForTesting(
page_node, DummyData::UserDataKey()));
EXPECT_EQ(0u, graph()->GetNodeAttachedDataCountForTesting(
proc_node, DummyData::UserDataKey()));
// Add data to both the process and the page.
DummyData::GetOrCreate(page_node);
FooData::GetOrCreate(page_node);
auto* proc_data = DummyData::GetOrCreate(proc_node);
EXPECT_EQ(3u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
EXPECT_EQ(2u,
graph()->GetNodeAttachedDataCountForTesting(page_node, nullptr));
EXPECT_EQ(1u, graph()->GetNodeAttachedDataCountForTesting(
proc_node, DummyData::UserDataKey()));
// Release the page node and expect the node attached data to have been
// cleaned up.
mock_graph.frame.reset();
mock_graph.page.reset();
EXPECT_EQ(1u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
EXPECT_EQ(0u,
graph()->GetNodeAttachedDataCountForTesting(page_node, nullptr));
EXPECT_EQ(1u, graph()->GetNodeAttachedDataCountForTesting(
proc_node, DummyData::UserDataKey()));
// The process data should still exist.
EXPECT_EQ(proc_data, DummyData::Get(proc_node));
}
TEST_F(NodeAttachedDataTest, NodeOwnedStorage) {
DummyNode dummy_node(graph());
// The storage in the dummy node should not be initialized.
EXPECT_FALSE(dummy_node.dummy_data_.get());
EXPECT_FALSE(DummyData::Get(&dummy_node));
// Creating a DummyData on the DummyNode should not create storage in the map.
EXPECT_EQ(0u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
DummyData* dummy_data = DummyData::GetOrCreate(&dummy_node);
NodeAttachedData* nad = static_cast<NodeAttachedData*>(dummy_data);
EXPECT_EQ(0u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
// The storage in the dummy node should now be initialized.
EXPECT_EQ(nad, dummy_node.dummy_data_.get());
EXPECT_EQ(dummy_data, DummyData::Get(&dummy_node));
// Destroying the data should free up the storage.
DummyData::Destroy(&dummy_node);
EXPECT_FALSE(dummy_node.dummy_data_.get());
EXPECT_FALSE(DummyData::Get(&dummy_node));
}
TEST_F(NodeAttachedDataTest, NodeInternalStorage) {
DummyNode dummy_node(graph());
// The storage in the dummy node should not be initialized.
EXPECT_FALSE(dummy_node.foo_data_.Get());
EXPECT_FALSE(FooData::Get(&dummy_node));
// Creating a FooData on the DummyNode should not create storage in the map.
EXPECT_EQ(0u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
FooData* foo_data = FooData::GetOrCreate(&dummy_node);
NodeAttachedData* nad = static_cast<NodeAttachedData*>(foo_data);
EXPECT_EQ(0u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
// The storage in the dummy node should now be initialized.
EXPECT_EQ(nad, dummy_node.foo_data_.Get());
EXPECT_EQ(foo_data, FooData::Get(&dummy_node));
// Destroying the data should free up the storage.
FooData::Destroy(&dummy_node);
EXPECT_FALSE(dummy_node.foo_data_.Get());
EXPECT_FALSE(FooData::Get(&dummy_node));
}
TEST_F(NodeAttachedDataTest, PublicNodeAttachedData) {
MockSinglePageInSingleProcessGraph mock_graph(graph());
PageNodeImpl* page_node_impl = mock_graph.page.get();
const PageNode* page_node = page_node_impl;
EXPECT_EQ(0u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
BarData* bar_data = BarData::Get(page_node);
EXPECT_FALSE(bar_data);
bar_data = BarData::GetOrCreate(page_node);
EXPECT_TRUE(bar_data);
EXPECT_EQ(1u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
EXPECT_EQ(1u, graph()->GetNodeAttachedDataCountForTesting(
page_node_impl, bar_data->GetKey()));
EXPECT_EQ(page_node, bar_data->page_node_);
EXPECT_EQ(bar_data, BarData::Get(page_node));
EXPECT_TRUE(BarData::Destroy(page_node));
EXPECT_EQ(0u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
EXPECT_FALSE(BarData::Destroy(page_node));
EXPECT_FALSE(BarData::Get(page_node));
}
} // namespace performance_manager