// Copyright 2015 The Crashpad Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crash_report_database.h" #include "client/settings.h" #include "gtest/gtest.h" #include "test/errors.h" #include "test/file.h" #include "test/scoped_temp_dir.h" #include "util/file/file_io.h" #include "util/file/filesystem.h" namespace crashpad { namespace test { namespace { class CrashReportDatabaseTest : public testing::Test { public: CrashReportDatabaseTest() { } protected: // testing::Test: void SetUp() override { db_ = CrashReportDatabase::Initialize(path()); ASSERT_TRUE(db_); } void ResetDatabase() { db_.reset(); } CrashReportDatabase* db() { return db_.get(); } base::FilePath path() const { return temp_dir_.path().Append(FILE_PATH_LITERAL("crashpad_test_database")); } void CreateCrashReport(CrashReportDatabase::Report* report) { std::unique_ptr new_report; ASSERT_EQ(db_->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); static constexpr char kTest[] = "test"; ASSERT_TRUE(new_report->Writer()->Write(kTest, sizeof(kTest))); UUID uuid; EXPECT_EQ(db_->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); EXPECT_EQ(db_->LookUpCrashReport(uuid, report), CrashReportDatabase::kNoError); ExpectPreparedCrashReport(*report); } void UploadReport(const UUID& uuid, bool successful, const std::string& id) { Settings* const settings = db_->GetSettings(); ASSERT_TRUE(settings); time_t times[2]; ASSERT_TRUE(settings->GetLastUploadAttemptTime(×[0])); const CrashReportDatabase::Report* report = nullptr; ASSERT_EQ(db_->GetReportForUploading(uuid, &report), CrashReportDatabase::kNoError); EXPECT_NE(report->uuid, UUID()); EXPECT_FALSE(report->file_path.empty()); EXPECT_TRUE(FileExists(report->file_path)) << report->file_path.value(); EXPECT_GT(report->creation_time, 0); EXPECT_EQ(db_->RecordUploadAttempt(report, successful, id), CrashReportDatabase::kNoError); ASSERT_TRUE(settings->GetLastUploadAttemptTime(×[1])); EXPECT_NE(times[1], 0); EXPECT_GE(times[1], times[0]); } void ExpectPreparedCrashReport(const CrashReportDatabase::Report& report) { EXPECT_NE(report.uuid, UUID()); EXPECT_FALSE(report.file_path.empty()); EXPECT_TRUE(FileExists(report.file_path)) << report.file_path.value(); EXPECT_TRUE(report.id.empty()); EXPECT_GT(report.creation_time, 0); EXPECT_FALSE(report.uploaded); EXPECT_EQ(report.last_upload_attempt_time, 0); EXPECT_EQ(report.upload_attempts, 0); EXPECT_FALSE(report.upload_explicitly_requested); } void RelocateDatabase() { ResetDatabase(); temp_dir_.Rename(); SetUp(); } CrashReportDatabase::OperationStatus RequestUpload(const UUID& uuid) { CrashReportDatabase::OperationStatus os = db()->RequestUpload(uuid); CrashReportDatabase::Report report; EXPECT_EQ(db_->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); return os; } private: ScopedTempDir temp_dir_; std::unique_ptr db_; DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseTest); }; TEST_F(CrashReportDatabaseTest, Initialize) { // Initialize the database for the first time, creating it. ASSERT_TRUE(db()); Settings* settings = db()->GetSettings(); UUID client_ids[3]; ASSERT_TRUE(settings->GetClientID(&client_ids[0])); EXPECT_NE(client_ids[0], UUID()); time_t last_upload_attempt_time; ASSERT_TRUE(settings->GetLastUploadAttemptTime(&last_upload_attempt_time)); EXPECT_EQ(last_upload_attempt_time, 0); // Close and reopen the database at the same path. ResetDatabase(); EXPECT_FALSE(db()); auto db = CrashReportDatabase::InitializeWithoutCreating(path()); ASSERT_TRUE(db); settings = db->GetSettings(); ASSERT_TRUE(settings->GetClientID(&client_ids[1])); EXPECT_EQ(client_ids[1], client_ids[0]); ASSERT_TRUE(settings->GetLastUploadAttemptTime(&last_upload_attempt_time)); EXPECT_EQ(last_upload_attempt_time, 0); // Check that the database can also be opened by the method that is permitted // to create it. db = CrashReportDatabase::Initialize(path()); ASSERT_TRUE(db); settings = db->GetSettings(); ASSERT_TRUE(settings->GetClientID(&client_ids[2])); EXPECT_EQ(client_ids[2], client_ids[0]); ASSERT_TRUE(settings->GetLastUploadAttemptTime(&last_upload_attempt_time)); EXPECT_EQ(last_upload_attempt_time, 0); std::vector reports; EXPECT_EQ(db->GetPendingReports(&reports), CrashReportDatabase::kNoError); EXPECT_TRUE(reports.empty()); reports.clear(); EXPECT_EQ(db->GetCompletedReports(&reports), CrashReportDatabase::kNoError); EXPECT_TRUE(reports.empty()); // InitializeWithoutCreating() shouldn’t create a nonexistent database. base::FilePath non_database_path = path().DirName().Append(FILE_PATH_LITERAL("not_a_database")); db = CrashReportDatabase::InitializeWithoutCreating(non_database_path); EXPECT_FALSE(db); } TEST_F(CrashReportDatabaseTest, NewCrashReport) { std::unique_ptr new_report; EXPECT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); UUID expect_uuid = new_report->ReportID(); UUID uuid; EXPECT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); EXPECT_EQ(uuid, expect_uuid); CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); ExpectPreparedCrashReport(report); std::vector reports; EXPECT_EQ(db()->GetPendingReports(&reports), CrashReportDatabase::kNoError); ASSERT_EQ(reports.size(), 1u); EXPECT_EQ(reports[0].uuid, report.uuid); reports.clear(); EXPECT_EQ(db()->GetCompletedReports(&reports), CrashReportDatabase::kNoError); EXPECT_TRUE(reports.empty()); } TEST_F(CrashReportDatabaseTest, LookUpCrashReport) { UUID uuid; { CrashReportDatabase::Report report; CreateCrashReport(&report); uuid = report.uuid; } { CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); EXPECT_EQ(report.uuid, uuid); EXPECT_NE(report.file_path.value().find(path().value()), std::string::npos); EXPECT_EQ(report.id, std::string()); EXPECT_FALSE(report.uploaded); EXPECT_EQ(report.last_upload_attempt_time, 0); EXPECT_EQ(report.upload_attempts, 0); EXPECT_FALSE(report.upload_explicitly_requested); } UploadReport(uuid, true, "test"); { CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); EXPECT_EQ(report.uuid, uuid); EXPECT_NE(report.file_path.value().find(path().value()), std::string::npos); EXPECT_EQ(report.id, "test"); EXPECT_TRUE(report.uploaded); EXPECT_NE(report.last_upload_attempt_time, 0); EXPECT_EQ(report.upload_attempts, 1); EXPECT_FALSE(report.upload_explicitly_requested); } } TEST_F(CrashReportDatabaseTest, RecordUploadAttempt) { std::vector reports(3); CreateCrashReport(&reports[0]); CreateCrashReport(&reports[1]); CreateCrashReport(&reports[2]); // Record two attempts: one successful, one not. UploadReport(reports[1].uuid, false, std::string()); UploadReport(reports[2].uuid, true, "abc123"); std::vector query(3); EXPECT_EQ(db()->LookUpCrashReport(reports[0].uuid, &query[0]), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(reports[1].uuid, &query[1]), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(reports[2].uuid, &query[2]), CrashReportDatabase::kNoError); EXPECT_EQ(query[0].id, std::string()); EXPECT_EQ(query[1].id, std::string()); EXPECT_EQ(query[2].id, "abc123"); EXPECT_FALSE(query[0].uploaded); EXPECT_FALSE(query[1].uploaded); EXPECT_TRUE(query[2].uploaded); EXPECT_EQ(query[0].last_upload_attempt_time, 0); EXPECT_NE(query[1].last_upload_attempt_time, 0); EXPECT_NE(query[2].last_upload_attempt_time, 0); EXPECT_EQ(query[0].upload_attempts, 0); EXPECT_EQ(query[1].upload_attempts, 1); EXPECT_EQ(query[2].upload_attempts, 1); // Attempt to upload and fail again. UploadReport(reports[1].uuid, false, std::string()); time_t report_2_upload_time = query[2].last_upload_attempt_time; EXPECT_EQ(db()->LookUpCrashReport(reports[0].uuid, &query[0]), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(reports[1].uuid, &query[1]), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(reports[2].uuid, &query[2]), CrashReportDatabase::kNoError); EXPECT_FALSE(query[0].uploaded); EXPECT_FALSE(query[1].uploaded); EXPECT_TRUE(query[2].uploaded); EXPECT_EQ(query[0].last_upload_attempt_time, 0); EXPECT_GE(query[1].last_upload_attempt_time, report_2_upload_time); EXPECT_EQ(query[2].last_upload_attempt_time, report_2_upload_time); EXPECT_EQ(query[0].upload_attempts, 0); EXPECT_EQ(query[1].upload_attempts, 2); EXPECT_EQ(query[2].upload_attempts, 1); // Third time's the charm: upload and succeed. UploadReport(reports[1].uuid, true, "666hahaha"); time_t report_1_upload_time = query[1].last_upload_attempt_time; EXPECT_EQ(db()->LookUpCrashReport(reports[0].uuid, &query[0]), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(reports[1].uuid, &query[1]), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(reports[2].uuid, &query[2]), CrashReportDatabase::kNoError); EXPECT_FALSE(query[0].uploaded); EXPECT_TRUE(query[1].uploaded); EXPECT_TRUE(query[2].uploaded); EXPECT_EQ(query[0].last_upload_attempt_time, 0); EXPECT_GE(query[1].last_upload_attempt_time, report_1_upload_time); EXPECT_EQ(query[2].last_upload_attempt_time, report_2_upload_time); EXPECT_EQ(query[0].upload_attempts, 0); EXPECT_EQ(query[1].upload_attempts, 3); EXPECT_EQ(query[2].upload_attempts, 1); } // This test covers both query functions since they are related. TEST_F(CrashReportDatabaseTest, GetCompletedAndNotUploadedReports) { std::vector reports(5); CreateCrashReport(&reports[0]); CreateCrashReport(&reports[1]); CreateCrashReport(&reports[2]); CreateCrashReport(&reports[3]); CreateCrashReport(&reports[4]); const UUID& report_0_uuid = reports[0].uuid; const UUID& report_1_uuid = reports[1].uuid; const UUID& report_2_uuid = reports[2].uuid; const UUID& report_3_uuid = reports[3].uuid; const UUID& report_4_uuid = reports[4].uuid; std::vector pending; EXPECT_EQ(db()->GetPendingReports(&pending), CrashReportDatabase::kNoError); std::vector completed; EXPECT_EQ(db()->GetCompletedReports(&completed), CrashReportDatabase::kNoError); EXPECT_EQ(pending.size(), reports.size()); EXPECT_EQ(completed.size(), 0u); // Upload one report successfully. UploadReport(report_1_uuid, true, "report1"); pending.clear(); EXPECT_EQ(db()->GetPendingReports(&pending), CrashReportDatabase::kNoError); completed.clear(); EXPECT_EQ(db()->GetCompletedReports(&completed), CrashReportDatabase::kNoError); EXPECT_EQ(pending.size(), 4u); ASSERT_EQ(completed.size(), 1u); for (const auto& report : pending) { EXPECT_NE(report.uuid, report_1_uuid); EXPECT_FALSE(report.file_path.empty()); } EXPECT_EQ(completed[0].uuid, report_1_uuid); EXPECT_EQ(completed[0].id, "report1"); EXPECT_EQ(completed[0].uploaded, true); EXPECT_GT(completed[0].last_upload_attempt_time, 0); EXPECT_EQ(completed[0].upload_attempts, 1); // Fail to upload one report. UploadReport(report_2_uuid, false, std::string()); pending.clear(); EXPECT_EQ(db()->GetPendingReports(&pending), CrashReportDatabase::kNoError); completed.clear(); EXPECT_EQ(db()->GetCompletedReports(&completed), CrashReportDatabase::kNoError); EXPECT_EQ(pending.size(), 4u); ASSERT_EQ(completed.size(), 1u); for (const auto& report : pending) { if (report.upload_attempts != 0) { EXPECT_EQ(report.uuid, report_2_uuid); EXPECT_GT(report.last_upload_attempt_time, 0); EXPECT_FALSE(report.uploaded); EXPECT_TRUE(report.id.empty()); } EXPECT_FALSE(report.file_path.empty()); } // Upload a second report. UploadReport(report_4_uuid, true, "report_4"); pending.clear(); EXPECT_EQ(db()->GetPendingReports(&pending), CrashReportDatabase::kNoError); completed.clear(); EXPECT_EQ(db()->GetCompletedReports(&completed), CrashReportDatabase::kNoError); EXPECT_EQ(pending.size(), 3u); ASSERT_EQ(completed.size(), 2u); // Succeed the failed report. UploadReport(report_2_uuid, true, "report 2"); pending.clear(); EXPECT_EQ(db()->GetPendingReports(&pending), CrashReportDatabase::kNoError); completed.clear(); EXPECT_EQ(db()->GetCompletedReports(&completed), CrashReportDatabase::kNoError); EXPECT_EQ(pending.size(), 2u); ASSERT_EQ(completed.size(), 3u); for (const auto& report : pending) { EXPECT_TRUE(report.uuid == report_0_uuid || report.uuid == report_3_uuid); EXPECT_FALSE(report.file_path.empty()); } // Skip upload for one report. EXPECT_EQ(db()->SkipReportUpload( report_3_uuid, Metrics::CrashSkippedReason::kUploadsDisabled), CrashReportDatabase::kNoError); pending.clear(); EXPECT_EQ(db()->GetPendingReports(&pending), CrashReportDatabase::kNoError); completed.clear(); EXPECT_EQ(db()->GetCompletedReports(&completed), CrashReportDatabase::kNoError); ASSERT_EQ(pending.size(), 1u); ASSERT_EQ(completed.size(), 4u); EXPECT_EQ(pending[0].uuid, report_0_uuid); for (const auto& report : completed) { if (report.uuid == report_3_uuid) { EXPECT_FALSE(report.uploaded); EXPECT_EQ(report.upload_attempts, 0); EXPECT_EQ(report.last_upload_attempt_time, 0); } else { EXPECT_TRUE(report.uploaded); EXPECT_GT(report.upload_attempts, 0); EXPECT_GT(report.last_upload_attempt_time, 0); } EXPECT_FALSE(report.file_path.empty()); } } TEST_F(CrashReportDatabaseTest, DuelingUploads) { CrashReportDatabase::Report report; CreateCrashReport(&report); const CrashReportDatabase::Report* upload_report; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report), CrashReportDatabase::kNoError); const CrashReportDatabase::Report* upload_report_2 = nullptr; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report_2), CrashReportDatabase::kBusyError); EXPECT_FALSE(upload_report_2); EXPECT_EQ(db()->RecordUploadAttempt(upload_report, true, std::string()), CrashReportDatabase::kNoError); } TEST_F(CrashReportDatabaseTest, UploadAlreadyUploaded) { CrashReportDatabase::Report report; CreateCrashReport(&report); const CrashReportDatabase::Report* upload_report; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report), CrashReportDatabase::kNoError); EXPECT_EQ(db()->RecordUploadAttempt(upload_report, true, std::string()), CrashReportDatabase::kNoError); const CrashReportDatabase::Report* upload_report_2 = nullptr; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report_2), CrashReportDatabase::kReportNotFound); EXPECT_FALSE(upload_report_2); } TEST_F(CrashReportDatabaseTest, MoveDatabase) { std::unique_ptr new_report; EXPECT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); UUID uuid; EXPECT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); RelocateDatabase(); CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); ExpectPreparedCrashReport(report); } TEST_F(CrashReportDatabaseTest, ReportRemoved) { std::unique_ptr new_report; EXPECT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); UUID uuid; EXPECT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); EXPECT_TRUE(LoggingRemoveFile(report.file_path)); EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kReportNotFound); } TEST_F(CrashReportDatabaseTest, DeleteReport) { CrashReportDatabase::Report keep_pending; CrashReportDatabase::Report delete_pending; CrashReportDatabase::Report keep_completed; CrashReportDatabase::Report delete_completed; CreateCrashReport(&keep_pending); CreateCrashReport(&delete_pending); CreateCrashReport(&keep_completed); CreateCrashReport(&delete_completed); EXPECT_TRUE(FileExists(keep_pending.file_path)); EXPECT_TRUE(FileExists(delete_pending.file_path)); EXPECT_TRUE(FileExists(keep_completed.file_path)); EXPECT_TRUE(FileExists(delete_completed.file_path)); UploadReport(keep_completed.uuid, true, "1"); UploadReport(delete_completed.uuid, true, "2"); EXPECT_EQ(db()->LookUpCrashReport(keep_completed.uuid, &keep_completed), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(delete_completed.uuid, &delete_completed), CrashReportDatabase::kNoError); EXPECT_TRUE(FileExists(keep_completed.file_path)); EXPECT_TRUE(FileExists(delete_completed.file_path)); EXPECT_EQ(db()->DeleteReport(delete_pending.uuid), CrashReportDatabase::kNoError); EXPECT_FALSE(FileExists(delete_pending.file_path)); EXPECT_EQ(db()->LookUpCrashReport(delete_pending.uuid, &delete_pending), CrashReportDatabase::kReportNotFound); EXPECT_EQ(db()->DeleteReport(delete_pending.uuid), CrashReportDatabase::kReportNotFound); EXPECT_EQ(db()->DeleteReport(delete_completed.uuid), CrashReportDatabase::kNoError); EXPECT_FALSE(FileExists(delete_completed.file_path)); EXPECT_EQ(db()->LookUpCrashReport(delete_completed.uuid, &delete_completed), CrashReportDatabase::kReportNotFound); EXPECT_EQ(db()->DeleteReport(delete_completed.uuid), CrashReportDatabase::kReportNotFound); EXPECT_EQ(db()->LookUpCrashReport(keep_pending.uuid, &keep_pending), CrashReportDatabase::kNoError); EXPECT_EQ(db()->LookUpCrashReport(keep_completed.uuid, &keep_completed), CrashReportDatabase::kNoError); } TEST_F(CrashReportDatabaseTest, DeleteReportEmptyingDatabase) { CrashReportDatabase::Report report; CreateCrashReport(&report); EXPECT_TRUE(FileExists(report.file_path)); UploadReport(report.uuid, true, "1"); EXPECT_EQ(db()->LookUpCrashReport(report.uuid, &report), CrashReportDatabase::kNoError); EXPECT_TRUE(FileExists(report.file_path)); // This causes an empty database to be written, make sure this is handled. EXPECT_EQ(db()->DeleteReport(report.uuid), CrashReportDatabase::kNoError); EXPECT_FALSE(FileExists(report.file_path)); } TEST_F(CrashReportDatabaseTest, ReadEmptyDatabase) { CrashReportDatabase::Report report; CreateCrashReport(&report); EXPECT_EQ(db()->DeleteReport(report.uuid), CrashReportDatabase::kNoError); // Deleting and the creating another report causes an empty database to be // loaded. Make sure this is handled. CrashReportDatabase::Report report2; CreateCrashReport(&report2); } TEST_F(CrashReportDatabaseTest, RequestUpload) { std::vector reports(2); CreateCrashReport(&reports[0]); CreateCrashReport(&reports[1]); const UUID& report_0_uuid = reports[0].uuid; const UUID& report_1_uuid = reports[1].uuid; // Skipped report gets back to pending state after RequestUpload is called. EXPECT_EQ(db()->SkipReportUpload( report_1_uuid, Metrics::CrashSkippedReason::kUploadsDisabled), CrashReportDatabase::kNoError); std::vector pending_reports; CrashReportDatabase::OperationStatus os = db()->GetPendingReports(&pending_reports); EXPECT_EQ(os, CrashReportDatabase::kNoError); ASSERT_EQ(pending_reports.size(), 1u); EXPECT_EQ(report_0_uuid, pending_reports[0].uuid); pending_reports.clear(); EXPECT_EQ(RequestUpload(report_1_uuid), CrashReportDatabase::kNoError); os = db()->GetPendingReports(&pending_reports); EXPECT_EQ(os, CrashReportDatabase::kNoError); ASSERT_EQ(pending_reports.size(), 2u); // Check individual reports. const CrashReportDatabase::Report* expicitly_requested_report; const CrashReportDatabase::Report* pending_report; if (pending_reports[0].uuid == report_0_uuid) { pending_report = &pending_reports[0]; expicitly_requested_report = &pending_reports[1]; } else { pending_report = &pending_reports[1]; expicitly_requested_report = &pending_reports[0]; } EXPECT_EQ(pending_report->uuid, report_0_uuid); EXPECT_FALSE(pending_report->upload_explicitly_requested); EXPECT_EQ(expicitly_requested_report->uuid, report_1_uuid); EXPECT_TRUE(expicitly_requested_report->upload_explicitly_requested); // Explicitly requested reports will not have upload_explicitly_requested bit // after getting skipped. EXPECT_EQ(db()->SkipReportUpload( report_1_uuid, Metrics::CrashSkippedReason::kUploadsDisabled), CrashReportDatabase::kNoError); CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(report_1_uuid, &report), CrashReportDatabase::kNoError); EXPECT_FALSE(report.upload_explicitly_requested); // Pending report gets correctly affected after RequestUpload is called. pending_reports.clear(); EXPECT_EQ(RequestUpload(report_0_uuid), CrashReportDatabase::kNoError); os = db()->GetPendingReports(&pending_reports); EXPECT_EQ(os, CrashReportDatabase::kNoError); EXPECT_EQ(pending_reports.size(), 1u); EXPECT_EQ(report_0_uuid, pending_reports[0].uuid); EXPECT_TRUE(pending_reports[0].upload_explicitly_requested); // Already uploaded report cannot be requested for the new upload. UploadReport(report_0_uuid, true, "1"); EXPECT_EQ(RequestUpload(report_0_uuid), CrashReportDatabase::kCannotRequestUpload); } } // namespace } // namespace test } // namespace crashpad