Add functionality to prune old crash reports from the database.

BUG=crashpad:22
R=mark@chromium.org

Review URL: https://codereview.chromium.org/1392653002 .
This commit is contained in:
Robert Sesek 2015-10-07 17:01:47 -04:00
parent 0884d4d3a8
commit f32ca63a91
9 changed files with 643 additions and 3 deletions

View File

@ -40,6 +40,8 @@
'crashpad_client_win.cc',
'crashpad_info.cc',
'crashpad_info.h',
'prune_crash_reports.cc',
'prune_crash_reports.h',
'settings.cc',
'settings.h',
'simple_string_dictionary.cc',

View File

@ -24,6 +24,7 @@
'client.gyp:crashpad_client',
'../compat/compat.gyp:crashpad_compat',
'../test/test.gyp:crashpad_test',
'../third_party/gmock/gmock.gyp:gmock',
'../third_party/gtest/gtest.gyp:gtest',
'../third_party/gtest/gtest.gyp:gtest_main',
'../third_party/mini_chromium/mini_chromium.gyp:base',
@ -35,6 +36,7 @@
'sources': [
'capture_context_mac_test.cc',
'crash_report_database_test.cc',
'prune_crash_reports_test.cc',
'settings_test.cc',
'simple_string_dictionary_test.cc',
'simulate_crash_mac_test.cc',

View File

@ -300,6 +300,13 @@ class CrashReportDatabase {
//! \return The operation status code.
virtual OperationStatus SkipReportUpload(const UUID& uuid) = 0;
//! \brief Deletes a crash report file and its associated metadata.
//!
//! \param[in] uuid The UUID of the report to delete.
//!
//! \return The operation status code.
virtual OperationStatus DeleteReport(const UUID& uuid) = 0;
protected:
CrashReportDatabase() {}

View File

@ -125,6 +125,7 @@ class CrashReportDatabaseMac : public CrashReportDatabase {
bool successful,
const std::string& id) override;
OperationStatus SkipReportUpload(const UUID& uuid) override;
OperationStatus DeleteReport(const UUID& uuid) override;
private:
//! \brief A private extension of the Report class that maintains bookkeeping
@ -476,6 +477,26 @@ CrashReportDatabase::OperationStatus CrashReportDatabaseMac::SkipReportUpload(
return kNoError;
}
CrashReportDatabase::OperationStatus CrashReportDatabaseMac::DeleteReport(
const UUID& uuid) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
base::FilePath report_path = LocateCrashReport(uuid);
if (report_path.empty())
return kReportNotFound;
base::ScopedFD lock(ObtainReportLock(report_path));
if (!lock.is_valid())
return kBusyError;
if (unlink(report_path.value().c_str()) != 0) {
PLOG(ERROR) << "unlink " << report_path.value();
return kFileSystemError;
}
return kNoError;
}
base::FilePath CrashReportDatabaseMac::LocateCrashReport(const UUID& uuid) {
const std::string target_uuid = uuid.ToString();
for (size_t i = 0; i < arraysize(kReportDirectories); ++i) {

View File

@ -497,6 +497,55 @@ TEST_F(CrashReportDatabaseTest, ReportRemoved) {
db()->LookUpCrashReport(uuid, &report));
}
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(CrashReportDatabase::kNoError,
db()->LookUpCrashReport(keep_completed.uuid, &keep_completed));
EXPECT_EQ(CrashReportDatabase::kNoError,
db()->LookUpCrashReport(delete_completed.uuid, &delete_completed));
EXPECT_TRUE(FileExists(keep_completed.file_path));
EXPECT_TRUE(FileExists(delete_completed.file_path));
EXPECT_EQ(CrashReportDatabase::kNoError,
db()->DeleteReport(delete_pending.uuid));
EXPECT_FALSE(FileExists(delete_pending.file_path));
EXPECT_EQ(CrashReportDatabase::kReportNotFound,
db()->LookUpCrashReport(delete_pending.uuid, &delete_pending));
EXPECT_EQ(CrashReportDatabase::kReportNotFound,
db()->DeleteReport(delete_pending.uuid));
EXPECT_EQ(CrashReportDatabase::kNoError,
db()->DeleteReport(delete_completed.uuid));
EXPECT_FALSE(FileExists(delete_completed.file_path));
EXPECT_EQ(CrashReportDatabase::kReportNotFound,
db()->LookUpCrashReport(delete_completed.uuid, &delete_completed));
EXPECT_EQ(CrashReportDatabase::kReportNotFound,
db()->DeleteReport(delete_completed.uuid));
EXPECT_EQ(CrashReportDatabase::kNoError,
db()->LookUpCrashReport(keep_pending.uuid, &keep_pending));
EXPECT_EQ(CrashReportDatabase::kNoError,
db()->LookUpCrashReport(keep_completed.uuid, &keep_completed));
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -229,6 +229,19 @@ class Metadata {
ReportState desired_state,
ReportDisk** report_disk);
//! \brief Removes a report from the metadata database, without touching the
//! on-disk file.
//!
//! The returned report is only valid if CrashReportDatabase::kNoError is
//! returned. This will mark the database as dirty. Future metadata
//! operations for this report will not succeed.
//!
//! \param[in] uuid The report identifier to remove.
//! \param[out] report_path The found report's file_path, valid only if
//! CrashReportDatabase::kNoError is returned.
OperationStatus DeleteReport(const UUID& uuid,
base::FilePath* report_path);
private:
Metadata(FileHandle handle, const base::FilePath& report_dir);
@ -351,6 +364,20 @@ OperationStatus Metadata::FindSingleReportAndMarkDirty(
return os;
}
OperationStatus Metadata::DeleteReport(const UUID& uuid,
base::FilePath* report_path) {
auto report_iter = std::find_if(
reports_.begin(), reports_.end(), [uuid](const ReportDisk& report) {
return report.uuid == uuid;
});
if (report_iter == reports_.end())
return CrashReportDatabase::kReportNotFound;
*report_path = report_iter->file_path;
reports_.erase(report_iter);
dirty_ = true;
return CrashReportDatabase::kNoError;
}
Metadata::Metadata(FileHandle handle, const base::FilePath& report_dir)
: handle_(handle), report_dir_(report_dir), dirty_(false), reports_() {
}
@ -531,6 +558,7 @@ class CrashReportDatabaseWin : public CrashReportDatabase {
bool successful,
const std::string& id) override;
OperationStatus SkipReportUpload(const UUID& uuid) override;
OperationStatus DeleteReport(const UUID& uuid) override;
private:
scoped_ptr<Metadata> AcquireMetadata();
@ -560,9 +588,6 @@ bool CrashReportDatabaseWin::Initialize() {
!CreateDirectoryIfNecessary(base_dir_.Append(kReportsDirectory)))
return false;
// TODO(scottmg): When are completed reports pruned from disk? Delete here or
// maybe on AcquireMetadata().
if (!settings_.Initialize())
return false;
@ -732,6 +757,26 @@ OperationStatus CrashReportDatabaseWin::RecordUploadAttempt(
return kNoError;
}
OperationStatus CrashReportDatabaseWin::DeleteReport(const UUID& uuid) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
scoped_ptr<Metadata> metadata(AcquireMetadata());
if (!metadata)
return kDatabaseError;
base::FilePath report_path;
OperationStatus os = metadata->DeleteReport(uuid, &report_path);
if (os != kNoError)
return os;
if (!DeleteFile(report_path.value().c_str())) {
PLOG(ERROR) << "DeleteFile "
<< base::UTF16ToUTF8(report_path.value());
return kFileSystemError;
}
return kNoError;
}
OperationStatus CrashReportDatabaseWin::SkipReportUpload(const UUID& uuid) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);

View File

@ -0,0 +1,132 @@
// 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/prune_crash_reports.h"
#include <sys/stat.h>
#include <algorithm>
#include <vector>
#include "base/logging.h"
namespace crashpad {
void PruneCrashReportDatabase(CrashReportDatabase* database,
PruneCondition* condition) {
std::vector<CrashReportDatabase::Report> all_reports;
CrashReportDatabase::OperationStatus status;
status = database->GetPendingReports(&all_reports);
if (status != CrashReportDatabase::kNoError) {
LOG(ERROR) << "PruneCrashReportDatabase: Failed to get pending reports";
return;
}
std::vector<CrashReportDatabase::Report> completed_reports;
status = database->GetCompletedReports(&completed_reports);
if (status != CrashReportDatabase::kNoError) {
LOG(ERROR) << "PruneCrashReportDatabase: Failed to get completed reports";
return;
}
all_reports.insert(all_reports.end(), completed_reports.begin(),
completed_reports.end());
std::sort(all_reports.begin(), all_reports.end(),
[](const CrashReportDatabase::Report& lhs,
const CrashReportDatabase::Report& rhs) {
return lhs.creation_time > rhs.creation_time;
});
for (const auto& report : all_reports) {
if (condition->ShouldPruneReport(report)) {
status = database->DeleteReport(report.uuid);
if (status != CrashReportDatabase::kNoError) {
LOG(ERROR) << "Database Pruning: Failed to remove report "
<< report.uuid.ToString();
}
}
}
// TODO(rsesek): For databases that do not use a directory structure,
// it is possible for the metadata sidecar to become corrupted and thus
// leave orphaned crash report files on-disk.
// https://code.google.com/p/crashpad/issues/detail?id=66
}
// static
scoped_ptr<PruneCondition> PruneCondition::GetDefault() {
// DatabaseSizePruneCondition must be the LHS so that it is always evaluated,
// due to the short-circuting behavior of BinaryPruneCondition.
return make_scoped_ptr(new BinaryPruneCondition(BinaryPruneCondition::OR,
new DatabaseSizePruneCondition(1024 * 128), new AgePruneCondition(365)));
}
static const time_t kSecondsInDay = 60 * 60 * 24;
AgePruneCondition::AgePruneCondition(int max_age_in_days)
: oldest_report_time_(
((time(nullptr) - (max_age_in_days * kSecondsInDay))
/ kSecondsInDay) * kSecondsInDay) {}
AgePruneCondition::~AgePruneCondition() {}
bool AgePruneCondition::ShouldPruneReport(
const CrashReportDatabase::Report& report) {
return report.creation_time < oldest_report_time_;
}
DatabaseSizePruneCondition::DatabaseSizePruneCondition(size_t max_size_in_kb)
: max_size_in_kb_(max_size_in_kb), measured_size_in_kb_(0) {}
DatabaseSizePruneCondition::~DatabaseSizePruneCondition() {}
bool DatabaseSizePruneCondition::ShouldPruneReport(
const CrashReportDatabase::Report& report) {
#if defined(OS_POSIX)
struct stat statbuf;
if (stat(report.file_path.value().c_str(), &statbuf) == 0) {
#elif defined(OS_WIN)
struct _stati64 statbuf;
if (_wstat64(report.file_path.value().c_str(), &statbuf) == 0) {
#else
#error "Not implemented"
#endif
// Round up fractional KB to the next 1-KB boundary.
measured_size_in_kb_ +=
static_cast<size_t>((statbuf.st_size + 1023) / 1024);
}
return measured_size_in_kb_ > max_size_in_kb_;
}
BinaryPruneCondition::BinaryPruneCondition(
Operator op, PruneCondition* lhs, PruneCondition* rhs)
: op_(op), lhs_(lhs), rhs_(rhs) {}
BinaryPruneCondition::~BinaryPruneCondition() {}
bool BinaryPruneCondition::ShouldPruneReport(
const CrashReportDatabase::Report& report) {
switch (op_) {
case AND:
return lhs_->ShouldPruneReport(report) && rhs_->ShouldPruneReport(report);
case OR:
return lhs_->ShouldPruneReport(report) || rhs_->ShouldPruneReport(report);
default:
NOTREACHED();
return false;
}
}
} // namespace crashpad

View File

@ -0,0 +1,146 @@
// 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.
#ifndef CRASHPAD_CLIENT_PRUNE_CRASH_REPORTS_H_
#define CRASHPAD_CLIENT_PRUNE_CRASH_REPORTS_H_
#include <time.h>
#include <sys/types.h>
#include "base/memory/scoped_ptr.h"
#include "client/crash_report_database.h"
namespace crashpad {
class PruneCondition;
//! \brief Deletes crash reports from \a database that match \a condition.
//!
//! This function can be used to remove old or large reports from the database.
//! The \a condition will be evaluated against each report in the \a database,
//! sorted in descending order by CrashReportDatabase::Report::creation_time.
//! This guarantee allows conditions to be stateful.
//!
//! \param[in] database The database from which crash reports will be deleted.
//! \param[in] condition The condition against which all reports in the database
//! will be evaluated.
void PruneCrashReportDatabase(CrashReportDatabase* database,
PruneCondition* condition);
scoped_ptr<PruneCondition> GetDefaultDatabasePruneCondition();
//! \brief An abstract base class for evaluating crash reports for deletion.
//!
//! When passed to PruneCrashReportDatabase(), each crash report in the
//! database will be evaluated according to ShouldPruneReport(). The reports
//! are evaluated serially in descending sort order by
//! CrashReportDatabase::Report::creation_time.
class PruneCondition {
public:
//! \brief Returns a sensible default condition for removing obsolete crash
//! reports.
//!
//! The default is to keep reports for one year or a maximum database size
//! of 128 MB.
//!
//! \return A PruneCondition for use with PruneCrashReportDatabase().
static scoped_ptr<PruneCondition> GetDefault();
virtual ~PruneCondition() {}
//! \brief Evaluates a crash report for deletion.
//!
//! \param[in] report The crash report to evaluate.
//!
//! \return `true` if the crash report should be deleted, `false` if it
//! should be kept.
virtual bool ShouldPruneReport(const CrashReportDatabase::Report& report) = 0;
};
//! \brief A PruneCondition that deletes reports older than the specified number
//! days.
class AgePruneCondition final : public PruneCondition {
public:
//! \brief Creates a PruneCondition based on Report::creation_time.
//!
//! \param[in] max_age_in_days Reports created more than this many days ago
//! will be deleted.
explicit AgePruneCondition(int max_age_in_days);
~AgePruneCondition();
bool ShouldPruneReport(const CrashReportDatabase::Report& report) override;
private:
const time_t oldest_report_time_;
DISALLOW_COPY_AND_ASSIGN(AgePruneCondition);
};
//! \brief A PruneCondition that deletes older reports to keep the total
//! Crashpad database size under the specified limit.
class DatabaseSizePruneCondition final : public PruneCondition {
public:
//! \brief Creates a PruneCondition that will keep newer reports, until the
//! sum of the size of all reports is not smaller than \a max_size_in_kb.
//! After the limit is reached, older reports will be pruned.
//!
//! \param[in] max_size_in_kb The maximum number of kilobytes that all crash
//! reports should consume.
explicit DatabaseSizePruneCondition(size_t max_size_in_kb);
~DatabaseSizePruneCondition();
bool ShouldPruneReport(const CrashReportDatabase::Report& report) override;
private:
const size_t max_size_in_kb_;
size_t measured_size_in_kb_;
DISALLOW_COPY_AND_ASSIGN(DatabaseSizePruneCondition);
};
//! \breif A PruneCondition that conjoins two other PruneConditions.
class BinaryPruneCondition final : public PruneCondition {
public:
enum Operator {
AND,
OR,
};
//! \brief Evaluates two sub-conditions according to the specified logical
//! operator.
//!
//! This implements left-to-right evaluation. For Operator::AND, this means
//! if the \a lhs is `false`, the \a rhs will not be consulted. Similarly,
//! with Operator::OR, if the \a lhs is `true`, the \a rhs will not be
//! consulted.
//!
//! \param[in] op The logical operator to apply on \a lhs and \a rhs.
//! \param[in] lhs The left-hand side of \a op. This class takes ownership.
//! \param[in] rhs The right-hand side of \a op. This class takes ownership.
BinaryPruneCondition(Operator op, PruneCondition* lhs, PruneCondition* rhs);
~BinaryPruneCondition();
bool ShouldPruneReport(const CrashReportDatabase::Report& report) override;
private:
const Operator op_;
scoped_ptr<PruneCondition> lhs_;
scoped_ptr<PruneCondition> rhs_;
DISALLOW_COPY_AND_ASSIGN(BinaryPruneCondition);
};
} // namespace crashpad
#endif // CRASHPAD_CLIENT_PRUNE_CRASH_REPORTS_H_

View File

@ -0,0 +1,236 @@
// 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/prune_crash_reports.h"
#include <stdlib.h>
#include <algorithm>
#include <string>
#include <vector>
#include "base/rand_util.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "test/scoped_temp_dir.h"
#include "util/file/file_io.h"
namespace crashpad {
namespace test {
namespace {
class MockDatabase : public CrashReportDatabase {
public:
// CrashReportDatabase:
MOCK_METHOD0(GetSettings, Settings*());
MOCK_METHOD1(PrepareNewCrashReport, OperationStatus(NewReport**));
MOCK_METHOD2(FinishedWritingCrashReport, OperationStatus(NewReport*, UUID*));
MOCK_METHOD1(ErrorWritingCrashReport, OperationStatus(NewReport*));
MOCK_METHOD2(LookUpCrashReport, OperationStatus(const UUID&, Report*));
MOCK_METHOD1(GetPendingReports, OperationStatus(std::vector<Report>*));
MOCK_METHOD1(GetCompletedReports, OperationStatus(std::vector<Report>*));
MOCK_METHOD2(GetReportForUploading,
OperationStatus(const UUID&, const Report**));
MOCK_METHOD3(RecordUploadAttempt,
OperationStatus(const Report*, bool, const std::string&));
MOCK_METHOD1(SkipReportUpload, OperationStatus(const UUID&));
MOCK_METHOD1(DeleteReport, OperationStatus(const UUID&));
};
time_t NDaysAgo(int num_days) {
return time(nullptr) - (num_days * 60 * 60 * 24);
}
TEST(PruneCrashReports, AgeCondition) {
CrashReportDatabase::Report report_80_days;
report_80_days.creation_time = NDaysAgo(80);
CrashReportDatabase::Report report_10_days;
report_10_days.creation_time = NDaysAgo(10);
CrashReportDatabase::Report report_30_days;
report_30_days.creation_time = NDaysAgo(30);
AgePruneCondition condition(30);
EXPECT_TRUE(condition.ShouldPruneReport(report_80_days));
EXPECT_FALSE(condition.ShouldPruneReport(report_10_days));
EXPECT_FALSE(condition.ShouldPruneReport(report_30_days));
}
TEST(PruneCrashReports, SizeCondition) {
ScopedTempDir temp_dir;
CrashReportDatabase::Report report_1k;
report_1k.file_path = temp_dir.path().Append(FILE_PATH_LITERAL("file1024"));
CrashReportDatabase::Report report_3k;
report_3k.file_path = temp_dir.path().Append(FILE_PATH_LITERAL("file3072"));
{
ScopedFileHandle scoped_file_1k(
LoggingOpenFileForWrite(report_1k.file_path,
FileWriteMode::kCreateOrFail,
FilePermissions::kOwnerOnly));
ASSERT_TRUE(scoped_file_1k.is_valid());
std::string string;
for (int i = 0; i < 128; ++i)
string.push_back(static_cast<char>(i));
for (int i = 0; i < 1024; i += string.size()) {
ASSERT_TRUE(LoggingWriteFile(scoped_file_1k.get(),
string.c_str(), string.length()));
}
ScopedFileHandle scoped_file_3k(
LoggingOpenFileForWrite(report_3k.file_path,
FileWriteMode::kCreateOrFail,
FilePermissions::kOwnerOnly));
ASSERT_TRUE(scoped_file_3k.is_valid());
for (int i = 0; i < 3072; i += string.size()) {
ASSERT_TRUE(LoggingWriteFile(scoped_file_3k.get(),
string.c_str(), string.length()));
}
}
{
DatabaseSizePruneCondition condition(1);
EXPECT_FALSE(condition.ShouldPruneReport(report_1k));
EXPECT_TRUE(condition.ShouldPruneReport(report_1k));
}
{
DatabaseSizePruneCondition condition(1);
EXPECT_TRUE(condition.ShouldPruneReport(report_3k));
}
{
DatabaseSizePruneCondition condition(6);
EXPECT_FALSE(condition.ShouldPruneReport(report_3k));
EXPECT_FALSE(condition.ShouldPruneReport(report_3k));
EXPECT_TRUE(condition.ShouldPruneReport(report_1k));
}
}
class StaticCondition final : public PruneCondition {
public:
explicit StaticCondition(bool value) : value_(value), did_execute_(false) {}
~StaticCondition() {}
bool ShouldPruneReport(const CrashReportDatabase::Report& report) override {
did_execute_ = true;
return value_;
}
bool did_execute() const { return did_execute_; }
private:
const bool value_;
bool did_execute_;
DISALLOW_COPY_AND_ASSIGN(StaticCondition);
};
TEST(PruneCrashReports, BinaryCondition) {
const struct {
const char* name;
BinaryPruneCondition::Operator op;
bool lhs_value;
bool rhs_value;
bool cond_result;
bool lhs_executed;
bool rhs_executed;
} kTests[] = {
{"false && false",
BinaryPruneCondition::AND, false, false,
false, true, false},
{"false && true",
BinaryPruneCondition::AND, false, true,
false, true, false},
{"true && false",
BinaryPruneCondition::AND, true, false,
false, true, true},
{"true && true",
BinaryPruneCondition::AND, true, true,
true, true, true},
{"false || false",
BinaryPruneCondition::OR, false, false,
false, true, true},
{"false || true",
BinaryPruneCondition::OR, false, true,
true, true, true},
{"true || false",
BinaryPruneCondition::OR, true, false,
true, true, false},
{"true || true",
BinaryPruneCondition::OR, true, true,
true, true, false},
};
for (const auto& test : kTests) {
SCOPED_TRACE(test.name);
auto lhs = new StaticCondition(test.lhs_value);
auto rhs = new StaticCondition(test.rhs_value);
BinaryPruneCondition condition(test.op, lhs, rhs);
CrashReportDatabase::Report report;
EXPECT_EQ(test.cond_result, condition.ShouldPruneReport(report));
EXPECT_EQ(test.lhs_executed, lhs->did_execute());
EXPECT_EQ(test.rhs_executed, rhs->did_execute());
}
}
MATCHER_P(TestUUID, data_1, "") {
return arg.data_1 == data_1;
}
TEST(PruneCrashReports, PruneOrder) {
using ::testing::_;
using ::testing::DoAll;
using ::testing::Return;
using ::testing::SetArgPointee;
std::vector<CrashReportDatabase::Report> reports;
for (int i = 0; i < 10; ++i) {
CrashReportDatabase::Report temp;
temp.uuid.data_1 = i;
temp.creation_time = NDaysAgo(i * 10);
reports.push_back(temp);
}
// The randomness from std::rand() is not, so use a better rand() instead.
std::random_shuffle(reports.begin(), reports.end(), [](int rand_max) {
return base::RandUint64() % rand_max;
});
std::vector<CrashReportDatabase::Report> pending_reports(
reports.begin(), reports.begin() + 5);
std::vector<CrashReportDatabase::Report> completed_reports(
reports.begin() + 5, reports.end());
MockDatabase db;
EXPECT_CALL(db, GetPendingReports(_)).WillOnce(DoAll(
SetArgPointee<0>(pending_reports),
Return(CrashReportDatabase::kNoError)));
EXPECT_CALL(db, GetCompletedReports(_)).WillOnce(DoAll(
SetArgPointee<0>(completed_reports),
Return(CrashReportDatabase::kNoError)));
for (size_t i = 0; i < reports.size(); ++i) {
EXPECT_CALL(db, DeleteReport(TestUUID(i)))
.WillOnce(Return(CrashReportDatabase::kNoError));
}
StaticCondition delete_all(true);
PruneCrashReportDatabase(&db, &delete_all);
}
} // namespace
} // namespace test
} // namespace crashpad