blob: a7d51cb967724a3d818c75456b25b04715252300 [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 "src/developer/forensics/crash_reports/queue.h"
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <memory>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/developer/forensics/crash_reports/constants.h"
#include "src/developer/forensics/crash_reports/info/info_context.h"
#include "src/developer/forensics/crash_reports/network_watcher.h"
#include "src/developer/forensics/crash_reports/reporting_policy_watcher.h"
#include "src/developer/forensics/crash_reports/tests/stub_crash_server.h"
#include "src/developer/forensics/testing/stubs/cobalt_logger_factory.h"
#include "src/developer/forensics/testing/stubs/network_reachability_provider.h"
#include "src/developer/forensics/testing/unit_test_fixture.h"
#include "src/developer/forensics/utils/cobalt/metrics.h"
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
#include "src/lib/files/scoped_temp_dir.h"
#include "src/lib/fsl/vmo/strings.h"
#include "src/lib/fxl/strings/string_printf.h"
#include "src/lib/timekeeper/test_clock.h"
namespace forensics {
namespace crash_reports {
namespace {
using testing::Contains;
using testing::ElementsAre;
using testing::IsEmpty;
using testing::IsSupersetOf;
using testing::Not;
using testing::Pair;
using testing::UnorderedElementsAre;
using testing::UnorderedElementsAreArray;
constexpr CrashServer::UploadStatus kUploadSuccessful = CrashServer::UploadStatus::kSuccess;
constexpr CrashServer::UploadStatus kUploadFailed = CrashServer::UploadStatus::kFailure;
constexpr CrashServer::UploadStatus kUploadThrottled = CrashServer::UploadStatus::kThrottled;
constexpr char kAttachmentKey[] = "attachment.key";
constexpr char kAttachmentValue[] = "attachment.value";
constexpr char kAnnotationKey[] = "annotation.key";
constexpr char kAnnotationValue[] = "annotation.value";
constexpr char kSnapshotUuidValue[] = "snapshot_uuid";
constexpr char kMinidumpKey[] = "uploadFileMinidump";
constexpr char kMinidumpValue[] = "minidump";
constexpr zx::duration kPeriodicUploadDuration = zx::min(15);
fuchsia::mem::Buffer BuildAttachment(const std::string& value) {
fuchsia::mem::Buffer attachment;
FX_CHECK(fsl::VmoFromString(value, &attachment));
return attachment;
}
std::map<std::string, fuchsia::mem::Buffer> MakeAttachments() {
std::map<std::string, fuchsia::mem::Buffer> attachments;
attachments[kAttachmentKey] = BuildAttachment(kAttachmentValue);
return attachments;
}
std::optional<std::string> DeleteReportFromStore() {
auto RemoveCurDir = [](std::vector<std::string>* contents) {
contents->erase(std::remove(contents->begin(), contents->end(), "."), contents->end());
};
std::vector<std::string> program_shortnames;
files::ReadDirContents(kStoreCachePath, &program_shortnames);
RemoveCurDir(&program_shortnames);
for (const auto& program_shortname : program_shortnames) {
const std::string path = files::JoinPath(kStoreCachePath, program_shortname);
std::vector<std::string> report_ids;
files::ReadDirContents(path, &report_ids);
RemoveCurDir(&report_ids);
if (!report_ids.empty()) {
files::DeletePath(files::JoinPath(path, report_ids.back()), /*recursive=*/true);
return report_ids.back();
}
}
return std::nullopt;
}
AnnotationMap MakeAnnotations() { return {{kAnnotationKey, kAnnotationValue}}; }
Report MakeReport(const std::size_t report_id) {
std::optional<Report> report =
Report::MakeReport(report_id, fxl::StringPrintf("program_%ld", report_id), MakeAnnotations(),
MakeAttachments(), kSnapshotUuidValue, BuildAttachment(kMinidumpValue));
FX_CHECK(report.has_value());
return std::move(report.value());
}
Report MakeHourlyReport(const std::size_t report_id) {
std::optional<Report> report = Report::MakeReport(
report_id, kHourlySnapshotProgramName, MakeAnnotations(), MakeAttachments(),
kSnapshotUuidValue, BuildAttachment(kMinidumpValue), /*is_hourly_report=*/true);
FX_CHECK(report.has_value());
return std::move(report.value());
}
class TestReportingPolicyWatcher : public ReportingPolicyWatcher {
public:
TestReportingPolicyWatcher() : ReportingPolicyWatcher(ReportingPolicy::kUndecided) {}
void Set(const ReportingPolicy policy) { SetPolicy(policy); }
};
class QueueTest : public UnitTestFixture {
public:
QueueTest() : network_watcher_(dispatcher(), services()) {}
void SetUp() override {
info_context_ =
std::make_shared<InfoContext>(&InspectRoot(), &clock_, dispatcher(), services());
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
SetUpNetworkReachabilityProvider();
RunLoopUntilIdle();
}
void TearDown() override {
ASSERT_TRUE(files::DeletePath(kStoreTmpPath, /*recursive=*/true));
ASSERT_TRUE(files::DeletePath(kStoreCachePath, /*recursive=*/true));
}
protected:
void SetUpNetworkReachabilityProvider() {
network_reachability_provider_ = std::make_unique<stubs::NetworkReachabilityProvider>();
InjectServiceProvider(network_reachability_provider_.get());
}
void SetUpQueue(std::vector<CrashServer::UploadStatus> upload_attempt_results =
std::vector<CrashServer::UploadStatus>{}) {
report_id_ = 1;
snapshot_manager_ = std::make_unique<SnapshotManager>(
dispatcher(), services(), &clock_, zx::sec(5), kGarbageCollectedSnapshotsPath,
StorageSize::Gigabytes(1), StorageSize::Gigabytes(1));
crash_server_ = std::make_unique<StubCrashServer>(upload_attempt_results);
crash_server_->AddSnapshotManager(snapshot_manager_.get());
InitQueue();
}
void InitQueue() {
queue_ = std::make_unique<Queue>(dispatcher(), services(), info_context_, &tags_,
crash_server_.get(), snapshot_manager_.get());
queue_->WatchReportingPolicy(&reporting_policy_watcher_);
queue_->WatchNetwork(&network_watcher_);
}
std::optional<ReportId> AddNewReport(const bool is_hourly_report) {
++report_id_;
Report report = (is_hourly_report) ? MakeHourlyReport(report_id_) : MakeReport(report_id_);
if (queue_->Add(std::move(report))) {
return report_id_;
}
return std::nullopt;
}
void CheckAnnotationsOnServer() {
FX_CHECK(crash_server_);
// Expect annotations that |snapshot_manager_| will for using |kSnapshotUuidValue| as the
// snapshot uuid.
EXPECT_THAT(crash_server_->latest_annotations().Raw(),
UnorderedElementsAreArray({
Pair(kAnnotationKey, kAnnotationValue),
Pair("debug.snapshot.error", "not persisted"),
Pair("debug.snapshot.present", "false"),
}));
}
void CheckAttachmentKeysOnServer() {
FX_CHECK(crash_server_);
EXPECT_THAT(crash_server_->latest_attachment_keys(),
UnorderedElementsAre(kAttachmentKey, kMinidumpKey));
}
LogTags tags_;
std::unique_ptr<Queue> queue_;
TestReportingPolicyWatcher reporting_policy_watcher_;
size_t report_id_ = 1;
NetworkWatcher network_watcher_;
timekeeper::TestClock clock_;
std::unique_ptr<stubs::NetworkReachabilityProvider> network_reachability_provider_;
std::unique_ptr<SnapshotManager> snapshot_manager_;
std::unique_ptr<StubCrashServer> crash_server_;
std::shared_ptr<InfoContext> info_context_;
std::shared_ptr<cobalt::Logger> cobalt_;
};
TEST_F(QueueTest, Add_ReportingPolicyUndecided) {
SetUpQueue();
reporting_policy_watcher_.Set(ReportingPolicy::kUndecided);
const auto report_id = AddNewReport(/*is_hourly_report=*/false);
ASSERT_TRUE(*report_id);
EXPECT_TRUE(queue_->Contains(*report_id));
RunLoopUntilIdle();
EXPECT_THAT(ReceivedCobaltEvents(), IsEmpty());
}
TEST_F(QueueTest, Add_ReportingPolicyUndecided_HourlyReport) {
SetUpQueue();
reporting_policy_watcher_.Set(ReportingPolicy::kUndecided);
const auto report_id_1 = AddNewReport(/*is_hourly_report=*/true);
ASSERT_TRUE(*report_id_1);
EXPECT_TRUE(queue_->Contains(*report_id_1));
// Later hourly reports shouldn't be kept in the queue.
const auto report_id_2 = AddNewReport(/*is_hourly_report=*/true);
ASSERT_TRUE(*report_id_2);
EXPECT_TRUE(queue_->Contains(*report_id_1));
EXPECT_FALSE(queue_->Contains(*report_id_2));
RunLoopUntilIdle();
EXPECT_THAT(ReceivedCobaltEvents(), UnorderedElementsAreArray({
cobalt::Event(cobalt::CrashState::kDeleted),
}));
}
TEST_F(QueueTest, Add_ReportingPolicyDoNotFileAndDelete) {
SetUpQueue();
reporting_policy_watcher_.Set(ReportingPolicy::kDoNotFileAndDelete);
const auto report_id = AddNewReport(/*is_hourly_report=*/false);
ASSERT_TRUE(*report_id);
EXPECT_FALSE(queue_->Contains(*report_id));
RunLoopUntilIdle();
EXPECT_THAT(ReceivedCobaltEvents(), UnorderedElementsAreArray({
cobalt::Event(cobalt::CrashState::kDeleted),
}));
}
TEST_F(QueueTest, Add_ReportingPolicyArchive) {
SetUpQueue();
reporting_policy_watcher_.Set(ReportingPolicy::kArchive);
auto report_id = AddNewReport(/*is_hourly_report=*/false);
ASSERT_TRUE(*report_id);
EXPECT_FALSE(queue_->Contains(*report_id));
report_id = AddNewReport(/*is_hourly_report=*/true);
ASSERT_TRUE(*report_id);
EXPECT_FALSE(queue_->Contains(*report_id));
RunLoopUntilIdle();
EXPECT_THAT(ReceivedCobaltEvents(), UnorderedElementsAreArray({
cobalt::Event(cobalt::CrashState::kArchived),
cobalt::Event(cobalt::CrashState::kArchived),
}));
}
TEST_F(QueueTest, Add_ReportingPolicyUpload_Successful) {
SetUpQueue({kUploadSuccessful});
reporting_policy_watcher_.Set(ReportingPolicy::kUpload);
const auto report_id = AddNewReport(/*is_hourly_report=*/false);
ASSERT_TRUE(*report_id);
EXPECT_FALSE(queue_->Contains(*report_id));
CheckAnnotationsOnServer();
CheckAttachmentKeysOnServer();
RunLoopUntilIdle();
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::CrashState::kUploaded),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 1u),
cobalt::Event(cobalt::UploadAttemptState::kUploaded, 1u),
}));
}
TEST_F(QueueTest, Add_ReportingPolicyUpload_Failure) {
SetUpQueue({kUploadFailed});
reporting_policy_watcher_.Set(ReportingPolicy::kUpload);
const auto report_id = AddNewReport(/*is_hourly_report=*/false);
ASSERT_TRUE(*report_id);
EXPECT_TRUE(queue_->Contains(*report_id));
RunLoopUntilIdle();
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 1u),
}));
}
TEST_F(QueueTest, Add_ReportingPolicyUpload_AfterStopUploading) {
SetUpQueue();
reporting_policy_watcher_.Set(ReportingPolicy::kUpload);
queue_->StopUploading();
const auto report_id = AddNewReport(/*is_hourly_report=*/false);
ASSERT_TRUE(*report_id);
EXPECT_TRUE(queue_->Contains(*report_id));
RunLoopUntilIdle();
EXPECT_THAT(ReceivedCobaltEvents(), IsEmpty());
}
TEST_F(QueueTest, PeriodicUpload) {
SetUpQueue({
kUploadSuccessful,
kUploadFailed,
kUploadFailed,
kUploadSuccessful,
kUploadSuccessful,
});
reporting_policy_watcher_.Set(ReportingPolicy::kUndecided);
auto report_id = AddNewReport(/*is_hourly_upload=*/false);
ASSERT_TRUE(report_id);
EXPECT_TRUE(queue_->Contains(*report_id));
reporting_policy_watcher_.Set(ReportingPolicy::kUpload);
ASSERT_TRUE(queue_->IsPeriodicUploadScheduled());
RunLoopUntilIdle();
EXPECT_FALSE(queue_->Contains(*report_id));
CheckAnnotationsOnServer();
CheckAttachmentKeysOnServer();
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::CrashState::kUploaded),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 1u),
cobalt::Event(cobalt::UploadAttemptState::kUploaded, 1u),
}));
report_id = AddNewReport(/*is_hourly_upload=*/false);
ASSERT_TRUE(report_id);
EXPECT_TRUE(queue_->Contains(*report_id));
const auto hourly_report_id = AddNewReport(/*is_hourly_upload=*/true);
ASSERT_TRUE(hourly_report_id);
EXPECT_TRUE(queue_->Contains(*hourly_report_id));
RunLoopFor(kPeriodicUploadDuration);
EXPECT_FALSE(queue_->Contains(*report_id));
EXPECT_FALSE(queue_->Contains(*hourly_report_id));
CheckAnnotationsOnServer();
CheckAttachmentKeysOnServer();
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::CrashState::kUploaded),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 1u),
cobalt::Event(cobalt::UploadAttemptState::kUploaded, 1u),
cobalt::Event(cobalt::CrashState::kUploaded),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 1u),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 2u),
cobalt::Event(cobalt::UploadAttemptState::kUploaded, 2u),
cobalt::Event(cobalt::CrashState::kUploaded),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 1u),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 2u),
cobalt::Event(cobalt::UploadAttemptState::kUploaded, 2u),
}));
}
TEST_F(QueueTest, PeriodicUpload_ReportingPolicyChanges) {
SetUpQueue();
EXPECT_FALSE(queue_->IsPeriodicUploadScheduled());
reporting_policy_watcher_.Set(ReportingPolicy::kUpload);
EXPECT_TRUE(queue_->IsPeriodicUploadScheduled());
reporting_policy_watcher_.Set(ReportingPolicy::kUndecided);
EXPECT_FALSE(queue_->IsPeriodicUploadScheduled());
reporting_policy_watcher_.Set(ReportingPolicy::kUpload);
EXPECT_TRUE(queue_->IsPeriodicUploadScheduled());
reporting_policy_watcher_.Set(ReportingPolicy::kDoNotFileAndDelete);
EXPECT_FALSE(queue_->IsPeriodicUploadScheduled());
reporting_policy_watcher_.Set(ReportingPolicy::kUpload);
EXPECT_TRUE(queue_->IsPeriodicUploadScheduled());
reporting_policy_watcher_.Set(ReportingPolicy::kArchive);
EXPECT_TRUE(queue_->IsPeriodicUploadScheduled());
}
TEST_F(QueueTest, PeriodicUpload_AfterStopUploading) {
SetUpQueue();
reporting_policy_watcher_.Set(ReportingPolicy::kUpload);
ASSERT_TRUE(queue_->IsPeriodicUploadScheduled());
queue_->StopUploading();
EXPECT_FALSE(queue_->IsPeriodicUploadScheduled());
}
TEST_F(QueueTest, UploadOnNetworkReachable) {
SetUpQueue({kUploadFailed, kUploadFailed, kUploadSuccessful, kUploadSuccessful});
reporting_policy_watcher_.Set(ReportingPolicy::kUpload);
const auto report_id = AddNewReport(/*is_hourly_report=*/false);
ASSERT_TRUE(report_id);
const auto hourly_report_id = AddNewReport(/*is_hourly_report=*/true);
ASSERT_TRUE(hourly_report_id);
network_reachability_provider_->TriggerOnNetworkReachable(true);
RunLoopUntilIdle();
EXPECT_FALSE(queue_->Contains(*report_id));
EXPECT_FALSE(queue_->Contains(*hourly_report_id));
RunLoopUntilIdle();
CheckAnnotationsOnServer();
CheckAttachmentKeysOnServer();
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::CrashState::kUploaded),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 1u),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 2u),
cobalt::Event(cobalt::UploadAttemptState::kUploaded, 2u),
cobalt::Event(cobalt::CrashState::kUploaded),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 1u),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 2u),
cobalt::Event(cobalt::UploadAttemptState::kUploaded, 2u),
}));
}
TEST_F(QueueTest, UploadThrottled) {
SetUpQueue({kUploadThrottled, kUploadFailed, kUploadThrottled});
reporting_policy_watcher_.Set(ReportingPolicy::kUpload);
auto report_id = AddNewReport(/*is_hourly_report=*/false);
ASSERT_TRUE(report_id);
EXPECT_FALSE(queue_->Contains(*report_id));
report_id = AddNewReport(/*is_hourly_report=*/false);
ASSERT_TRUE(report_id);
EXPECT_TRUE(queue_->Contains(*report_id));
RunLoopFor(kPeriodicUploadDuration);
EXPECT_FALSE(queue_->Contains(*report_id));
RunLoopUntilIdle();
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::CrashState::kUploadThrottled),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 1u),
cobalt::Event(cobalt::UploadAttemptState::kUploadThrottled, 1u),
cobalt::Event(cobalt::CrashState::kUploadThrottled),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 1u),
cobalt::Event(cobalt::UploadAttemptState::kUploadAttempt, 2u),
cobalt::Event(cobalt::UploadAttemptState::kUploadThrottled, 2u),
}));
}
TEST_F(QueueTest, InitializeFromStore) {
// This test cannot call RunLoopUntilIdle in any capacity once InitQueue has been called been
// called for the second time. The watchers still hold callbacks tied to the old, deleted queue
// and will crash if they attempt to execute the callbacks.
SetUpQueue();
reporting_policy_watcher_.Set(ReportingPolicy::kUndecided);
const auto report_id = AddNewReport(/*is_hourly_report=*/false);
ASSERT_TRUE(report_id);
EXPECT_TRUE(queue_->Contains(*report_id));
SetUpQueue();
EXPECT_TRUE(queue_->Contains(*report_id));
}
TEST_F(QueueTest, ReportDeletedByStore) {
SetUpQueue();
reporting_policy_watcher_.Set(ReportingPolicy::kUndecided);
const auto report_id = AddNewReport(/*is_hourly_report=*/false);
ASSERT_TRUE(report_id);
EXPECT_TRUE(queue_->Contains(*report_id));
ASSERT_TRUE(DeleteReportFromStore().has_value());
reporting_policy_watcher_.Set(ReportingPolicy::kUpload);
RunLoopFor(kPeriodicUploadDuration);
EXPECT_FALSE(queue_->Contains(*report_id));
}
} // namespace
} // namespace crash_reports
} // namespace forensics