mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 15:32:10 +08:00
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:
parent
0884d4d3a8
commit
f32ca63a91
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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() {}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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_);
|
||||
|
||||
|
132
client/prune_crash_reports.cc
Normal file
132
client/prune_crash_reports.cc
Normal 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
|
146
client/prune_crash_reports.h
Normal file
146
client/prune_crash_reports.h
Normal 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_
|
236
client/prune_crash_reports_test.cc
Normal file
236
client/prune_crash_reports_test.cc
Normal 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
|
Loading…
x
Reference in New Issue
Block a user