mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
Create CrashReportDatabase interface, a test, and a Mac implementation.
R=mark@chromium.org TEST=client_test --gtest_filter=CrashReportDatabase\* Review URL: https://codereview.chromium.org/842513002
This commit is contained in:
parent
070ea9bfd7
commit
ee98449755
@ -31,6 +31,9 @@
|
||||
'crashpad_client_mac.cc',
|
||||
'crashpad_info.cc',
|
||||
'crashpad_info.h',
|
||||
'crash_report_database_mac.mm',
|
||||
'crash_report_database.cc',
|
||||
'crash_report_database.h',
|
||||
'simple_string_dictionary.cc',
|
||||
'simple_string_dictionary.h',
|
||||
'simulate_crash.h',
|
||||
@ -54,6 +57,7 @@
|
||||
],
|
||||
'sources': [
|
||||
'capture_context_mac_test.cc',
|
||||
'crash_report_database_test.cc',
|
||||
'simple_string_dictionary_test.cc',
|
||||
'simulate_crash_mac_test.cc',
|
||||
],
|
||||
|
24
client/crash_report_database.cc
Normal file
24
client/crash_report_database.cc
Normal file
@ -0,0 +1,24 @@
|
||||
// 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"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
CrashReportDatabase::Report::Report()
|
||||
: uuid(), file_path(), id(), creation_time(0), uploaded(false),
|
||||
last_upload_attempt_time(0), upload_attempts(0) {
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
255
client/crash_report_database.h
Normal file
255
client/crash_report_database.h
Normal file
@ -0,0 +1,255 @@
|
||||
// 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_CRASH_REPORT_DATABASE_H_
|
||||
#define CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/misc/uuid.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief An interface for managing a collection of crash report files and
|
||||
//! metadata associated with the crash reports.
|
||||
//!
|
||||
//! All Report objects that are returned by this class are logically const.
|
||||
//! They are snapshots of the database at the time the query was run, and the
|
||||
//! data returned is liable to change after the query is executed.
|
||||
//!
|
||||
//! The lifecycle of a crash report has three stages:
|
||||
//!
|
||||
//! 1. New: A crash report is created with PrepareNewCrashReport(), the
|
||||
//! the client then writes the report, and then calls
|
||||
//! FinishedWritingCrashReport() to make the report Pending.
|
||||
//! 2. Pending: The report has been written but has not been locally
|
||||
//! processed.
|
||||
//! 3. Completed: The report has been locally processed, either by uploading
|
||||
//! it to a collection server and calling RecordUploadAttempt(), or by
|
||||
//! calling SkipReportUpload().
|
||||
class CrashReportDatabase {
|
||||
public:
|
||||
//! \brief A crash report record.
|
||||
//!
|
||||
//! This represents the metadata for a crash report, as well as the location
|
||||
//! of the report itself. A CrashReportDatabase maintains at least this
|
||||
//! information.
|
||||
struct Report {
|
||||
Report();
|
||||
|
||||
//! A unique identifier by which this report will always be known to the
|
||||
//! database.
|
||||
UUID uuid;
|
||||
|
||||
//! The current location of the crash report on the client’s filesystem.
|
||||
//! The location of a crash report may change over time, so the UUID should
|
||||
//! be used as the canonical identifier.
|
||||
base::FilePath file_path;
|
||||
|
||||
//! An identifier issued to this crash report by a collection server.
|
||||
std::string id;
|
||||
|
||||
//! The time at which the report was generated.
|
||||
time_t creation_time;
|
||||
|
||||
//! Whether this crash report was successfully uploaded to a collection
|
||||
//! server.
|
||||
bool uploaded;
|
||||
|
||||
//! The last timestamp at which an attempt was made to submit this crash
|
||||
//! report to a collection server. If this is zero, then the report has
|
||||
//! never been uploaded. If #uploaded is true, then this timestamp is the
|
||||
//! time at which the report was uploaded, and no other attempts to upload
|
||||
//! this report will be made.
|
||||
time_t last_upload_attempt_time;
|
||||
|
||||
//! The number of times an attempt was made to submit this report to
|
||||
//! a collection server. If this is more than zero, then
|
||||
//! #last_upload_attempt_time will be set to the timestamp of the most
|
||||
//! recent attempt.
|
||||
int upload_attempts;
|
||||
};
|
||||
|
||||
//! \brief A crash report that is in the process of being written.
|
||||
//!
|
||||
//! An instance of this struct should be created via PrepareNewCrashReport()
|
||||
//! and destroyed with FinishedWritingCrashReport().
|
||||
struct NewReport {
|
||||
//! The file handle to which the report should be written.
|
||||
FileHandle handle;
|
||||
|
||||
//! The path to the crash report being written.
|
||||
base::FilePath path;
|
||||
};
|
||||
|
||||
//! \brief The result code for operations performed on a database.
|
||||
enum OperationStatus {
|
||||
//! \brief No error occurred.
|
||||
kNoError = 0,
|
||||
|
||||
//! \brief The report that was requested could not be located.
|
||||
kReportNotFound,
|
||||
|
||||
//! \brief An error occured while performing a file operation on a crash
|
||||
//! report.
|
||||
//!
|
||||
//! A database is responsible for managing both the metadata about a report
|
||||
//! and the actual crash report itself. This error is returned when an
|
||||
//! error occurred when managing the report file. Additional information
|
||||
//! will be logged.
|
||||
kFileSystemError,
|
||||
|
||||
//! \brief An error occured while recording metadata for a crash report.
|
||||
//!
|
||||
//! A database is responsible for managing both the metadata about a report
|
||||
//! and the actual crash report itself. This error is returned when an
|
||||
//! error occurred when managing the metadata about a crash report.
|
||||
//! Additional information will be logged.
|
||||
kDatabaseError,
|
||||
|
||||
//! \brief The operation could not be completed because a concurrent
|
||||
//! operation affecting the report is occurring.
|
||||
kBusyError,
|
||||
};
|
||||
|
||||
virtual ~CrashReportDatabase() {}
|
||||
|
||||
//! \brief Initializes a database of crash reports.
|
||||
//!
|
||||
//! \param[in] path A path to a writable directory, where the database can
|
||||
//! be created or opened.
|
||||
//!
|
||||
//! \return A database object on success, `nullptr` on failure with an error
|
||||
//! logged.
|
||||
static scoped_ptr<CrashReportDatabase> Initialize(const base::FilePath& path);
|
||||
|
||||
//! \brief Creates a record of a new crash report.
|
||||
//!
|
||||
//! Callers can then write the crash report using the file handle provided.
|
||||
//! The caller does not own this handle, and it must be explicitly closed with
|
||||
//! FinishedWritingCrashReport().
|
||||
//!
|
||||
//! \param[out] report A file handle to which the crash report data should be
|
||||
//! written. Only valid if this returns #kNoError. The caller must not
|
||||
//! close this handle.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus PrepareNewCrashReport(NewReport** report) = 0;
|
||||
|
||||
//! \brief Informs the database that a crash report has been written.
|
||||
//!
|
||||
//! After calling this method, the database is permitted to move and rename
|
||||
//! the file at Report::file_path.
|
||||
//!
|
||||
//! \param[in] report A handle obtained with PrepareNewCrashReport(). The
|
||||
//! handle will be invalidated as part of this call.
|
||||
//! \param[out] uuid The UUID of this crash report.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus FinishedWritingCrashReport(NewReport* report,
|
||||
UUID* uuid) = 0;
|
||||
|
||||
//! \brief Returns the crash report record for the unique identifier.
|
||||
//!
|
||||
//! \param[in] uuid The crash report record unique identifier.
|
||||
//! \param[out] report A crash report record. Only valid if this returns
|
||||
//! #kNoError.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus LookUpCrashReport(const UUID& uuid,
|
||||
Report* report) = 0;
|
||||
|
||||
//! \brief Returns a list of crash report records that have not been uploaded.
|
||||
//!
|
||||
//! \param[out] reports A list of crash report record objects. This must be
|
||||
//! empty on entry. Only valid if this returns #kNoError.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus GetPendingReports(
|
||||
std::vector<const Report>* reports) = 0;
|
||||
|
||||
//! \brief Returns a list of crash report records that have been completed,
|
||||
//! either by being uploaded or by skipping upload.
|
||||
//!
|
||||
//! \param[out] reports A list of crash report record objects. This must be
|
||||
//! empty on entry. Only valid if this returns #kNoError.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus GetCompletedReports(
|
||||
std::vector<const Report>* reports) = 0;
|
||||
|
||||
//! \brief Obtains a report object for uploading to a collection server.
|
||||
//!
|
||||
//! The file at Report::file_path should be uploaded by the caller, and then
|
||||
//! the returned Report object must be disposed of via a call to
|
||||
//! RecordUploadAttempt().
|
||||
//!
|
||||
//! A subsequent call to this method with the same \a uuid is illegal until
|
||||
//! RecordUploadAttempt() has been called.
|
||||
//!
|
||||
//! \param[in] uuid The unique identifier for the crash report record.
|
||||
//! \param[out] report A crash report record for the report to be uploaded.
|
||||
//! The caller does not own this object. Only valid if this returns
|
||||
//! #kNoError.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus GetReportForUploading(const UUID& uuid,
|
||||
const Report** report) = 0;
|
||||
|
||||
//! \brief Adjusts a crash report record’s metadata to account for an upload
|
||||
//! attempt.
|
||||
//!
|
||||
//! After calling this method, the database is permitted to move and rename
|
||||
//! the file at Report::file_path.
|
||||
//!
|
||||
//! \param[in] report The report object obtained from
|
||||
//! GetReportForUploading(). This object is invalidated after this call.
|
||||
//! \param[in] successful Whether the upload attempt was successful.
|
||||
//! \param[in] id The identifier assigned to this crash report by the
|
||||
//! collection server. Must be empty if \a successful is `false`; may be
|
||||
//! empty if it is `true`.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus RecordUploadAttempt(const Report* report,
|
||||
bool successful,
|
||||
const std::string& id) = 0;
|
||||
|
||||
//! \brief Moves a report from the pending state to the completed state, but
|
||||
//! without the report being uploaded.
|
||||
//!
|
||||
//! This can be used if the user has disabled crash report collection, but
|
||||
//! crash generation is still enabled in the product.
|
||||
//!
|
||||
//! \param[in] uuid The unique identifier for the crash report record.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus SkipReportUpload(const UUID& uuid) = 0;
|
||||
|
||||
protected:
|
||||
CrashReportDatabase() {}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashReportDatabase);
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_
|
541
client/crash_report_database_mac.mm
Normal file
541
client/crash_report_database_mac.mm
Normal file
@ -0,0 +1,541 @@
|
||||
// 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 <errno.h>
|
||||
#include <fcntl.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <uuid/uuid.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
#include "base/scoped_generic.h"
|
||||
#include "base/strings/string_piece.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/mac/xattr.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
||||
const char kDatabaseDirectoryName[] = "Crashpad";
|
||||
|
||||
const char kWriteDirectory[] = "new";
|
||||
const char kUploadPendingDirectory[] = "pending";
|
||||
const char kCompletedDirectory[] = "completed";
|
||||
|
||||
const char* const kReportDirectories[] = {
|
||||
kWriteDirectory,
|
||||
kUploadPendingDirectory,
|
||||
kCompletedDirectory,
|
||||
};
|
||||
|
||||
const char kCrashReportFileExtension[] = "dmp";
|
||||
|
||||
const char kXattrUUID[] = "uuid";
|
||||
const char kXattrCollectorID[] = "id";
|
||||
const char kXattrCreationTime[] = "creation_time";
|
||||
const char kXattrIsUploaded[] = "uploaded";
|
||||
const char kXattrLastUploadTime[] = "last_upload_time";
|
||||
const char kXattrUploadAttemptCount[] = "upload_count";
|
||||
|
||||
const char kXattrDatabaseInitialized[] = "initialized";
|
||||
|
||||
// Ensures that the node at |path| is a directory, and creates it if it does
|
||||
// not exist. If the |path| points to a file, rather than a directory, or the
|
||||
// directory could not be created, returns false. Otherwise, returns true,
|
||||
// indicating that |path| already was or now is a directory.
|
||||
bool CreateOrEnsureDirectoryExists(const base::FilePath& path) {
|
||||
if (mkdir(path.value().c_str(), 0755) == 0) {
|
||||
return true;
|
||||
} else if (errno == EEXIST) {
|
||||
struct stat st;
|
||||
if (stat(path.value().c_str(), &st) != 0) {
|
||||
PLOG(ERROR) << "stat";
|
||||
return false;
|
||||
}
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
return true;
|
||||
} else {
|
||||
LOG(ERROR) << "not a directory";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
PLOG(ERROR) << "mkdir";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//! \brief A CrashReportDatabase that uses HFS+ extended attributes to store
|
||||
//! report metadata.
|
||||
//!
|
||||
//! The database maintains three directories of reports: `"new"` to hold crash
|
||||
//! reports that are in the process of being written, `"completed"` to hold
|
||||
//! reports that have been written and are awaing upload, and `"uploaded"` to
|
||||
//! hold reports successfully uploaded to a collection server. If the user has
|
||||
//! opted out of report collection, reports will still be written and moved
|
||||
//! to the completed directory, but they just will not be uploaded.
|
||||
//!
|
||||
//! The database stores its metadata in extended filesystem attributes. To
|
||||
//! ensure safe access, the report file is locked using `O_EXLOCK` during all
|
||||
//! extended attribute operations. The lock should be obtained using
|
||||
//! ObtainReportLock().
|
||||
class CrashReportDatabaseMac : public CrashReportDatabase {
|
||||
public:
|
||||
explicit CrashReportDatabaseMac(const base::FilePath& path);
|
||||
virtual ~CrashReportDatabaseMac();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
// CrashReportDatabase:
|
||||
OperationStatus PrepareNewCrashReport(NewReport** report) override;
|
||||
OperationStatus FinishedWritingCrashReport(NewReport* report,
|
||||
UUID* uuid) override;
|
||||
OperationStatus LookUpCrashReport(const UUID& uuid,
|
||||
Report* report) override;
|
||||
OperationStatus GetPendingReports(
|
||||
std::vector<const Report>* reports) override;
|
||||
OperationStatus GetCompletedReports(
|
||||
std::vector<const Report>* reports) override;
|
||||
OperationStatus GetReportForUploading(const UUID& uuid,
|
||||
const Report** report) override;
|
||||
OperationStatus RecordUploadAttempt(const Report* report,
|
||||
bool successful,
|
||||
const std::string& id) override;
|
||||
OperationStatus SkipReportUpload(const UUID& uuid) override;
|
||||
|
||||
private:
|
||||
//! \brief A private extension of the Report class that maintains bookkeeping
|
||||
//! information of the database.
|
||||
struct UploadReport : public Report {
|
||||
//! \brief Stores the flock of the file for the duration of
|
||||
//! GetReportForUploading() and RecordUploadAttempt().
|
||||
int lock_fd;
|
||||
};
|
||||
|
||||
//! \brief Locates a crash report in the database by UUID.
|
||||
//!
|
||||
//! \param[in] uuid The UUID of the crash report to locate.
|
||||
//!
|
||||
//! \return The full path to the report file, or an empty path if it cannot be
|
||||
//! found.
|
||||
base::FilePath LocateCrashReport(const UUID& uuid);
|
||||
|
||||
//! \brief Obtains an exclusive advisory lock on a file.
|
||||
//!
|
||||
//! The flock is used to prevent cross-process concurrent metadata reads or
|
||||
//! writes. While xattrs do not observe the lock, if the lock-then-mutate
|
||||
//! protocol is observed by all clients of the database, it still enforces
|
||||
//! synchronization.
|
||||
//!
|
||||
//! This does not block, and so callers must ensure that the lock is valid
|
||||
//! after calling.
|
||||
//!
|
||||
//! \param[in] path The path of the file to lcok.
|
||||
//!
|
||||
//! \return A scoped lock object. If the result is not valid, an error is
|
||||
//! logged.
|
||||
static base::ScopedFD ObtainReportLock(const base::FilePath& path);
|
||||
|
||||
//! \brief Reads all the database xattrs from a file into a Report. The file
|
||||
//! must be locked with ObtainReportLock.
|
||||
//!
|
||||
//! \param[in] path The path of the report.
|
||||
//! \param[out] report The object into which data will be read.
|
||||
//!
|
||||
//! \return `true` if all the metadata was read successfully, `false`
|
||||
//! otherwise.
|
||||
static bool ReadReportMetadataLocked(const base::FilePath& path,
|
||||
Report* report);
|
||||
|
||||
//! \brief Reads the metadata from all the reports in a database subdirectory.
|
||||
//! Invalid reports are skipped.
|
||||
//!
|
||||
//! \param[in] path The database subdirectory path.
|
||||
//! \param[out] reports An empty vector of reports, which will be filled.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
static OperationStatus ReportsInDirectory(const base::FilePath& path,
|
||||
std::vector<const Report>* reports);
|
||||
|
||||
|
||||
//! \brief Creates a database xattr name from the short constant name.
|
||||
//!
|
||||
//! \param[in] name The short name of the extended attribute.
|
||||
//!
|
||||
//! \return The long name of the extended attribute.
|
||||
static std::string XattrName(const base::StringPiece& name);
|
||||
|
||||
base::FilePath base_dir_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseMac);
|
||||
};
|
||||
|
||||
CrashReportDatabaseMac::CrashReportDatabaseMac(const base::FilePath& path)
|
||||
: CrashReportDatabase(), base_dir_(path) {
|
||||
}
|
||||
|
||||
CrashReportDatabaseMac::~CrashReportDatabaseMac() {}
|
||||
|
||||
bool CrashReportDatabaseMac::Initialize() {
|
||||
// Check if the database already exists.
|
||||
if (!CreateOrEnsureDirectoryExists(base_dir_))
|
||||
return false;
|
||||
|
||||
// Create the three processing directories for the database.
|
||||
for (size_t i = 0; i < arraysize(kReportDirectories); ++i) {
|
||||
if (!CreateOrEnsureDirectoryExists(base_dir_.Append(kReportDirectories[i])))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write an xattr as the last step, to ensure the filesystem has support for
|
||||
// them. This attribute will never be read.
|
||||
return WriteXattrBool(base_dir_, XattrName(kXattrDatabaseInitialized), true);
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::PrepareNewCrashReport(NewReport** out_report) {
|
||||
uuid_t uuid_gen;
|
||||
uuid_generate(uuid_gen);
|
||||
UUID uuid(uuid_gen);
|
||||
|
||||
scoped_ptr<NewReport> report(new NewReport());
|
||||
|
||||
report->path =
|
||||
base_dir_.Append(kWriteDirectory)
|
||||
.Append(uuid.ToString() + "." + kCrashReportFileExtension);
|
||||
|
||||
report->handle = HANDLE_EINTR(open(report->path.value().c_str(),
|
||||
O_CREAT | O_WRONLY | O_EXCL | O_EXLOCK,
|
||||
0600));
|
||||
if (report->handle < 0) {
|
||||
PLOG(ERROR) << "open " << report->path.value();
|
||||
return kFileSystemError;
|
||||
}
|
||||
|
||||
// TODO(rsesek): Potentially use an fsetxattr() here instead.
|
||||
if (!WriteXattr(report->path, XattrName(kXattrUUID), uuid.ToString())) {
|
||||
PLOG_IF(ERROR, IGNORE_EINTR(close(report->handle)) != 0) << "close";
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
*out_report = report.release();
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::FinishedWritingCrashReport(NewReport* report,
|
||||
UUID* uuid) {
|
||||
// Takes ownership of the |handle| and the O_EXLOCK.
|
||||
base::ScopedFD lock(report->handle);
|
||||
|
||||
// Take ownership of the report.
|
||||
scoped_ptr<NewReport> scoped_report(report);
|
||||
|
||||
// Get the report's UUID to return.
|
||||
std::string uuid_string;
|
||||
if (ReadXattr(report->path, XattrName(kXattrUUID),
|
||||
&uuid_string) != XattrStatus::kOK ||
|
||||
!uuid->InitializeFromString(uuid_string)) {
|
||||
LOG(ERROR) << "Failed to read UUID for crash report "
|
||||
<< report->path.value();
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
// Record the creation time of this report.
|
||||
if (!WriteXattrTimeT(report->path, XattrName(kXattrCreationTime),
|
||||
time(nullptr))) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
// Move the report to its new location for uploading.
|
||||
base::FilePath new_path =
|
||||
base_dir_.Append(kUploadPendingDirectory).Append(report->path.BaseName());
|
||||
if (rename(report->path.value().c_str(), new_path.value().c_str()) != 0) {
|
||||
PLOG(ERROR) << "rename " << report->path.value() << " to "
|
||||
<< new_path.value();
|
||||
return kFileSystemError;
|
||||
}
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::LookUpCrashReport(const UUID& uuid,
|
||||
CrashReportDatabase::Report* report) {
|
||||
base::FilePath path = LocateCrashReport(uuid);
|
||||
if (path.empty())
|
||||
return kReportNotFound;
|
||||
|
||||
base::ScopedFD lock(ObtainReportLock(path));
|
||||
if (!lock.is_valid())
|
||||
return kBusyError;
|
||||
|
||||
*report = Report();
|
||||
report->file_path = path;
|
||||
if (!ReadReportMetadataLocked(path, report))
|
||||
return kDatabaseError;
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::GetPendingReports(
|
||||
std::vector<const CrashReportDatabase::Report>* reports) {
|
||||
return ReportsInDirectory(base_dir_.Append(kUploadPendingDirectory), reports);
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::GetCompletedReports(
|
||||
std::vector<const CrashReportDatabase::Report>* reports) {
|
||||
return ReportsInDirectory(base_dir_.Append(kCompletedDirectory), reports);
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::GetReportForUploading(const UUID& uuid,
|
||||
const Report** report) {
|
||||
base::FilePath report_path = LocateCrashReport(uuid);
|
||||
if (report_path.empty())
|
||||
return kReportNotFound;
|
||||
|
||||
scoped_ptr<UploadReport> upload_report(new UploadReport());
|
||||
upload_report->file_path = report_path;
|
||||
|
||||
base::ScopedFD lock(ObtainReportLock(report_path));
|
||||
if (!lock.is_valid())
|
||||
return kBusyError;
|
||||
|
||||
if (!ReadReportMetadataLocked(report_path, upload_report.get()))
|
||||
return kDatabaseError;
|
||||
|
||||
upload_report->lock_fd = lock.release();
|
||||
*report = upload_report.release();
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::RecordUploadAttempt(const Report* report,
|
||||
bool successful,
|
||||
const std::string& id) {
|
||||
DCHECK(report);
|
||||
DCHECK(successful || id.empty());
|
||||
|
||||
base::FilePath report_path = LocateCrashReport(report->uuid);
|
||||
if (report_path.empty())
|
||||
return kReportNotFound;
|
||||
|
||||
scoped_ptr<const UploadReport> upload_report(
|
||||
static_cast<const UploadReport*>(report));
|
||||
|
||||
base::ScopedFD lock(upload_report->lock_fd);
|
||||
if (!lock.is_valid())
|
||||
return kBusyError;
|
||||
|
||||
if (successful) {
|
||||
base::FilePath new_path =
|
||||
base_dir_.Append(kCompletedDirectory).Append(report_path.BaseName());
|
||||
if (rename(report_path.value().c_str(), new_path.value().c_str()) != 0) {
|
||||
PLOG(ERROR) << "rename " << report_path.value() << " to "
|
||||
<< new_path.value();
|
||||
return kFileSystemError;
|
||||
}
|
||||
report_path = new_path;
|
||||
}
|
||||
|
||||
if (!WriteXattrBool(report_path, XattrName(kXattrIsUploaded), successful)) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
if (!WriteXattr(report_path, XattrName(kXattrCollectorID), id)) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
if (!WriteXattrTimeT(report_path,
|
||||
XattrName(kXattrLastUploadTime),
|
||||
time(nullptr))) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
int upload_attempts = 0;
|
||||
std::string name = XattrName(kXattrUploadAttemptCount);
|
||||
if (ReadXattrInt(report_path, name, &upload_attempts) ==
|
||||
XattrStatus::kOtherError) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
if (!WriteXattrInt(report_path, name, ++upload_attempts)) {
|
||||
return kDatabaseError;
|
||||
}
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
CrashReportDatabase::OperationStatus CrashReportDatabaseMac::SkipReportUpload(
|
||||
const UUID& uuid) {
|
||||
base::FilePath report_path = LocateCrashReport(uuid);
|
||||
if (report_path.empty())
|
||||
return kReportNotFound;
|
||||
|
||||
base::ScopedFD lock(ObtainReportLock(report_path));
|
||||
if (!lock.is_valid())
|
||||
return kBusyError;
|
||||
|
||||
base::FilePath new_path =
|
||||
base_dir_.Append(kCompletedDirectory).Append(report_path.BaseName());
|
||||
if (rename(report_path.value().c_str(), new_path.value().c_str()) != 0) {
|
||||
PLOG(ERROR) << "rename " << report_path.value() << " to "
|
||||
<< new_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) {
|
||||
base::FilePath path =
|
||||
base_dir_.Append(kReportDirectories[i])
|
||||
.Append(target_uuid + "." + kCrashReportFileExtension);
|
||||
|
||||
// Test if the path exists.
|
||||
struct stat st;
|
||||
if (lstat(path.value().c_str(), &st)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that the UUID of the report matches.
|
||||
std::string uuid_string;
|
||||
if (ReadXattr(path, XattrName(kXattrUUID),
|
||||
&uuid_string) == XattrStatus::kOK &&
|
||||
uuid_string == target_uuid) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return base::FilePath();
|
||||
}
|
||||
|
||||
// static
|
||||
base::ScopedFD CrashReportDatabaseMac::ObtainReportLock(
|
||||
const base::FilePath& path) {
|
||||
int fd = HANDLE_EINTR(open(path.value().c_str(),
|
||||
O_RDONLY | O_EXLOCK | O_CLOEXEC | O_NONBLOCK));
|
||||
PLOG_IF(ERROR, fd < 0) << "open lock " << path.value();
|
||||
return base::ScopedFD(fd);
|
||||
}
|
||||
|
||||
// static
|
||||
bool CrashReportDatabaseMac::ReadReportMetadataLocked(
|
||||
const base::FilePath& path, Report* report) {
|
||||
std::string uuid_string;
|
||||
if (ReadXattr(path, XattrName(kXattrUUID),
|
||||
&uuid_string) != XattrStatus::kOK ||
|
||||
!report->uuid.InitializeFromString(uuid_string)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReadXattrTimeT(path, XattrName(kXattrCreationTime),
|
||||
&report->creation_time) != XattrStatus::kOK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
report->id = std::string();
|
||||
if (ReadXattr(path, XattrName(kXattrCollectorID),
|
||||
&report->id) == XattrStatus::kOtherError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
report->uploaded = false;
|
||||
if (ReadXattrBool(path, XattrName(kXattrIsUploaded),
|
||||
&report->uploaded) == XattrStatus::kOtherError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
report->last_upload_attempt_time = 0;
|
||||
if (ReadXattrTimeT(path, XattrName(kXattrLastUploadTime),
|
||||
&report->last_upload_attempt_time) ==
|
||||
XattrStatus::kOtherError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
report->upload_attempts = 0;
|
||||
if (ReadXattrInt(path, XattrName(kXattrUploadAttemptCount),
|
||||
&report->upload_attempts) == XattrStatus::kOtherError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::ReportsInDirectory(
|
||||
const base::FilePath& path,
|
||||
std::vector<const CrashReportDatabase::Report>* reports) {
|
||||
DCHECK(reports->empty());
|
||||
|
||||
NSError* error = nil;
|
||||
NSArray* paths = [[NSFileManager defaultManager]
|
||||
contentsOfDirectoryAtPath:base::SysUTF8ToNSString(path.value())
|
||||
error:&error];
|
||||
if (error) {
|
||||
LOG(ERROR) << "Failed to enumerate reports in directory " << path.value()
|
||||
<< ": " << [[error description] UTF8String];
|
||||
return kFileSystemError;
|
||||
}
|
||||
|
||||
reports->reserve([paths count]);
|
||||
for (NSString* entry in paths) {
|
||||
base::FilePath report_path = path.Append([entry fileSystemRepresentation]);
|
||||
base::ScopedFD lock(ObtainReportLock(report_path));
|
||||
if (!lock.is_valid())
|
||||
continue;
|
||||
|
||||
Report report;
|
||||
if (!ReadReportMetadataLocked(report_path, &report)) {
|
||||
LOG(WARNING) << "Failed to read report metadata for "
|
||||
<< report_path.value();
|
||||
continue;
|
||||
}
|
||||
reports->push_back(report);
|
||||
}
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
// static
|
||||
std::string CrashReportDatabaseMac::XattrName(const base::StringPiece& name) {
|
||||
return base::StringPrintf("com.googlecode.crashpad.%s", name.data());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
scoped_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
|
||||
const base::FilePath& path) {
|
||||
scoped_ptr<CrashReportDatabaseMac> database_mac(
|
||||
new CrashReportDatabaseMac(path.Append(kDatabaseDirectoryName)));
|
||||
if (!database_mac->Initialize())
|
||||
database_mac.reset();
|
||||
|
||||
return scoped_ptr<CrashReportDatabase>(database_mac.release());
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
420
client/crash_report_database_test.cc
Normal file
420
client/crash_report_database_test.cc
Normal file
@ -0,0 +1,420 @@
|
||||
// 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 <sys/stat.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/test/scoped_temp_dir.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
bool FileExistsAtPath(const base::FilePath& path) {
|
||||
#if defined(OS_POSIX)
|
||||
struct stat st;
|
||||
return lstat(path.value().c_str(), &st) == 0;
|
||||
#else
|
||||
#error "Not implemented"
|
||||
#endif
|
||||
}
|
||||
|
||||
void CreateFile(const base::FilePath& path) {
|
||||
FileHandle handle = LoggingOpenFileForWrite(path,
|
||||
FileWriteMode::kCreateOrFail,
|
||||
FilePermissions::kWorldReadable);
|
||||
ASSERT_GE(handle, 0);
|
||||
ASSERT_TRUE(
|
||||
LoggingWriteFile(handle, path.value().c_str(), path.value().length()));
|
||||
ASSERT_TRUE(LoggingCloseFile(handle));
|
||||
}
|
||||
|
||||
class CrashReportDatabaseTest : public testing::Test {
|
||||
protected:
|
||||
// testing::Test:
|
||||
void SetUp() override {
|
||||
db_ = CrashReportDatabase::Initialize(path());
|
||||
ASSERT_TRUE(db_.get());
|
||||
}
|
||||
|
||||
void ResetDatabase() {
|
||||
db_.reset();
|
||||
}
|
||||
|
||||
CrashReportDatabase* db() const { return db_.get(); }
|
||||
const base::FilePath& path() const { return temp_dir_.path(); }
|
||||
|
||||
void CreateCrashReport(CrashReportDatabase::Report* report) {
|
||||
CrashReportDatabase::NewReport* new_report;
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db_->PrepareNewCrashReport(&new_report));
|
||||
const char kTest[] = "test";
|
||||
ASSERT_TRUE(LoggingWriteFile(new_report->handle, kTest, sizeof(kTest)));
|
||||
|
||||
UUID uuid;
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db_->FinishedWritingCrashReport(new_report, &uuid));
|
||||
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db_->LookUpCrashReport(uuid, report));
|
||||
ExpectPreparedCrashReport(*report);
|
||||
ASSERT_TRUE(FileExistsAtPath(report->file_path));
|
||||
}
|
||||
|
||||
void UploadReport(const UUID& uuid, bool successful, const std::string& id) {
|
||||
const CrashReportDatabase::Report* report = nullptr;
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db_->GetReportForUploading(uuid, &report));
|
||||
EXPECT_TRUE(report);
|
||||
EXPECT_NE(UUID(), report->uuid);
|
||||
EXPECT_FALSE(report->file_path.empty());
|
||||
EXPECT_TRUE(FileExistsAtPath(report->file_path))
|
||||
<< report->file_path.value();
|
||||
EXPECT_GT(report->creation_time, 0);
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db_->RecordUploadAttempt(report, successful, id));
|
||||
}
|
||||
|
||||
void ExpectPreparedCrashReport(const CrashReportDatabase::Report& report) {
|
||||
EXPECT_NE(UUID(), report.uuid);
|
||||
EXPECT_FALSE(report.file_path.empty());
|
||||
EXPECT_TRUE(FileExistsAtPath(report.file_path)) << report.file_path.value();
|
||||
EXPECT_TRUE(report.id.empty());
|
||||
EXPECT_GT(report.creation_time, 0);
|
||||
EXPECT_FALSE(report.uploaded);
|
||||
EXPECT_EQ(0, report.last_upload_attempt_time);
|
||||
EXPECT_EQ(0, report.upload_attempts);
|
||||
}
|
||||
|
||||
private:
|
||||
ScopedTempDir temp_dir_;
|
||||
scoped_ptr<CrashReportDatabase> db_;
|
||||
};
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, Initialize) {
|
||||
// Initialize the database for the first time, creating it.
|
||||
EXPECT_TRUE(db());
|
||||
|
||||
// Close and reopen the database at the same path.
|
||||
ResetDatabase();
|
||||
EXPECT_FALSE(db());
|
||||
auto db = CrashReportDatabase::Initialize(path());
|
||||
EXPECT_TRUE(db.get());
|
||||
|
||||
std::vector<const CrashReportDatabase::Report> reports;
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError, db->GetPendingReports(&reports));
|
||||
EXPECT_TRUE(reports.empty());
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError, db->GetCompletedReports(&reports));
|
||||
EXPECT_TRUE(reports.empty());
|
||||
}
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, NewCrashReport) {
|
||||
CrashReportDatabase::NewReport* new_report;
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->PrepareNewCrashReport(&new_report));
|
||||
UUID uuid;
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->FinishedWritingCrashReport(new_report, &uuid));
|
||||
|
||||
CrashReportDatabase::Report report;
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->LookUpCrashReport(uuid, &report));
|
||||
ExpectPreparedCrashReport(report);
|
||||
|
||||
std::vector<const CrashReportDatabase::Report> reports;
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetPendingReports(&reports));
|
||||
ASSERT_EQ(1u, reports.size());
|
||||
EXPECT_EQ(report.uuid, reports[0].uuid);
|
||||
|
||||
reports.clear();
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetCompletedReports(&reports));
|
||||
EXPECT_TRUE(reports.empty());
|
||||
}
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, LookUpCrashReport) {
|
||||
UUID uuid;
|
||||
|
||||
{
|
||||
CrashReportDatabase::Report report;
|
||||
CreateCrashReport(&report);
|
||||
uuid = report.uuid;
|
||||
}
|
||||
|
||||
{
|
||||
CrashReportDatabase::Report report;
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->LookUpCrashReport(uuid, &report));
|
||||
EXPECT_EQ(uuid, report.uuid);
|
||||
EXPECT_NE(std::string::npos, report.file_path.value().find(path().value()));
|
||||
EXPECT_EQ("", report.id);
|
||||
EXPECT_FALSE(report.uploaded);
|
||||
EXPECT_EQ(0, report.last_upload_attempt_time);
|
||||
EXPECT_EQ(0, report.upload_attempts);
|
||||
}
|
||||
|
||||
UploadReport(uuid, true, "test");
|
||||
|
||||
{
|
||||
CrashReportDatabase::Report report;
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->LookUpCrashReport(uuid, &report));
|
||||
EXPECT_EQ(uuid, report.uuid);
|
||||
EXPECT_NE(std::string::npos, report.file_path.value().find(path().value()));
|
||||
EXPECT_EQ("test", report.id);
|
||||
EXPECT_TRUE(report.uploaded);
|
||||
EXPECT_NE(0, report.last_upload_attempt_time);
|
||||
EXPECT_EQ(1, report.upload_attempts);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, RecordUploadAttempt) {
|
||||
std::vector<CrashReportDatabase::Report> reports(3);
|
||||
CreateCrashReport(&reports[0]);
|
||||
CreateCrashReport(&reports[1]);
|
||||
CreateCrashReport(&reports[2]);
|
||||
|
||||
// Record two attempts: one successful, one not.
|
||||
UploadReport(reports[1].uuid, false, "");
|
||||
UploadReport(reports[2].uuid, true, "abc123");
|
||||
|
||||
std::vector<CrashReportDatabase::Report> query(3);
|
||||
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->LookUpCrashReport(reports[0].uuid, &query[0]));
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->LookUpCrashReport(reports[1].uuid, &query[1]));
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->LookUpCrashReport(reports[2].uuid, &query[2]));
|
||||
|
||||
EXPECT_EQ("", query[0].id);
|
||||
EXPECT_EQ("", query[1].id);
|
||||
EXPECT_EQ("abc123", query[2].id);
|
||||
|
||||
EXPECT_FALSE(query[0].uploaded);
|
||||
EXPECT_FALSE(query[1].uploaded);
|
||||
EXPECT_TRUE(query[2].uploaded);
|
||||
|
||||
EXPECT_EQ(0, query[0].last_upload_attempt_time);
|
||||
EXPECT_NE(0, query[1].last_upload_attempt_time);
|
||||
EXPECT_NE(0, query[2].last_upload_attempt_time);
|
||||
|
||||
EXPECT_EQ(0, query[0].upload_attempts);
|
||||
EXPECT_EQ(1, query[1].upload_attempts);
|
||||
EXPECT_EQ(1, query[2].upload_attempts);
|
||||
|
||||
// Attempt to upload and fail again.
|
||||
UploadReport(reports[1].uuid, false, "");
|
||||
|
||||
time_t report_2_upload_time = query[2].last_upload_attempt_time;
|
||||
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->LookUpCrashReport(reports[0].uuid, &query[0]));
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->LookUpCrashReport(reports[1].uuid, &query[1]));
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->LookUpCrashReport(reports[2].uuid, &query[2]));
|
||||
|
||||
EXPECT_FALSE(query[0].uploaded);
|
||||
EXPECT_FALSE(query[1].uploaded);
|
||||
EXPECT_TRUE(query[2].uploaded);
|
||||
|
||||
EXPECT_EQ(0, query[0].last_upload_attempt_time);
|
||||
EXPECT_GE(query[1].last_upload_attempt_time, report_2_upload_time);
|
||||
EXPECT_EQ(report_2_upload_time, query[2].last_upload_attempt_time);
|
||||
|
||||
EXPECT_EQ(0, query[0].upload_attempts);
|
||||
EXPECT_EQ(2, query[1].upload_attempts);
|
||||
EXPECT_EQ(1, query[2].upload_attempts);
|
||||
|
||||
// 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(CrashReportDatabase::kNoError,
|
||||
db()->LookUpCrashReport(reports[0].uuid, &query[0]));
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->LookUpCrashReport(reports[1].uuid, &query[1]));
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->LookUpCrashReport(reports[2].uuid, &query[2]));
|
||||
|
||||
EXPECT_FALSE(query[0].uploaded);
|
||||
EXPECT_TRUE(query[1].uploaded);
|
||||
EXPECT_TRUE(query[2].uploaded);
|
||||
|
||||
EXPECT_EQ(0, query[0].last_upload_attempt_time);
|
||||
EXPECT_GE(query[1].last_upload_attempt_time, report_1_upload_time);
|
||||
EXPECT_EQ(report_2_upload_time, query[2].last_upload_attempt_time);
|
||||
|
||||
EXPECT_EQ(0, query[0].upload_attempts);
|
||||
EXPECT_EQ(3, query[1].upload_attempts);
|
||||
EXPECT_EQ(1, query[2].upload_attempts);
|
||||
}
|
||||
|
||||
// This test covers both query functions since they are related.
|
||||
TEST_F(CrashReportDatabaseTest, GetCompletedAndNotUploadedReports) {
|
||||
std::vector<CrashReportDatabase::Report> 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<const CrashReportDatabase::Report> pending;
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetPendingReports(&pending));
|
||||
|
||||
std::vector<const CrashReportDatabase::Report> completed;
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetCompletedReports(&completed));
|
||||
|
||||
EXPECT_EQ(reports.size(), pending.size());
|
||||
EXPECT_EQ(0u, completed.size());
|
||||
|
||||
// Upload one report successfully.
|
||||
UploadReport(report_1_uuid, true, "report1");
|
||||
|
||||
pending.clear();
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetPendingReports(&pending));
|
||||
completed.clear();
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetCompletedReports(&completed));
|
||||
|
||||
EXPECT_EQ(4u, pending.size());
|
||||
ASSERT_EQ(1u, completed.size());
|
||||
|
||||
for (const auto& report : pending)
|
||||
EXPECT_NE(report_1_uuid, report.uuid);
|
||||
EXPECT_EQ(report_1_uuid, completed[0].uuid);
|
||||
EXPECT_EQ("report1", completed[0].id);
|
||||
EXPECT_EQ(true, completed[0].uploaded);
|
||||
EXPECT_GT(completed[0].last_upload_attempt_time, 0);
|
||||
EXPECT_EQ(1, completed[0].upload_attempts);
|
||||
|
||||
const CrashReportDatabase::Report completed_report_1 = completed[0];
|
||||
|
||||
// Fail to upload one report.
|
||||
UploadReport(report_2_uuid, false, "");
|
||||
|
||||
pending.clear();
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetPendingReports(&pending));
|
||||
completed.clear();
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetCompletedReports(&completed));
|
||||
|
||||
EXPECT_EQ(4u, pending.size());
|
||||
ASSERT_EQ(1u, completed.size());
|
||||
|
||||
for (const auto& report : pending) {
|
||||
if (report.upload_attempts != 0) {
|
||||
EXPECT_EQ(report_2_uuid, report.uuid);
|
||||
EXPECT_GT(report.last_upload_attempt_time, 0);
|
||||
EXPECT_FALSE(report.uploaded);
|
||||
EXPECT_TRUE(report.id.empty());
|
||||
}
|
||||
}
|
||||
|
||||
// Upload a second report.
|
||||
UploadReport(report_4_uuid, true, "report_4");
|
||||
|
||||
pending.clear();
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetPendingReports(&pending));
|
||||
completed.clear();
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetCompletedReports(&completed));
|
||||
|
||||
EXPECT_EQ(3u, pending.size());
|
||||
ASSERT_EQ(2u, completed.size());
|
||||
|
||||
// Succeed the failed report.
|
||||
UploadReport(report_2_uuid, true, "report 2");
|
||||
|
||||
pending.clear();
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetPendingReports(&pending));
|
||||
completed.clear();
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetCompletedReports(&completed));
|
||||
|
||||
EXPECT_EQ(2u, pending.size());
|
||||
ASSERT_EQ(3u, completed.size());
|
||||
|
||||
for (const auto& report : pending) {
|
||||
EXPECT_TRUE(report.uuid == report_0_uuid ||
|
||||
report.uuid == report_3_uuid);
|
||||
}
|
||||
|
||||
// Skip upload for one report.
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->SkipReportUpload(report_3_uuid));
|
||||
|
||||
pending.clear();
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetPendingReports(&pending));
|
||||
completed.clear();
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetCompletedReports(&completed));
|
||||
|
||||
ASSERT_EQ(1u, pending.size());
|
||||
ASSERT_EQ(4u, completed.size());
|
||||
|
||||
EXPECT_EQ(report_0_uuid, pending[0].uuid);
|
||||
|
||||
for (const auto& report : completed) {
|
||||
if (report.uuid == report_3_uuid) {
|
||||
EXPECT_FALSE(report.uploaded);
|
||||
EXPECT_EQ(0, report.upload_attempts);
|
||||
EXPECT_EQ(0, report.last_upload_attempt_time);
|
||||
} else {
|
||||
EXPECT_TRUE(report.uploaded);
|
||||
EXPECT_GT(report.upload_attempts, 0);
|
||||
EXPECT_GT(report.last_upload_attempt_time, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CrashReportDatabaseTest, DuelingUploads) {
|
||||
CrashReportDatabase::Report report;
|
||||
CreateCrashReport(&report);
|
||||
|
||||
const CrashReportDatabase::Report* upload_report;
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->GetReportForUploading(report.uuid, &upload_report));
|
||||
|
||||
const CrashReportDatabase::Report* upload_report_2 = nullptr;
|
||||
EXPECT_EQ(CrashReportDatabase::kBusyError,
|
||||
db()->GetReportForUploading(report.uuid, &upload_report_2));
|
||||
EXPECT_FALSE(upload_report_2);
|
||||
|
||||
EXPECT_EQ(CrashReportDatabase::kNoError,
|
||||
db()->RecordUploadAttempt(upload_report, true, ""));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
Loading…
x
Reference in New Issue
Block a user