blob: 438539e57ea296ec13b0842a2879dcb8d9a54579 [file] [log] [blame]
// Copyright 2018 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/ledger/bin/app/ledger_repository_factory_impl.h"
#include <fuchsia/inspect/cpp/fidl.h>
#include <lib/callback/capture.h>
#include <lib/callback/set_when_called.h>
#include <lib/fsl/io/fd.h>
#include <lib/inspect/inspect.h>
#include <lib/inspect/reader.h>
#include <lib/inspect/testing/inspect.h>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "peridot/lib/scoped_tmpfs/scoped_tmpfs.h"
#include "src/ledger/bin/app/constants.h"
#include "src/ledger/bin/testing/test_with_environment.h"
#include "src/lib/files/directory.h"
#include "src/lib/files/unique_fd.h"
#include "src/lib/fxl/strings/string_view.h"
namespace ledger {
namespace {
using ::inspect::testing::ChildrenMatch;
using ::inspect::testing::MetricList;
using ::inspect::testing::NameMatches;
using ::inspect::testing::NodeMatches;
using ::inspect::testing::PropertyList;
using ::inspect::testing::UIntMetricIs;
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::UnorderedElementsAre;
constexpr char kTestTopLevelNodeName[] = "top-level-of-test node";
constexpr char kUserID[] = "test user ID";
class LedgerRepositoryFactoryImplTest : public TestWithEnvironment {
public:
LedgerRepositoryFactoryImplTest() {
top_level_inspect_node_ = inspect::Node(kTestTopLevelNodeName);
repository_factory_ = std::make_unique<LedgerRepositoryFactoryImpl>(
&environment_, nullptr,
top_level_inspect_node_.CreateChild(kRepositoriesInspectPathComponent));
}
~LedgerRepositoryFactoryImplTest() override {}
protected:
::testing::AssertionResult CreateDirectory(const std::string& name);
::testing::AssertionResult CallGetRepository(
const std::string& name,
ledger_internal::LedgerRepositoryPtr* ledger_repository_ptr);
scoped_tmpfs::ScopedTmpFS tmpfs_;
inspect::Node top_level_inspect_node_;
std::unique_ptr<LedgerRepositoryFactoryImpl> repository_factory_;
private:
FXL_DISALLOW_COPY_AND_ASSIGN(LedgerRepositoryFactoryImplTest);
};
::testing::AssertionResult LedgerRepositoryFactoryImplTest::CreateDirectory(
const std::string& name) {
if (!files::CreateDirectoryAt(tmpfs_.root_fd(), name)) {
return ::testing::AssertionFailure()
<< "Failed to create directory \"" << name << "\"!";
}
return ::testing::AssertionSuccess();
}
::testing::AssertionResult LedgerRepositoryFactoryImplTest::CallGetRepository(
const std::string& name,
ledger_internal::LedgerRepositoryPtr* ledger_repository_ptr) {
fxl::UniqueFD fd(openat(tmpfs_.root_fd(), name.c_str(), O_RDONLY));
if (!fd.is_valid()) {
return ::testing::AssertionFailure()
<< "Failed to validate directory \"" << name << "\"!";
}
bool callback_called;
Status status = Status::UNKNOWN_ERROR;
repository_factory_->GetRepository(
fsl::CloneChannelFromFileDescriptor(fd.get()), nullptr, kUserID,
ledger_repository_ptr->NewRequest(),
callback::Capture(callback::SetWhenCalled(&callback_called), &status));
if (!callback_called) {
return ::testing::AssertionFailure()
<< "Callback passed to GetRepository not called!";
}
if (status != Status::OK) {
return ::testing::AssertionFailure() << "Status of GetRepository call was "
<< static_cast<int32_t>(status) << "!";
}
return ::testing::AssertionSuccess();
}
TEST_F(LedgerRepositoryFactoryImplTest, InspectAPINoRepositories) {
auto hierarchy = inspect::ReadFromObject(top_level_inspect_node_);
EXPECT_THAT(
hierarchy,
AllOf(NodeMatches(AllOf(NameMatches(kTestTopLevelNodeName),
MetricList(IsEmpty()), PropertyList(IsEmpty()))),
ChildrenMatch(UnorderedElementsAre(NodeMatches(
AllOf(NameMatches(kRepositoriesInspectPathComponent)))))));
}
TEST_F(LedgerRepositoryFactoryImplTest,
InspectAPITwoRepositoriesOneAccessedTwice) {
// The directories in which the two repositories will be created.
std::string first_directory = "first directory";
std::string second_directory = "second directory";
// The names of the two repositories, determined by the
// LedgerRepositoryFactoryImpl under test.
std::string first_repository_name;
std::string second_repository_name;
// Bindings to the two repositories. If these are not maintained, the
// LedgerRepositoryFactoryImpl::LedgerRepositoryContainer objects associated
// with the repositories will be destroyed and the repositories will no
// longer appear represented in the Inspect API.
ledger_internal::LedgerRepositoryPtr first_ledger_repository_ptr;
ledger_internal::LedgerRepositoryPtr second_ledger_repository_ptr;
ledger_internal::LedgerRepositoryPtr first_again_ledger_repository_ptr;
// Create the directories for the repositories.
ASSERT_TRUE(CreateDirectory(first_directory));
ASSERT_TRUE(CreateDirectory(second_directory));
// Request one repository, then query the top_level_inspect_node_ (and its
// children) to verify that that repository is listed (and to learn the name
// under which it is listed) and that it was requested once.
ASSERT_TRUE(CallGetRepository(first_directory, &first_ledger_repository_ptr));
auto top_hierarchy = inspect::ReadFromObject(top_level_inspect_node_);
auto lone_repository_match = NodeMatches(
MetricList(Contains(UIntMetricIs(kRequestsInspectPathComponent, 1UL))));
auto first_inspection_repositories_match =
AllOf(NodeMatches(NameMatches(kRepositoriesInspectPathComponent)),
ChildrenMatch(UnorderedElementsAre(lone_repository_match)));
auto first_inspection_top_level_match =
ChildrenMatch(UnorderedElementsAre(first_inspection_repositories_match));
EXPECT_THAT(top_hierarchy, first_inspection_top_level_match);
first_repository_name =
top_hierarchy.children()[0].children()[0].node().name();
EXPECT_THAT(first_repository_name, Not(IsEmpty()));
// Request a second repository, then query the "repositories" Inspect object
// to verify that that second repository is listed in addition to the first
// (and to learn the name under which it is listed) and that the two
// repositories were each requested once.
ASSERT_TRUE(
CallGetRepository(second_directory, &second_ledger_repository_ptr));
top_hierarchy = inspect::ReadFromObject(top_level_inspect_node_);
auto second_inspection_two_repositories_match = UnorderedElementsAre(
NodeMatches(AllOf(NameMatches(first_repository_name),
MetricList(Contains(UIntMetricIs(
kRequestsInspectPathComponent, 1UL))))),
NodeMatches(MetricList(
Contains(UIntMetricIs(kRequestsInspectPathComponent, 1UL)))));
auto second_inspection_repositories_match = UnorderedElementsAre(
AllOf(NodeMatches(NameMatches(kRepositoriesInspectPathComponent)),
ChildrenMatch(second_inspection_two_repositories_match)));
auto second_inspection_top_level_match =
ChildrenMatch(second_inspection_repositories_match);
EXPECT_THAT(top_hierarchy, second_inspection_top_level_match);
second_repository_name =
find_if_not(top_hierarchy.children()[0].children().begin(),
top_hierarchy.children()[0].children().end(),
[&first_repository_name](
const inspect::ObjectHierarchy& repository_hierarchy) {
return repository_hierarchy.node().name() ==
first_repository_name;
})
->node()
.name();
EXPECT_THAT(second_repository_name, Not(IsEmpty()));
// Request the first repository a second time, then query the "repositories"
// Inspect object to verify that both repositories remain listed (with their
// same names) and are described as having been requested twice and once,
// respectively.
ASSERT_TRUE(
CallGetRepository(first_directory, &first_again_ledger_repository_ptr));
top_hierarchy = inspect::ReadFromObject(top_level_inspect_node_);
auto third_inspection_two_repositories_match = UnorderedElementsAre(
NodeMatches(AllOf(NameMatches(first_repository_name),
MetricList(Contains(UIntMetricIs(
kRequestsInspectPathComponent, 2UL))))),
NodeMatches(AllOf(NameMatches(second_repository_name),
MetricList(Contains(UIntMetricIs(
kRequestsInspectPathComponent, 1UL))))));
auto third_inspection_repositories_match = UnorderedElementsAre(
AllOf(NodeMatches(NameMatches(kRepositoriesInspectPathComponent)),
ChildrenMatch(third_inspection_two_repositories_match)));
auto third_inspection_top_level_match =
ChildrenMatch(third_inspection_repositories_match);
EXPECT_THAT(top_hierarchy, third_inspection_top_level_match);
}
TEST_F(LedgerRepositoryFactoryImplTest, CloseOnFilesystemUnavailable) {
std::unique_ptr<scoped_tmpfs::ScopedTmpFS> tmpfs =
std::make_unique<scoped_tmpfs::ScopedTmpFS>();
ledger_internal::LedgerRepositoryPtr ledger_repository_ptr;
bool get_repository_called;
Status status = Status::UNKNOWN_ERROR;
repository_factory_->GetRepository(
fsl::CloneChannelFromFileDescriptor(tmpfs->root_fd()), nullptr, "",
ledger_repository_ptr.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_repository_called),
&status));
bool channel_closed;
zx_status_t zx_status;
ledger_repository_ptr.set_error_handler(
callback::Capture(callback::SetWhenCalled(&channel_closed), &zx_status));
RunLoopUntilIdle();
EXPECT_TRUE(get_repository_called);
EXPECT_EQ(Status::OK, status);
EXPECT_FALSE(channel_closed);
tmpfs.reset();
RunLoopUntilIdle();
EXPECT_TRUE(channel_closed);
}
} // namespace
} // namespace ledger