| Index: components/browser_watcher/postmortem_report_collector_unittest.cc
|
| diff --git a/components/browser_watcher/postmortem_report_collector_unittest.cc b/components/browser_watcher/postmortem_report_collector_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a988ebad72bf6acf7cc91b0e2ee61652670cb52d
|
| --- /dev/null
|
| +++ b/components/browser_watcher/postmortem_report_collector_unittest.cc
|
| @@ -0,0 +1,375 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "components/browser_watcher/postmortem_report_collector.h"
|
| +
|
| +#include <stdint.h>
|
| +
|
| +#include <memory>
|
| +#include <set>
|
| +#include <string>
|
| +#include <utility>
|
| +#include <vector>
|
| +
|
| +#include "base/debug/activity_tracker.h"
|
| +#include "base/files/file.h"
|
| +#include "base/files/file_path.h"
|
| +#include "base/files/file_util.h"
|
| +#include "base/files/memory_mapped_file.h"
|
| +#include "base/files/scoped_file.h"
|
| +#include "base/files/scoped_temp_dir.h"
|
| +#include "base/memory/ptr_util.h"
|
| +#include "base/metrics/persistent_memory_allocator.h"
|
| +#include "base/threading/platform_thread.h"
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +#include "third_party/crashpad/crashpad/client/crash_report_database.h"
|
| +
|
| +namespace browser_watcher {
|
| +
|
| +using base::debug::Activity;
|
| +using base::debug::ActivityData;
|
| +using base::debug::GlobalActivityTracker;
|
| +using base::debug::ThreadActivityTracker;
|
| +using base::File;
|
| +using base::FilePersistentMemoryAllocator;
|
| +using base::MemoryMappedFile;
|
| +using base::PersistentMemoryAllocator;
|
| +using base::WrapUnique;
|
| +using crashpad::CrashReportDatabase;
|
| +using crashpad::Settings;
|
| +using crashpad::UUID;
|
| +using testing::_;
|
| +using testing::Return;
|
| +using testing::SetArgPointee;
|
| +
|
| +namespace {
|
| +
|
| +// Exposes a public constructor in order to create a dummy database.
|
| +class MockCrashReportDatabase : public CrashReportDatabase {
|
| + public:
|
| + MockCrashReportDatabase() {}
|
| + MOCK_METHOD0(GetSettings, Settings*());
|
| + MOCK_METHOD1(PrepareNewCrashReport,
|
| + CrashReportDatabase::CrashReportDatabase::OperationStatus(
|
| + NewReport** report));
|
| + MOCK_METHOD2(FinishedWritingCrashReport,
|
| + CrashReportDatabase::CrashReportDatabase::OperationStatus(
|
| + CrashReportDatabase::NewReport* report,
|
| + crashpad::UUID* uuid));
|
| + MOCK_METHOD1(ErrorWritingCrashReport,
|
| + CrashReportDatabase::CrashReportDatabase::OperationStatus(
|
| + NewReport* report));
|
| + MOCK_METHOD2(LookUpCrashReport,
|
| + CrashReportDatabase::CrashReportDatabase::OperationStatus(
|
| + const UUID& uuid,
|
| + Report* report));
|
| + MOCK_METHOD1(
|
| + GetPendingReports,
|
| + CrashReportDatabase::OperationStatus(std::vector<Report>* reports));
|
| + MOCK_METHOD1(
|
| + GetCompletedReports,
|
| + CrashReportDatabase::OperationStatus(std::vector<Report>* reports));
|
| + MOCK_METHOD2(GetReportForUploading,
|
| + CrashReportDatabase::OperationStatus(const UUID& uuid,
|
| + const Report** report));
|
| + MOCK_METHOD3(RecordUploadAttempt,
|
| + CrashReportDatabase::OperationStatus(const Report* report,
|
| + bool successful,
|
| + const std::string& id));
|
| + MOCK_METHOD1(SkipReportUpload,
|
| + CrashReportDatabase::OperationStatus(const UUID& uuid));
|
| + MOCK_METHOD1(DeleteReport,
|
| + CrashReportDatabase::OperationStatus(const UUID& uuid));
|
| + MOCK_METHOD1(RequestUpload,
|
| + CrashReportDatabase::OperationStatus(const UUID& uuid));
|
| +};
|
| +
|
| +// Used for testing CollectAndSubmitForUpload.
|
| +class MockPostmortemReportCollector : public PostmortemReportCollector {
|
| + public:
|
| + MockPostmortemReportCollector() {}
|
| +
|
| + // A function that returns a unique_ptr cannot be mocked, so mock a function
|
| + // that returns a raw pointer instead.
|
| + std::unique_ptr<StabilityReport> Collect(
|
| + const base::FilePath& debug_state_file) override {
|
| + return std::unique_ptr<StabilityReport>(CollectRaw(debug_state_file));
|
| + }
|
| +
|
| + MOCK_METHOD3(GetDebugStateFilePaths,
|
| + std::vector<base::FilePath>(
|
| + const base::FilePath& debug_info_dir,
|
| + const base::FilePath::StringType& debug_file_pattern,
|
| + const std::set<base::FilePath>&));
|
| + MOCK_METHOD1(CollectRaw, StabilityReport*(const base::FilePath&));
|
| + MOCK_METHOD4(WriteReportToMinidump,
|
| + bool(const StabilityReport& report,
|
| + const crashpad::UUID& client_id,
|
| + const crashpad::UUID& report_id,
|
| + base::PlatformFile minidump_file));
|
| +};
|
| +
|
| +// Checks if two proto messages are the same.
|
| +MATCHER_P(EqualsProto, message, "") {
|
| + std::string expected_serialized;
|
| + std::string actual_serialized;
|
| + message.SerializeToString(&expected_serialized);
|
| + arg.SerializeToString(&actual_serialized);
|
| + return expected_serialized == actual_serialized;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +TEST(PostmortemReportCollectorTest, CollectAndSubmitForUpload) {
|
| + // Create a dummy debug file, to validate deletion.
|
| + base::ScopedTempDir temp_dir;
|
| + ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
|
| + base::FilePath debug_file = temp_dir.path().AppendASCII("foo-1.pma");
|
| + {
|
| + base::ScopedFILE file(base::OpenFile(debug_file, "w"));
|
| + ASSERT_NE(file.get(), nullptr);
|
| + }
|
| + ASSERT_TRUE(base::PathExists(debug_file));
|
| + const base::FilePath::StringType debug_file_pattern(
|
| + FILE_PATH_LITERAL("foo-*.pma"));
|
| +
|
| + // Create mocks.
|
| + // Note: the database is owned by the collector.
|
| + std::unique_ptr<MockCrashReportDatabase> database(
|
| + new MockCrashReportDatabase());
|
| + MockPostmortemReportCollector collector;
|
| +
|
| + // Expect collection of the debug file paths.
|
| + std::set<base::FilePath> no_excluded_files;
|
| + std::vector<base::FilePath> debug_files{debug_file};
|
| +
|
| + EXPECT_CALL(collector, GetDebugStateFilePaths(
|
| + debug_file.DirName(), debug_file_pattern,
|
| + no_excluded_files)) // debug_file.DirName(),
|
| + // FILE_PATH_LITERAL("foo-*.pma"),no_excluded_files))
|
| + .Times(1)
|
| + .WillOnce(Return(debug_files));
|
| +
|
| + EXPECT_CALL(*database, GetSettings()).Times(1).WillOnce(Return(nullptr));
|
| +
|
| + // Expect collection to a proto of a single debug file.
|
| + // Note: caller takes ownership.
|
| + StabilityReport* stability_report = new StabilityReport();
|
| + EXPECT_CALL(collector, CollectRaw(debug_file))
|
| + .Times(1)
|
| + .WillOnce(Return(stability_report));
|
| +
|
| + // Expect the call to write the proto to a minidump. This involves requesting
|
| + // a report from the crashpad database, writing the report, then finalizing it
|
| + // with the database.
|
| + base::FilePath minidump_path = temp_dir.path().AppendASCII("foo-1.dmp");
|
| + base::File minidump_file(
|
| + minidump_path, base::File::FLAG_CREATE | base::File::File::FLAG_WRITE);
|
| + CrashReportDatabase::NewReport crashpad_report = {
|
| + minidump_file.GetPlatformFile(),
|
| + crashpad::UUID(UUID::InitializeWithNewTag{}), minidump_path};
|
| + EXPECT_CALL(*database, PrepareNewCrashReport(_))
|
| + .Times(1)
|
| + .WillOnce(DoAll(SetArgPointee<0>(&crashpad_report),
|
| + Return(CrashReportDatabase::kNoError)));
|
| +
|
| + EXPECT_CALL(collector,
|
| + WriteReportToMinidump(EqualsProto(*stability_report), _, _,
|
| + minidump_file.GetPlatformFile()))
|
| + .Times(1)
|
| + .WillOnce(Return(true));
|
| +
|
| + EXPECT_CALL(*database, FinishedWritingCrashReport(&crashpad_report, _))
|
| + .Times(1)
|
| + .WillOnce(Return(CrashReportDatabase::kNoError));
|
| +
|
| + // Run the test.
|
| + int postmortem_cnt = collector.CollectAndSubmitForUpload(
|
| + debug_file.DirName(), debug_file_pattern, no_excluded_files,
|
| + database.get());
|
| + ASSERT_EQ(1, postmortem_cnt);
|
| + ASSERT_FALSE(base::PathExists(debug_file));
|
| +}
|
| +
|
| +TEST(PostmortemReportCollectorTest, GetDebugStateFilePaths) {
|
| + base::ScopedTempDir temp_dir;
|
| + ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
|
| +
|
| + // Create files.
|
| + std::vector<base::FilePath> expected_paths;
|
| + std::set<base::FilePath> excluded_paths;
|
| + {
|
| + // Matches the pattern.
|
| + base::FilePath path = temp_dir.path().AppendASCII("foo1.pma");
|
| + base::ScopedFILE file(base::OpenFile(path, "w"));
|
| + ASSERT_NE(file.get(), nullptr);
|
| + expected_paths.push_back(path);
|
| +
|
| + // Matches the pattern, but is excluded.
|
| + path = temp_dir.path().AppendASCII("foo2.pma");
|
| + file.reset(base::OpenFile(path, "w"));
|
| + ASSERT_NE(file.get(), nullptr);
|
| + ASSERT_TRUE(excluded_paths.insert(path).second);
|
| +
|
| + // Matches the pattern.
|
| + path = temp_dir.path().AppendASCII("foo3.pma");
|
| + file.reset(base::OpenFile(path, "w"));
|
| + ASSERT_NE(file.get(), nullptr);
|
| + expected_paths.push_back(path);
|
| +
|
| + // Does not match the pattern.
|
| + path = temp_dir.path().AppendASCII("bar.baz");
|
| + file.reset(base::OpenFile(path, "w"));
|
| + ASSERT_NE(file.get(), nullptr);
|
| + }
|
| +
|
| + PostmortemReportCollector collector;
|
| + EXPECT_THAT(
|
| + collector.GetDebugStateFilePaths(
|
| + temp_dir.path(), FILE_PATH_LITERAL("foo*.pma"), excluded_paths),
|
| + testing::UnorderedElementsAreArray(expected_paths));
|
| +}
|
| +
|
| +TEST(PostmortemReportCollectorTest, CollectEmptyFile) {
|
| + // Create an empty file.
|
| + base::ScopedTempDir temp_dir;
|
| + ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
|
| + base::FilePath file_path = temp_dir.path().AppendASCII("empty.pma");
|
| + {
|
| + base::ScopedFILE file(base::OpenFile(file_path, "w"));
|
| + ASSERT_NE(file.get(), nullptr);
|
| + }
|
| + ASSERT_TRUE(PathExists(file_path));
|
| +
|
| + // Validate collection returns nullptr.
|
| + PostmortemReportCollector collector;
|
| + ASSERT_EQ(nullptr, collector.Collect(file_path));
|
| +}
|
| +
|
| +TEST(PostmortemReportCollectorTest, CollectRandomFile) {
|
| + // Create a file with content we don't expect to be valid for a debug file.
|
| + base::ScopedTempDir temp_dir;
|
| + ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
|
| + base::FilePath file_path = temp_dir.path().AppendASCII("invalid_content.pma");
|
| + {
|
| + base::ScopedFILE file(base::OpenFile(file_path, "w"));
|
| + ASSERT_NE(file.get(), nullptr);
|
| + // Assuming this size is greater than the minimum size of a debug file.
|
| + std::vector<uint8_t> data(1024);
|
| + for (size_t i = 0; i < data.size(); ++i)
|
| + data[i] = i % UINT8_MAX;
|
| + ASSERT_EQ(data.size(),
|
| + fwrite(&data.at(0), sizeof(uint8_t), data.size(), file.get()));
|
| + }
|
| + ASSERT_TRUE(PathExists(file_path));
|
| +
|
| + // Validate collection returns nullptr.
|
| + PostmortemReportCollector collector;
|
| + ASSERT_EQ(nullptr, collector.Collect(file_path));
|
| +}
|
| +
|
| +namespace {
|
| +
|
| +// Parameters for the activity tracking.
|
| +const size_t kFileSize = 2 * 1024;
|
| +const int kStackDepth = 4;
|
| +const uint64_t kAllocatorId = 0;
|
| +const char kAllocatorName[] = "PostmortemReportCollectorCollectionTest";
|
| +
|
| +} // namespace
|
| +
|
| +class PostmortemReportCollectorCollectionTest : public testing::Test {
|
| + public:
|
| + // Create a proper debug file.
|
| + void SetUp() override {
|
| + testing::Test::SetUp();
|
| +
|
| + // Create a file backed allocator.
|
| + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
|
| + debug_file_path_ = temp_dir_.path().AppendASCII("debug_file.pma");
|
| + std::unique_ptr<PersistentMemoryAllocator> allocator = CreateAllocator();
|
| + ASSERT_NE(nullptr, allocator);
|
| +
|
| + size_t tracker_mem_size =
|
| + ThreadActivityTracker::SizeForStackDepth(kStackDepth);
|
| + ASSERT_GT(kFileSize, tracker_mem_size);
|
| +
|
| + // Create some debug data using trackers.
|
| + std::unique_ptr<ThreadActivityTracker> tracker =
|
| + CreateTracker(allocator.get(), tracker_mem_size);
|
| + ASSERT_NE(nullptr, tracker);
|
| + ASSERT_TRUE(tracker->IsValid());
|
| +
|
| + const void* dummy_task_origin = reinterpret_cast<void*>(0xCAFE);
|
| + const int dummy_task_sequence_num = 42;
|
| + tracker->PushActivity(dummy_task_origin, Activity::ACT_TASK_RUN,
|
| + ActivityData::ForTask(dummy_task_sequence_num));
|
| +
|
| + // TODO(manzagop): flesh out the data (more trackers and content).
|
| + }
|
| +
|
| + std::unique_ptr<PersistentMemoryAllocator> CreateAllocator() {
|
| + // Create the memory mapped file.
|
| + std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile());
|
| + bool success = mmfile->Initialize(
|
| + File(debug_file_path_, File::FLAG_CREATE | File::FLAG_READ |
|
| + File::FLAG_WRITE | File::FLAG_SHARE_DELETE),
|
| + {0, static_cast<int64_t>(kFileSize)},
|
| + MemoryMappedFile::READ_WRITE_EXTEND);
|
| + if (!success || !mmfile->IsValid())
|
| + return nullptr;
|
| +
|
| + // Create a persistent memory allocator.
|
| + if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, true))
|
| + return nullptr;
|
| + return WrapUnique(new FilePersistentMemoryAllocator(
|
| + std::move(mmfile), kFileSize, kAllocatorId, kAllocatorName, false));
|
| + }
|
| +
|
| + std::unique_ptr<ThreadActivityTracker> CreateTracker(
|
| + PersistentMemoryAllocator* allocator,
|
| + size_t tracker_mem_size) {
|
| + // Allocate a block of memory for the tracker to use.
|
| + PersistentMemoryAllocator::Reference mem_reference = allocator->Allocate(
|
| + tracker_mem_size, GlobalActivityTracker::kTypeIdActivityTracker);
|
| + if (mem_reference == 0U)
|
| + return nullptr;
|
| +
|
| + // Get the memory's base address.
|
| + void* mem_base = allocator->GetAsObject<char>(
|
| + mem_reference, GlobalActivityTracker::kTypeIdActivityTracker);
|
| + if (mem_base == nullptr)
|
| + return nullptr;
|
| +
|
| + // Make the allocation iterable so it can be found by other processes.
|
| + allocator->MakeIterable(mem_reference);
|
| +
|
| + return WrapUnique(new ThreadActivityTracker(mem_base, tracker_mem_size));
|
| + }
|
| +
|
| + const base::FilePath& debug_file_path() const { return debug_file_path_; }
|
| +
|
| + private:
|
| + base::ScopedTempDir temp_dir_;
|
| + base::FilePath debug_file_path_;
|
| +};
|
| +
|
| +TEST_F(PostmortemReportCollectorCollectionTest, CollectSuccess) {
|
| + // Validate collection returns the expected report.
|
| + PostmortemReportCollector collector;
|
| + std::unique_ptr<StabilityReport> report =
|
| + collector.Collect(debug_file_path());
|
| + ASSERT_NE(nullptr, report);
|
| +
|
| + // Build the expected report.
|
| + StabilityReport expected_report;
|
| + ProcessState* process_state = expected_report.add_process_states();
|
| + ThreadState* thread_state = process_state->add_threads();
|
| + thread_state->set_thread_name(base::PlatformThread::GetName());
|
| +
|
| + ASSERT_EQ(expected_report.SerializeAsString(), report->SerializeAsString());
|
| +}
|
| +
|
| +} // namespace browser_watcher
|
|
|