blob: 54b1d3946bf4d476778352a47b8cde6b0628d961 [file] [log] [blame]
// Copyright 2019 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 <fuchsia/inspect/deprecated/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/executor.h>
#include <lib/async/cpp/task.h>
#include <lib/fit/function.h>
#include <lib/gtest/test_loop_fixture.h>
#include <map>
#include <random>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/lib/callback/auto_cleanable.h"
#include "src/lib/callback/capture.h"
#include "src/lib/fxl/memory/weak_ptr.h"
#include "src/lib/inspect_deprecated/hierarchy.h"
#include "src/lib/inspect_deprecated/inspect.h"
#include "src/lib/inspect_deprecated/reader.h"
#include "src/lib/inspect_deprecated/testing/inspect.h"
namespace {
using testing::AllOf;
using testing::ElementsAre;
using testing::IsEmpty;
using testing::UnorderedElementsAre;
using namespace inspect_deprecated::testing;
fit::closure SetWhenCalled(bool* value) {
*value = false;
return [value] { *value = true; };
}
using Random = std::independent_bits_engine<std::mt19937_64, 1, bool>;
bool NextBool(Random* random) { return (*random)(); }
struct Table {
std::map<std::string, std::unique_ptr<Table>> children;
};
// |table_description| is a set of the full names of leaf elements.
Table TableFromTableDescription(const std::set<std::vector<std::string>>& table_description) {
Table table;
for (const std::vector<std::string>& leaf_full_name : table_description) {
Table* current = &table;
for (const std::string& short_name : leaf_full_name) {
auto emplacement = current->children.emplace(short_name, std::make_unique<Table>());
current = emplacement.first->second.get();
}
}
return table;
}
bool PresentInTable(const Table* table, const std::vector<std::string>& full_name) {
const Table* current = table;
for (const auto& short_name : full_name) {
const auto& it = current->children.find(short_name);
if (it == current->children.end()) {
return false;
}
current = it->second.get();
}
return true;
}
// Constructs the full names of all leaf nodes of a table of depth |depth|. The
// table has a branching factor of three and all siblings are named "a", "b",
// and "c".
std::set<std::vector<std::string>> CompleteTableDescription(int depth) {
if (depth == 1) {
return {{"a"}, {"b"}, {"c"}};
} else {
std::set<std::vector<std::string>> depth_minus_one_table = CompleteTableDescription(depth - 1);
std::set<std::vector<std::string>> table;
for (const auto& prefix : depth_minus_one_table) {
for (const auto& suffix : {"a", "b", "c"}) {
std::vector<std::string> table_entry = prefix;
table_entry.emplace_back(suffix);
table.insert(table_entry);
}
}
return table;
}
}
::testing::Matcher<const inspect_deprecated::ObjectHierarchy&> CompleteMatcher(int depth) {
if (depth == 0) {
return ChildrenMatch(IsEmpty());
} else {
return ChildrenMatch(
ElementsAre(AllOf(NodeMatches(NameMatches("a")), CompleteMatcher(depth - 1)),
AllOf(NodeMatches(NameMatches("b")), CompleteMatcher(depth - 1)),
AllOf(NodeMatches(NameMatches("c")), CompleteMatcher(depth - 1))));
}
}
class Element final : public inspect_deprecated::ChildrenManager {
public:
Element(async::TestLoop* test_loop, Random* random, Table* table,
inspect_deprecated::Node inspect_node)
: random_(random),
test_loop_(test_loop),
table_(table),
inspect_node_(std::move(inspect_node)),
children_manager_retainer_(inspect_node_.SetChildrenManager(this)),
user_serving_retention_(0),
inspect_retention_(0),
children_(test_loop->dispatcher()),
weak_factory_(this) {
children_.SetOnDiscardable([this]() { CheckDiscardable(); });
}
~Element() override {
children_.SetOnDiscardable([]() {});
};
bool IsDiscardable() const {
FXL_DCHECK(0 <= user_serving_retention_);
FXL_DCHECK(0 <= inspect_retention_);
return user_serving_retention_ == 0 && inspect_retention_ == 0 && children_.empty();
}
void SetOnDiscardable(fit::closure on_discardable) {
on_discardable_ = std::move(on_discardable);
}
void GetNames(fit::function<void(std::set<std::string>)> callback) override {
std::set<std::string> names;
for (const auto& [child_name, unused_child_unique_pointer] : table_->children) {
names.insert(child_name);
}
if (NextBool(random_)) {
async::PostTask(test_loop_->dispatcher(), [callback = std::move(callback),
names = std::move(names)]() { callback(names); });
} else {
callback(names);
}
}
void Attach(std::string name, fit::function<void(fit::closure)> callback) override {
if (table_->children.find(name) == table_->children.end()) {
if (NextBool(random_)) {
async::PostTask(test_loop_->dispatcher(),
[callback = std::move(callback)]() { callback([]() {}); });
} else {
callback([]() {});
}
return;
}
auto child = ActivateChild(name);
auto retainer = child->RetainForInspect();
if (NextBool(random_)) {
async::PostTask(test_loop_->dispatcher(),
[callback = std::move(callback), retainer = std::move(retainer)]() mutable {
callback(std::move(retainer));
});
} else {
callback(std::move(retainer));
}
}
std::pair<Element*, fit::closure> GetChild(const std::string& child_short_name) {
Element* child_ptr = ActivateChild(child_short_name);
return std::make_pair(child_ptr, child_ptr->RetainToServeUser());
}
void ActivateDescendant(std::vector<std::string> relative_descendant_name,
fit::function<void(bool, fit::closure)> callback) {
auto implementation = [this, weak_this = weak_factory_.GetWeakPtr(),
relative_descendant_name = std::move(relative_descendant_name),
callback = std::move(callback)]() mutable {
if (!weak_this) {
callback(false, []() {});
return;
}
auto child = ActivateChild(relative_descendant_name[0]);
if (relative_descendant_name.size() == 1) {
callback(true, child->RetainToServeUser());
} else {
child->ActivateDescendant(
{relative_descendant_name.begin() + 1, relative_descendant_name.end()},
std::move(callback));
}
};
if (NextBool(random_)) {
async::PostTask(test_loop_->dispatcher(), std::move(implementation));
} else {
implementation();
}
}
void DeleteDescendant(std::vector<std::string> relative_descendant_name) {
auto it = children_.find(relative_descendant_name[0]);
if (it != children_.end()) {
if (relative_descendant_name.size() == 1) {
children_.erase(it);
} else {
it->second.DeleteDescendant(
{relative_descendant_name.begin() + 1, relative_descendant_name.end()});
}
}
}
Element* DebugGetDescendant(std::vector<std::string> relative_descendant_name) {
const auto& it = children_.find(relative_descendant_name[0]);
if (it == children_.end()) {
return nullptr;
} else if (relative_descendant_name.size() == 1) {
return &it->second;
} else {
return it->second.DebugGetDescendant(
{relative_descendant_name.begin() + 1, relative_descendant_name.end()});
}
}
fit::closure RetainToServeUser() {
user_serving_retention_++;
auto weak_this = weak_factory_.GetWeakPtr();
return [this, weak_this]() {
if (weak_this) {
user_serving_retention_--;
CheckDiscardable();
}
};
}
private:
Element* ActivateChild(const std::string& child_short_name) {
auto it = children_.find(child_short_name);
if (it == children_.end()) {
inspect_deprecated::Node child_inspect_node = inspect_node_.CreateChild(child_short_name);
auto emplacement = children_.try_emplace(
child_short_name, test_loop_, random_,
table_->children.find(child_short_name)->second.get(), std::move(child_inspect_node));
return &emplacement.first->second;
} else {
return &it->second;
}
}
fit::closure RetainForInspect() {
inspect_retention_++;
return [this]() {
inspect_retention_--;
CheckDiscardable();
};
}
void CheckDiscardable() {
if (IsDiscardable() && on_discardable_) {
on_discardable_();
}
}
Random* random_;
async::TestLoop* test_loop_;
Table* table_;
inspect_deprecated::Node inspect_node_;
fit::deferred_callback children_manager_retainer_;
fit::closure on_discardable_;
int64_t user_serving_retention_;
int64_t inspect_retention_;
callback::AutoCleanableMap<std::string, Element> children_;
// Must be the last member.
fxl::WeakPtrFactory<Element> weak_factory_;
FXL_DISALLOW_COPY_AND_ASSIGN(Element);
};
enum class Activity {
ABSENT,
INACTIVE,
ACTIVE,
};
// Inspect-using application representative of those that use and that we think
// are likely to use a ChildrenManager. The application:
// (1) Maintains a frequently-changing-shape variable-depth tree of elements
// that each statically maintain an inspect_deprecated::Node.
// (2) The application is asynchronous.
class Application final {
public:
Application(async::TestLoop* loop, Random* random,
inspect_deprecated::Node* application_inspect_node,
const std::set<std::vector<std::string>>& table_description)
: loop_(loop),
random_(random),
application_inspect_node_(application_inspect_node),
table_(TableFromTableDescription(table_description)),
elements_(loop_->dispatcher()){};
// The "user interface" of the application, this method is called by the test
// when the test is acting as the application's user. If the element for
// |full_name| is not resident in memory, this method "activates" it by
// creating an in-memory Element (and thus alters the Inspect hierarchy).
// Passed to |callback| are:
// (1) A boolean "success" indicator that is representative of how in real
// applications analogs of this method can fail or time out.
// (2) A closure to call when the "user" (again, the test acting as the
// user) no longer needs the element to remain "activated".
void Activate(std::vector<std::string> full_name,
fit::function<void(bool, fit::closure)> callback) {
auto implementation = [this, full_name = std::move(full_name),
callback = std::move(callback)]() mutable {
auto& first_short_name = full_name[0];
Element* element;
const auto& it = elements_.find(first_short_name);
if (it == elements_.end()) {
inspect_deprecated::Node child_inspect_node =
application_inspect_node_->CreateChild(first_short_name);
auto emplacement = elements_.try_emplace(
first_short_name, loop_, random_, table_.children.find(first_short_name)->second.get(),
std::move(child_inspect_node));
element = &emplacement.first->second;
} else {
element = &it->second;
}
if (full_name.size() == 1) {
callback(true, element->RetainToServeUser());
} else {
element->ActivateDescendant({full_name.begin() + 1, full_name.end()}, std::move(callback));
}
};
if (NextBool(random_)) {
async::PostTask(loop_->dispatcher(), std::move(implementation));
} else {
implementation();
}
}
// The "administrator interface" of the application, this method is called
// when the test is acting as the application's owner and decides for whatever
// reason that some portion of the activated elements (the element at
// |full_name| and all elements under it) must be deleted from memory.
void Delete(const std::vector<std::string>& full_name) {
if (full_name.empty()) {
while (!elements_.empty()) {
elements_.erase(elements_.begin());
}
} else {
auto it = elements_.find(full_name[0]);
if (it != elements_.end()) {
if (full_name.size() == 1) {
elements_.erase(it);
} else {
it->second.DeleteDescendant({full_name.begin() + 1, full_name.end()});
}
}
}
}
// Called by the test acting as the test, this method describes for use in
// assertions whether an element is active at |full_name|, is inactive at
// |full_name|, or is not understood as either active or inactive.
Activity DebugGetActivity(const std::vector<std::string>& full_name) {
if (!PresentInTable(&table_, full_name)) {
return Activity::ABSENT;
} else {
auto it = elements_.find(full_name[0]);
if (it == elements_.end()) {
return Activity::INACTIVE;
} else if (full_name.size() == 1) {
return Activity::ACTIVE;
} else {
auto descendant = it->second.DebugGetDescendant({full_name.begin() + 1, full_name.end()});
return descendant == nullptr ? Activity::INACTIVE : Activity::ACTIVE;
}
}
}
private:
async::TestLoop* loop_;
Random* random_;
inspect_deprecated::Node* application_inspect_node_;
// Representative of the application's persistent data on disk, the set of
// names for which the application considers elements to exist (whether
// activated or not).
Table table_;
callback::AutoCleanableMap<std::string, Element> elements_;
};
constexpr char kTestTopLevelNodeName[] = "top-level-of-test node";
constexpr char kElementsInspectPathComponent[] = "elements";
class ChildrenManagerTest : public gtest::TestLoopFixture {
public:
ChildrenManagerTest()
: executor_(dispatcher()),
random_(std::mt19937_64(test_loop().initial_state())),
top_level_node_(inspect_deprecated::Node(kTestTopLevelNodeName)) {
elements_node_ = top_level_node_.CreateChild(kElementsInspectPathComponent);
}
protected:
::testing::AssertionResult OpenElementsNode(
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect>* elements);
::testing::AssertionResult ReadData(
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect>* node,
fuchsia::inspect::deprecated::Object* object);
::testing::AssertionResult ListChildren(
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect>* node,
std::vector<std::string>* child_names);
::testing::AssertionResult OpenChild(
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect>* parent,
const std::string& child_name,
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect>* child);
::testing::AssertionResult Activate(Application* application, std::vector<std::string> full_name,
fit::closure* retainer);
::testing::AssertionResult ReadWithReaderAPI(inspect_deprecated::ObjectHierarchy* hierarchy);
async::Executor executor_;
Random random_;
inspect_deprecated::Node top_level_node_;
inspect_deprecated::Node elements_node_;
};
::testing::AssertionResult ChildrenManagerTest::OpenElementsNode(
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect>* elements) {
bool callback_called;
bool success;
top_level_node_.object_dir().object()->OpenChild(
kElementsInspectPathComponent, elements->NewRequest(),
callback::Capture(SetWhenCalled(&callback_called), &success));
RunLoopUntilIdle();
if (!callback_called) {
return ::testing::AssertionFailure()
<< "OpenElementsNode callback passed to OpenChild not called!";
} else if (!success) {
return ::testing::AssertionFailure() << "OpenElementsNode unsuccessful!";
} else {
return ::testing::AssertionSuccess();
}
}
::testing::AssertionResult ChildrenManagerTest::ReadData(
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect>* node,
fuchsia::inspect::deprecated::Object* object) {
bool callback_called;
(*node)->ReadData(callback::Capture(SetWhenCalled(&callback_called), object));
RunLoopUntilIdle();
if (!callback_called) {
return ::testing::AssertionFailure() << "Callback passed to ReadData not called!";
} else {
return ::testing::AssertionSuccess();
}
}
::testing::AssertionResult ChildrenManagerTest::ListChildren(
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect>* node,
std::vector<std::string>* child_names) {
bool callback_called;
(*node)->ListChildren(callback::Capture(SetWhenCalled(&callback_called), child_names));
RunLoopUntilIdle();
if (!callback_called) {
return ::testing::AssertionFailure() << "Callback passed to ListChildren not called!";
} else {
return ::testing::AssertionSuccess();
}
}
::testing::AssertionResult ChildrenManagerTest::OpenChild(
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect>* parent,
const std::string& child_name,
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect>* child) {
bool callback_called;
bool success;
(*parent)->OpenChild(child_name, child->NewRequest(),
callback::Capture(SetWhenCalled(&callback_called), &success));
RunLoopUntilIdle();
if (!callback_called) {
return ::testing::AssertionFailure() << "Callback passed to OpenChild not called!";
} else if (!success) {
return ::testing::AssertionFailure() << "OpenChild unsuccessful!";
} else {
return ::testing::AssertionSuccess();
}
}
::testing::AssertionResult ChildrenManagerTest::Activate(Application* application,
std::vector<std::string> full_name,
fit::closure* retainer) {
bool callback_called;
bool success;
application->Activate(std::move(full_name),
callback::Capture(SetWhenCalled(&callback_called), &success, retainer));
RunLoopUntilIdle();
if (!callback_called) {
return ::testing::AssertionFailure() << "Callback passed to Activate not called!";
} else if (!success) {
return ::testing::AssertionFailure() << "Activate not successful!";
} else {
return ::testing::AssertionSuccess();
}
}
::testing::AssertionResult ChildrenManagerTest::ReadWithReaderAPI(
inspect_deprecated::ObjectHierarchy* hierarchy) {
bool callback_called;
bool success;
fidl::InterfaceHandle<fuchsia::inspect::deprecated::Inspect> inspect_handle;
top_level_node_.object_dir().object()->OpenChild(
kElementsInspectPathComponent, inspect_handle.NewRequest(),
callback::Capture(SetWhenCalled(&callback_called), &success));
RunLoopUntilIdle();
if (!callback_called) {
return ::testing::AssertionFailure() << "Callback passed to OpenChild not called!";
} else if (!success) {
return ::testing::AssertionFailure() << "OpenChild not successful!";
}
callback_called = false;
fit::result<inspect_deprecated::ObjectHierarchy> hierarchy_result;
auto hierarchy_promise =
inspect_deprecated::ReadFromFidl(inspect_deprecated::ObjectReader(std::move(inspect_handle)))
.then([&](fit::result<inspect_deprecated::ObjectHierarchy>& then_hierarchy_result) {
callback_called = true;
hierarchy_result = std::move(then_hierarchy_result);
});
executor_.schedule_task(std::move(hierarchy_promise));
RunLoopUntilIdle();
if (!callback_called) {
return ::testing::AssertionFailure()
<< "Callback passed to ReadFromFidl(<...>).then not called!";
} else if (!hierarchy_result.is_ok()) {
return ::testing::AssertionFailure() << "Hierarchy result not okay!";
}
*hierarchy = hierarchy_result.take_value();
return ::testing::AssertionSuccess();
}
// Verifies that a single inactive element is made active by an inspection and
// made inactive by the inspection's completion.
TEST_F(ChildrenManagerTest, SingleDynamicElement) {
std::vector<std::string> dynamic_child_full_name({"a", "b"});
auto application =
Application(&test_loop(), &random_, &elements_node_, {dynamic_child_full_name});
fit::closure a_retainer;
ASSERT_TRUE(Activate(&application, {dynamic_child_full_name[0]}, &a_retainer));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> elements_ptr;
ASSERT_TRUE(OpenElementsNode(&elements_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_ptr;
ASSERT_TRUE(OpenChild(&elements_ptr, dynamic_child_full_name[0], &a_ptr));
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_b_ptr;
ASSERT_TRUE(OpenChild(&a_ptr, dynamic_child_full_name[1], &a_b_ptr));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fuchsia::inspect::deprecated::Object object;
ASSERT_TRUE(ReadData(&a_b_ptr, &object));
ASSERT_EQ(dynamic_child_full_name[1], object.name);
a_ptr.Unbind();
RunLoopUntilIdle();
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
a_b_ptr.Unbind();
RunLoopUntilIdle();
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(dynamic_child_full_name));
}
// Verifies one-quarter of the "overlap" core use case: that the user can begin
// making use of an element, an inspection can start, the inspection can end,
// the user can release the element, and the element was active exactly as long
// as it should have been.
TEST_F(ChildrenManagerTest, SingleElementInspectInsideUse) {
std::vector<std::string> dynamic_child_full_name({"a", "b"});
auto application =
Application(&test_loop(), &random_, &elements_node_, {dynamic_child_full_name});
fit::closure a_retainer;
ASSERT_TRUE(Activate(&application, {dynamic_child_full_name[0]}, &a_retainer));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> elements_ptr;
ASSERT_TRUE(OpenElementsNode(&elements_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_ptr;
ASSERT_TRUE(OpenChild(&elements_ptr, dynamic_child_full_name[0], &a_ptr));
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fit::closure a_b_retainer;
ASSERT_TRUE(Activate(&application, dynamic_child_full_name, &a_b_retainer));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_b_ptr;
ASSERT_TRUE(OpenChild(&a_ptr, dynamic_child_full_name[1], &a_b_ptr));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fuchsia::inspect::deprecated::Object object;
ASSERT_TRUE(ReadData(&a_b_ptr, &object));
ASSERT_EQ(dynamic_child_full_name[1], object.name);
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
a_b_ptr.Unbind();
RunLoopUntilIdle();
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
a_b_retainer();
RunLoopUntilIdle();
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(dynamic_child_full_name));
}
// Verifies one-quarter of the "overlap" core use case: that an inspection can
// start, the user can start making use of an element, the inspection can end,
// the user can release the element, and the element was active exactly as long
// as it should have been.
TEST_F(ChildrenManagerTest, SingleElementInspectBeforeAndIntoUse) {
std::vector<std::string> dynamic_child_full_name({"a", "b"});
auto application =
Application(&test_loop(), &random_, &elements_node_, {dynamic_child_full_name});
fit::closure a_retainer;
ASSERT_TRUE(Activate(&application, {dynamic_child_full_name[0]}, &a_retainer));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> elements_ptr;
ASSERT_TRUE(OpenElementsNode(&elements_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_ptr;
ASSERT_TRUE(OpenChild(&elements_ptr, dynamic_child_full_name[0], &a_ptr));
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_b_ptr;
ASSERT_TRUE(OpenChild(&a_ptr, dynamic_child_full_name[1], &a_b_ptr));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fit::closure a_b_retainer;
ASSERT_TRUE(Activate(&application, dynamic_child_full_name, &a_b_retainer));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fuchsia::inspect::deprecated::Object object;
ASSERT_TRUE(ReadData(&a_b_ptr, &object));
ASSERT_EQ(dynamic_child_full_name[1], object.name);
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
a_b_ptr.Unbind();
RunLoopUntilIdle();
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
a_b_retainer();
RunLoopUntilIdle();
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(dynamic_child_full_name));
}
// Verifies one-quarter of the "overlap" core use case: that an inspection can
// start, the user can start making use of an element, the user can release the
// element, the inspection can end, and the element was active exactly as long
// as it should have been.
TEST_F(ChildrenManagerTest, SingleElementUseInsideInspect) {
std::vector<std::string> dynamic_child_full_name({"a", "b"});
auto application =
Application(&test_loop(), &random_, &elements_node_, {dynamic_child_full_name});
fit::closure a_retainer;
ASSERT_TRUE(Activate(&application, {dynamic_child_full_name[0]}, &a_retainer));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> elements_ptr;
ASSERT_TRUE(OpenElementsNode(&elements_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_ptr;
ASSERT_TRUE(OpenChild(&elements_ptr, dynamic_child_full_name[0], &a_ptr));
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_b_ptr;
ASSERT_TRUE(OpenChild(&a_ptr, dynamic_child_full_name[1], &a_b_ptr));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fit::closure a_b_retainer;
ASSERT_TRUE(Activate(&application, dynamic_child_full_name, &a_b_retainer));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
a_b_retainer();
RunLoopUntilIdle();
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fuchsia::inspect::deprecated::Object object;
ASSERT_TRUE(ReadData(&a_b_ptr, &object));
ASSERT_EQ(dynamic_child_full_name[1], object.name);
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
a_b_ptr.Unbind();
RunLoopUntilIdle();
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(dynamic_child_full_name));
}
// Verifies one-quarter of the "overlap" core use case: that the user can begin
// making use of an element, an inspection can start, the user can release the
// element, the inspection can end, and the element was active exactly as long
// as it should have been.
TEST_F(ChildrenManagerTest, SingleElementUseBeforeAndIntoInspect) {
std::vector<std::string> dynamic_child_full_name({"a", "b"});
auto application =
Application(&test_loop(), &random_, &elements_node_, {dynamic_child_full_name});
fit::closure a_retainer;
ASSERT_TRUE(Activate(&application, {dynamic_child_full_name[0]}, &a_retainer));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> elements_ptr;
ASSERT_TRUE(OpenElementsNode(&elements_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_ptr;
ASSERT_TRUE(OpenChild(&elements_ptr, dynamic_child_full_name[0], &a_ptr));
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fit::closure a_b_retainer;
ASSERT_TRUE(Activate(&application, dynamic_child_full_name, &a_b_retainer));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_b_ptr;
ASSERT_TRUE(OpenChild(&a_ptr, dynamic_child_full_name[1], &a_b_ptr));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
fuchsia::inspect::deprecated::Object object;
ASSERT_TRUE(ReadData(&a_b_ptr, &object));
ASSERT_EQ(dynamic_child_full_name[1], object.name);
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
a_b_retainer();
RunLoopUntilIdle();
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(dynamic_child_full_name));
a_b_ptr.Unbind();
RunLoopUntilIdle();
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(dynamic_child_full_name));
}
// Verifies that the application does not surrender control of the lifetimes of
// its objects: the application can delete elements in the middle of an ongoing
// inspection and the inspection completes without crashing.
TEST_F(ChildrenManagerTest, ElementsDeletedDuringInspection) {
std::vector<std::string> deepest_child_full_name({"a", "b", "c"});
auto application =
Application(&test_loop(), &random_, &elements_node_, {deepest_child_full_name});
fit::closure a_retainer;
ASSERT_TRUE(Activate(&application, {deepest_child_full_name[0]}, &a_retainer));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> elements_ptr;
ASSERT_TRUE(OpenElementsNode(&elements_ptr));
bool a_error_callback_called;
zx_status_t a_error_status;
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_ptr;
a_ptr.set_error_handler(
callback::Capture(SetWhenCalled(&a_error_callback_called), &a_error_status));
ASSERT_TRUE(OpenChild(&elements_ptr, deepest_child_full_name[0], &a_ptr));
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(deepest_child_full_name));
bool a_b_error_callback_called;
zx_status_t a_b_error_status;
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_b_ptr;
a_b_ptr.set_error_handler(
callback::Capture(SetWhenCalled(&a_b_error_callback_called), &a_b_error_status));
ASSERT_TRUE(OpenChild(&a_ptr, deepest_child_full_name[1], &a_b_ptr));
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(deepest_child_full_name));
bool a_b_c_error_callback_called;
zx_status_t a_b_c_error_status;
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_b_c_ptr;
a_b_c_ptr.set_error_handler(
callback::Capture(SetWhenCalled(&a_b_c_error_callback_called), &a_b_c_error_status));
ASSERT_TRUE(OpenChild(&a_b_ptr, deepest_child_full_name[2], &a_b_c_ptr));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(deepest_child_full_name));
fuchsia::inspect::deprecated::Object object;
ASSERT_TRUE(ReadData(&a_b_c_ptr, &object));
ASSERT_EQ(deepest_child_full_name[2], object.name);
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(deepest_child_full_name));
application.Delete({deepest_child_full_name.begin(), deepest_child_full_name.begin() + 2});
RunLoopUntilIdle();
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(deepest_child_full_name));
ASSERT_EQ(Activity::INACTIVE,
application.DebugGetActivity(
{deepest_child_full_name.begin(), deepest_child_full_name.begin() + 2}));
// TODO(crjohns, nathaniel): We would like it to be the case that when nodes
// are deleted in the middle of an inspection that the FIDL connections are
// broken and the inspection cannot continue, but... nodes being deleted in
// the middle of an inspection is enough of an edge-case that the current
// behavior of (1) not crashing and (2) reporting stale data is acceptable for
// the remainder of the FIDL implementation's life.
//
// The behavior we'd like:
// ASSERT_TRUE(a_b_c_error_callback_called);
// ASSERT_EQ(ZX_OK, a_b_c_error_status);
// ASSERT_TRUE(!a_b_c_ptr.is_bound());
// ASSERT_EQ(Activity::INACTIVE,
// application.DebugGetActivity(deepest_child_full_name));
// ASSERT_TRUE(a_b_error_callback_called);
// ASSERT_EQ(ZX_OK, a_b_error_status);
// ASSERT_TRUE(!a_b_ptr.is_bound());
// ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity(
// {deepest_child_full_name.begin(),
// deepest_child_full_name.begin() + 2}));
// ASSERT_TRUE(!a_error_callback_called);
// ASSERT_TRUE(a_ptr.is_bound());
// ASSERT_EQ(Activity::ACTIVE,
// application.DebugGetActivity({deepest_child_full_name[0]}));
//
// ASSERT_TRUE(ReadData(&a_ptr, &object));
// ASSERT_EQ(deepest_child_full_name[0], object.name);
//
// a_ptr.Unbind();
// RunLoopUntilIdle();
//
// ASSERT_EQ(Activity::INACTIVE,
// application.DebugGetActivity({deepest_child_full_name[0]}));
}
// Verifies that activation of elements can be multi-level and that active
// inspections serve to keep active only those portions of the tree of elements
// that should be kept active.
TEST_F(ChildrenManagerTest, FiveLevelsOfDynamicism) {
std::vector<std::string> deep_child_full_name({"a", "b", "c", "d", "e"});
std::vector<std::string> deeper_child_full_name({"a", "b", "c", "1", "2", "3"});
auto application = Application(&test_loop(), &random_, &elements_node_,
{deep_child_full_name, deeper_child_full_name});
fit::closure a_retainer;
ASSERT_TRUE(Activate(&application, {"a"}, &a_retainer));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> elements_ptr;
ASSERT_TRUE(OpenElementsNode(&elements_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_ptr;
ASSERT_TRUE(OpenChild(&elements_ptr, "a", &a_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> b_ptr;
ASSERT_TRUE(OpenChild(&a_ptr, "b", &b_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> c_ptr;
ASSERT_TRUE(OpenChild(&b_ptr, "c", &c_ptr));
std::vector<std::string> c_child_names;
ASSERT_TRUE(ListChildren(&c_ptr, &c_child_names));
ASSERT_THAT(c_child_names, ElementsAre("1", "d"));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> d_ptr;
ASSERT_TRUE(OpenChild(&c_ptr, "d", &d_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> e_ptr;
ASSERT_TRUE(OpenChild(&d_ptr, "e", &e_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> one_ptr;
ASSERT_TRUE(OpenChild(&c_ptr, "1", &one_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> two_ptr;
ASSERT_TRUE(OpenChild(&one_ptr, "2", &two_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> three_ptr;
ASSERT_TRUE(OpenChild(&two_ptr, "3", &three_ptr));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(deep_child_full_name));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(deeper_child_full_name));
// Dropping connections to intermediate nodes doesn't cause those intermediate
// nodes to go inactive.
a_ptr.Unbind();
b_ptr.Unbind();
c_ptr.Unbind();
d_ptr.Unbind();
one_ptr.Unbind();
two_ptr.Unbind();
RunLoopUntilIdle();
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c", "d"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c", "1"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c", "1", "2"}));
// Dropping the connection to one end of the "fork" causes the nodes on that
// "fork" to go inactive but not the nodes on the shared "stem".
three_ptr.Unbind();
RunLoopUntilIdle();
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c", "d"}));
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity({"a", "b", "c", "1"}));
ASSERT_EQ(Activity::INACTIVE, application.DebugGetActivity({"a", "b", "c", "1", "2"}));
}
// Verifies that concurrent inspections complement one another rather than
// conflict.
TEST_F(ChildrenManagerTest, ConcurrentInspections) {
std::vector<std::string> deep_child_full_name({"a", "b", "c", "d", "e"});
std::vector<std::string> deeper_child_full_name({"a", "b", "c", "1", "2", "3"});
auto application = Application(&test_loop(), &random_, &elements_node_,
{deep_child_full_name, deeper_child_full_name});
fit::closure a_retainer;
ASSERT_TRUE(Activate(&application, {"a"}, &a_retainer));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> first_elements_ptr;
ASSERT_TRUE(OpenElementsNode(&first_elements_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> second_elements_ptr;
ASSERT_TRUE(OpenElementsNode(&second_elements_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> first_a_ptr;
ASSERT_TRUE(OpenChild(&first_elements_ptr, "a", &first_a_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> first_b_ptr;
ASSERT_TRUE(OpenChild(&first_a_ptr, "b", &first_b_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> first_c_ptr;
ASSERT_TRUE(OpenChild(&first_b_ptr, "c", &first_c_ptr));
std::vector<std::string> c_child_names;
ASSERT_TRUE(ListChildren(&first_c_ptr, &c_child_names));
ASSERT_THAT(c_child_names, ElementsAre("1", "d"));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> first_d_ptr;
ASSERT_TRUE(OpenChild(&first_c_ptr, "d", &first_d_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> first_e_ptr;
ASSERT_TRUE(OpenChild(&first_d_ptr, "e", &first_e_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> first_one_ptr;
ASSERT_TRUE(OpenChild(&first_c_ptr, "1", &first_one_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> first_two_ptr;
ASSERT_TRUE(OpenChild(&first_one_ptr, "2", &first_two_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> first_three_ptr;
ASSERT_TRUE(OpenChild(&first_two_ptr, "3", &first_three_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> second_a_ptr;
ASSERT_TRUE(OpenChild(&second_elements_ptr, "a", &second_a_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> second_b_ptr;
ASSERT_TRUE(OpenChild(&second_a_ptr, "b", &second_b_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> second_c_ptr;
ASSERT_TRUE(OpenChild(&second_b_ptr, "c", &second_c_ptr));
ASSERT_TRUE(ListChildren(&second_c_ptr, &c_child_names));
ASSERT_THAT(c_child_names, ElementsAre("1", "d"));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> second_d_ptr;
ASSERT_TRUE(OpenChild(&second_c_ptr, "d", &second_d_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> second_e_ptr;
ASSERT_TRUE(OpenChild(&second_d_ptr, "e", &second_e_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> second_one_ptr;
ASSERT_TRUE(OpenChild(&second_c_ptr, "1", &second_one_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> second_two_ptr;
ASSERT_TRUE(OpenChild(&second_one_ptr, "2", &second_two_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> second_three_ptr;
ASSERT_TRUE(OpenChild(&second_two_ptr, "3", &second_three_ptr));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(deep_child_full_name));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity(deeper_child_full_name));
// Dropping connections to intermediate nodes doesn't cause those intermediate
// nodes to go inactive.
first_a_ptr.Unbind();
first_b_ptr.Unbind();
first_c_ptr.Unbind();
first_d_ptr.Unbind();
first_one_ptr.Unbind();
first_two_ptr.Unbind();
second_a_ptr.Unbind();
second_b_ptr.Unbind();
second_c_ptr.Unbind();
second_d_ptr.Unbind();
second_one_ptr.Unbind();
second_two_ptr.Unbind();
RunLoopUntilIdle();
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c", "d"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c", "1"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c", "1", "2"}));
// Dropping one but not the other the connection to each end of the "fork"
// causes no nodes to go inactive since all nodes either are or are ancestors
// of nodes that are still "under inspection".
first_three_ptr.Unbind();
second_e_ptr.Unbind();
RunLoopUntilIdle();
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c", "d"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c", "1"}));
ASSERT_EQ(Activity::ACTIVE, application.DebugGetActivity({"a", "b", "c", "1", "2"}));
}
// Verifies that the Reader API reads an only-active-at-the-first-level
// application hierarchy.
TEST_F(ChildrenManagerTest, ReaderAPIMinimalActiveElements) {
auto depth = 3;
auto application =
Application(&test_loop(), &random_, &elements_node_, CompleteTableDescription(depth));
fit::closure a_retainer;
ASSERT_TRUE(Activate(&application, {"a"}, &a_retainer));
fit::closure b_retainer;
ASSERT_TRUE(Activate(&application, {"b"}, &b_retainer));
fit::closure c_retainer;
ASSERT_TRUE(Activate(&application, {"c"}, &c_retainer));
inspect_deprecated::ObjectHierarchy hierarchy;
ASSERT_TRUE(ReadWithReaderAPI(&hierarchy));
ASSERT_THAT(hierarchy, CompleteMatcher(depth));
}
// Verifies that the Reader API reads a hierarchy with scattershot activity
// throughout.
TEST_F(ChildrenManagerTest, ReaderAPISomeInactiveElements) {
auto depth = 3;
auto application =
Application(&test_loop(), &random_, &elements_node_, CompleteTableDescription(depth));
fit::closure a_a_a_retainer;
ASSERT_TRUE(Activate(&application, {"a", "a", "a"}, &a_a_a_retainer));
fit::closure b_b_retainer;
ASSERT_TRUE(Activate(&application, {"b", "b"}, &b_b_retainer));
fit::closure c_retainer;
ASSERT_TRUE(Activate(&application, {"c"}, &c_retainer));
inspect_deprecated::ObjectHierarchy hierarchy;
ASSERT_TRUE(ReadWithReaderAPI(&hierarchy));
ASSERT_THAT(hierarchy, CompleteMatcher(depth));
}
// Verifies that the Reader API reads a hierarchy in which every element is
// active.
TEST_F(ChildrenManagerTest, ReaderAPINoInactiveElements) {
auto depth = 3;
auto leaf_full_names = CompleteTableDescription(depth);
auto application = Application(&test_loop(), &random_, &elements_node_, leaf_full_names);
std::vector<fit::closure> retainers;
for (const auto& leaf_full_name : leaf_full_names) {
fit::closure retainer;
ASSERT_TRUE(Activate(&application, leaf_full_name, &retainer));
retainers.push_back(std::move(retainer));
}
inspect_deprecated::ObjectHierarchy hierarchy;
ASSERT_TRUE(ReadWithReaderAPI(&hierarchy));
ASSERT_THAT(hierarchy, CompleteMatcher(depth));
}
// Verifies that the Reader API reads a hierarchy in which another inspection
// is already progressing.
TEST_F(ChildrenManagerTest, ReaderAPIConcurrentInspection) {
auto depth = 3;
auto application =
Application(&test_loop(), &random_, &elements_node_, CompleteTableDescription(depth));
fit::closure a_retainer;
ASSERT_TRUE(Activate(&application, {"a"}, &a_retainer));
fit::closure b_retainer;
ASSERT_TRUE(Activate(&application, {"b"}, &b_retainer));
fit::closure c_retainer;
ASSERT_TRUE(Activate(&application, {"c"}, &c_retainer));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> elements_ptr;
ASSERT_TRUE(OpenElementsNode(&elements_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_ptr;
ASSERT_TRUE(OpenChild(&elements_ptr, "a", &a_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_a_ptr;
ASSERT_TRUE(OpenChild(&a_ptr, "a", &a_a_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> a_a_a_ptr;
ASSERT_TRUE(OpenChild(&a_a_ptr, "a", &a_a_a_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> b_ptr;
ASSERT_TRUE(OpenChild(&elements_ptr, "b", &b_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> b_b_ptr;
ASSERT_TRUE(OpenChild(&b_ptr, "b", &b_b_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> c_ptr;
ASSERT_TRUE(OpenChild(&elements_ptr, "c", &c_ptr));
// And for the heck of it: keep a connection to b-a-b without keeping a
// connection to b-a:
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> b_a_ptr;
ASSERT_TRUE(OpenChild(&b_ptr, "a", &b_a_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> b_a_b_ptr;
ASSERT_TRUE(OpenChild(&b_a_ptr, "b", &b_a_b_ptr));
b_a_ptr.Unbind();
RunLoopUntilIdle();
inspect_deprecated::ObjectHierarchy hierarchy;
ASSERT_TRUE(ReadWithReaderAPI(&hierarchy));
ASSERT_THAT(hierarchy, CompleteMatcher(depth));
}
TEST_F(ChildrenManagerTest, AbsentChildDoesNotDeadlock) {
// Since we're testing an edge behavior our "representative application"
// doesn't work and we use a custom ChildrenManager.
class ChildrenManager final : public component::ChildrenManager {
public:
ChildrenManager(fit::closure on_detachment) : on_detachment_(std::move(on_detachment)) {}
private:
void GetNames(fit::function<void(std::set<std::string>)> callback) override {}
void Attach(std::string name, fit::function<void(fit::closure)> callback) override {
callback(std::move(on_detachment_));
}
fit::closure on_detachment_;
};
bool on_detachment_called = false;
ChildrenManager children_manager([this, &on_detachment_called]() {
on_detachment_called = true;
auto int_metric = elements_node_.CreateIntMetric("ignored_int_metric", 0);
});
auto children_manager_retainer = elements_node_.SetChildrenManager(&children_manager);
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> elements_ptr;
ASSERT_TRUE(OpenElementsNode(&elements_ptr));
fidl::InterfacePtr<fuchsia::inspect::deprecated::Inspect> no_such_child_ptr;
ASSERT_FALSE(OpenChild(&elements_ptr, "no_such_child", &no_such_child_ptr));
ASSERT_TRUE(on_detachment_called);
}
} // namespace