blob: 9ac4a1f99cdd51ff59bf1f2f847bac59abe06614 [file] [log] [blame]
// Copyright 2020 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/developer/forensics/crash_reports/crash_register.h"
#include <fuchsia/feedback/cpp/fidl.h>
#include <lib/async/cpp/executor.h>
#include <lib/fpromise/promise.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <memory>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/developer/forensics/crash_reports/info/info_context.h"
#include "src/developer/forensics/crash_reports/product.h"
#include "src/developer/forensics/feedback/annotations/constants.h"
#include "src/developer/forensics/testing/unit_test_fixture.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
#include "src/lib/files/scoped_temp_dir.h"
#include "src/lib/timekeeper/test_clock.h"
namespace forensics {
namespace crash_reports {
namespace {
using fuchsia::feedback::CrashReportingProduct;
using inspect::testing::ChildrenMatch;
using inspect::testing::NameMatches;
using inspect::testing::NodeMatches;
using inspect::testing::PropertyList;
using inspect::testing::StringIs;
using testing::Not;
using testing::UnorderedElementsAreArray;
constexpr char kComponentUrl[] = "fuchsia-pkg://fuchsia.com/my-pkg#meta/my-component.cmx";
// Unit-tests the server of fuchsia.feedback.CrashReportingProductRegister.
//
// This does not test the environment service. It directly instantiates the class, without
// connecting through FIDL.
class CrashRegisterTest : public UnitTestFixture {
public:
CrashRegisterTest()
: info_context_(
std::make_shared<InfoContext>(&InspectRoot(), &clock_, dispatcher(), services())) {}
void SetUp() override { MakeNewCrashRegister(); }
protected:
void Upsert(const std::string& component_url, CrashReportingProduct product) {
crash_register_->Upsert(component_url, std::move(product));
}
void Upsert(const std::string& component_url, CrashReportingProduct product,
CrashRegister::UpsertWithAckCallback callback) {
crash_register_->UpsertWithAck(component_url, std::move(product), std::move(callback));
}
bool HasProduct(const std::string& program_name) const {
return crash_register_->HasProduct(program_name);
}
Product GetProduct(const std::string& program_name) const {
return crash_register_->GetProduct(program_name);
}
std::string RegisterJsonPath() { return files::JoinPath(tmp_dir_.path(), "register.json"); }
std::string ReadRegisterJson() {
std::string json;
files::ReadFileToString(RegisterJsonPath(), &json);
return json;
}
void MakeNewCrashRegister() {
crash_register_ = std::make_unique<CrashRegister>(info_context_, RegisterJsonPath());
}
private:
timekeeper::TestClock clock_;
files::ScopedTempDir tmp_dir_;
std::shared_ptr<InfoContext> info_context_;
std::unique_ptr<CrashRegister> crash_register_;
};
TEST_F(CrashRegisterTest, Upsert_Basic) {
CrashReportingProduct product;
product.set_name("some name");
product.set_version("some version");
product.set_channel("some channel");
Upsert(kComponentUrl, std::move(product));
EXPECT_THAT(InspectTree(), ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("crash_register")),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("mappings")),
ChildrenMatch(UnorderedElementsAreArray({
NodeMatches(AllOf(NameMatches(kComponentUrl),
PropertyList(UnorderedElementsAreArray({
StringIs("name", "some name"),
StringIs("version", "some version"),
StringIs("channel", "some channel"),
})))),
})))))))));
EXPECT_EQ(ReadRegisterJson(), R"({
"fuchsia-pkg://fuchsia.com/my-pkg#meta/my-component.cmx": {
"name": "some name",
"version": "some version",
"channel": "some channel"
}
})");
}
TEST_F(CrashRegisterTest, UpsertWithAck_Basic) {
CrashReportingProduct product;
product.set_name("some name");
product.set_version("some version");
product.set_channel("some channel");
bool success{false};
Upsert(kComponentUrl, std::move(product), [&] { success = true; });
ASSERT_TRUE(success);
EXPECT_THAT(InspectTree(), ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("crash_register")),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("mappings")),
ChildrenMatch(UnorderedElementsAreArray({
NodeMatches(AllOf(NameMatches(kComponentUrl),
PropertyList(UnorderedElementsAreArray({
StringIs("name", "some name"),
StringIs("version", "some version"),
StringIs("channel", "some channel"),
})))),
})))))))));
EXPECT_EQ(ReadRegisterJson(), R"({
"fuchsia-pkg://fuchsia.com/my-pkg#meta/my-component.cmx": {
"name": "some name",
"version": "some version",
"channel": "some channel"
}
})");
}
TEST_F(CrashRegisterTest, Upsert_NoInsertOnMissingProductName) {
CrashReportingProduct product;
product.set_version("some version");
product.set_channel("some channel");
Upsert(kComponentUrl, std::move(product));
EXPECT_THAT(InspectTree(),
ChildrenMatch(Not(Contains(NodeMatches(NameMatches("crash_register"))))));
EXPECT_TRUE(ReadRegisterJson().empty());
}
TEST_F(CrashRegisterTest, Upsert_UpdateIfSameComponentUrl) {
CrashReportingProduct product;
product.set_name("some name");
product.set_version("some version");
product.set_channel("some channel");
Upsert(kComponentUrl, std::move(product));
EXPECT_THAT(InspectTree(), ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("crash_register")),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("mappings")),
ChildrenMatch(UnorderedElementsAreArray({
NodeMatches(AllOf(NameMatches(kComponentUrl),
PropertyList(UnorderedElementsAreArray({
StringIs("name", "some name"),
StringIs("version", "some version"),
StringIs("channel", "some channel"),
})))),
})))))))));
CrashReportingProduct another_product;
another_product.set_name("some other name");
another_product.set_version("some other version");
another_product.set_channel("some other channel");
Upsert(kComponentUrl, std::move(another_product));
EXPECT_THAT(InspectTree(),
ChildrenMatch(Contains(
AllOf(NodeMatches(NameMatches("crash_register")),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("mappings")),
ChildrenMatch(UnorderedElementsAreArray({
NodeMatches(AllOf(NameMatches(kComponentUrl),
PropertyList(UnorderedElementsAreArray({
StringIs("name", "some other name"),
StringIs("version", "some other version"),
StringIs("channel", "some other channel"),
})))),
})))))))));
EXPECT_EQ(ReadRegisterJson(), R"({
"fuchsia-pkg://fuchsia.com/my-pkg#meta/my-component.cmx": {
"name": "some other name",
"version": "some other version",
"channel": "some other channel"
}
})");
}
TEST_F(CrashRegisterTest, GetProduct_NoUpsert) {
EXPECT_FALSE(HasProduct("some program name"));
EXPECT_TRUE(ReadRegisterJson().empty());
}
TEST_F(CrashRegisterTest, GetProduct_FromUpsert) {
CrashReportingProduct product;
product.set_name("some name");
product.set_version("some version");
product.set_channel("some channel");
Upsert(kComponentUrl, std::move(product));
const auto expected = Product{
.name = "some name",
.version = std::string("some version"),
.channel = std::string("some channel"),
};
EXPECT_THAT(GetProduct(kComponentUrl), expected);
EXPECT_EQ(ReadRegisterJson(), R"({
"fuchsia-pkg://fuchsia.com/my-pkg#meta/my-component.cmx": {
"name": "some name",
"version": "some version",
"channel": "some channel"
}
})");
}
TEST_F(CrashRegisterTest, GetProduct_DifferentUpsert) {
CrashReportingProduct product;
product.set_name("some name");
product.set_version("some version");
product.set_channel("some channel");
Upsert(kComponentUrl, std::move(product));
EXPECT_FALSE(HasProduct("some program name"));
EXPECT_EQ(ReadRegisterJson(), R"({
"fuchsia-pkg://fuchsia.com/my-pkg#meta/my-component.cmx": {
"name": "some name",
"version": "some version",
"channel": "some channel"
}
})");
}
TEST_F(CrashRegisterTest, BuildDefaultProduct) {
{
Product actual{
.name = std::string("Fuchsia"),
.version = Error::kMissingValue,
.channel = Error::kMissingValue,
};
CrashRegister::AddVersionAndChannel(actual, {});
const Product expected{
.name = std::string("Fuchsia"),
.version = Error::kMissingValue,
.channel = Error::kMissingValue,
};
EXPECT_EQ(actual, expected);
}
{
Product actual{
.name = std::string("Fuchsia"),
.version = Error::kMissingValue,
.channel = Error::kMissingValue,
};
CrashRegister::AddVersionAndChannel(actual, {
{feedback::kBuildVersionKey, "some version"},
});
const Product expected{
.name = std::string("Fuchsia"),
.version = std::string("some version"),
.channel = Error::kMissingValue,
};
EXPECT_EQ(actual, expected);
}
{
Product actual{
.name = std::string("Fuchsia"),
.version = Error::kMissingValue,
.channel = Error::kMissingValue,
};
CrashRegister::AddVersionAndChannel(
actual, {
{feedback::kBuildVersionKey, "some version"},
{feedback::kSystemUpdateChannelCurrentKey, "some channel"},
});
const Product expected{
.name = std::string("Fuchsia"),
.version = std::string("some version"),
.channel = std::string("some channel"),
};
EXPECT_EQ(actual, expected);
}
}
TEST_F(CrashRegisterTest, ReinitializesFromJson) {
constexpr char kOtherComponentUrl[] =
"fuchsia-pkg://fuchsia.com/my-other-pkg#meta/my-other-component.cmx";
CrashReportingProduct product;
product.set_name("some name");
product.set_version("some version");
product.set_channel("some channel");
Upsert(kComponentUrl, std::move(product));
CrashReportingProduct another_product;
another_product.set_name("some other name");
another_product.set_version("some other version");
another_product.set_channel("some other channel");
Upsert(kComponentUrl, std::move(another_product));
CrashReportingProduct yet_another_product;
yet_another_product.set_name("yet another name");
yet_another_product.set_version("yet another version");
Upsert(kOtherComponentUrl, std::move(yet_another_product));
MakeNewCrashRegister();
auto expected = Product{
.name = "some other name",
.version = std::string("some other version"),
.channel = std::string("some other channel"),
};
EXPECT_THAT(GetProduct(kComponentUrl), expected);
expected = Product{
.name = "yet another name",
.version = std::string("yet another version"),
.channel = Error::kMissingValue,
};
EXPECT_THAT(GetProduct(kOtherComponentUrl), expected);
}
} // namespace
} // namespace crash_reports
} // namespace forensics