Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(150)

Unified Diff: components/browser_watcher/postmortem_report_collector_unittest.cc

Issue 2339193003: A collector for postmortem reports (Closed)
Patch Set: Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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 {
Sigurður Ásgeirsson 2016/09/14 18:04:31 ah, this is an interface - sweet!
manzagop (departed) 2016/09/15 15:07:38 Acknowledged.
+ 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);
Sigurður Ásgeirsson 2016/09/14 18:04:31 is the serialized protobuf canonical? ISTR an upco
manzagop (departed) 2016/09/15 15:07:38 You're right, there is no deterministic iteration
+ return expected_serialized == actual_serialized;
+}
+
+} // namespace
+
+TEST(PostmortemReportCollectorTest, CollectAndSubmitForUpload) {
Sigurður Ásgeirsson 2016/09/14 18:04:32 IMHO there ought to be a test for the "stuck file"
manzagop (departed) 2016/09/15 15:07:38 Done.
+ // 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(
Sigurður Ásgeirsson 2016/09/14 18:04:31 nit: is there a reason why this can't be a plain-o
manzagop (departed) 2016/09/15 15:07:38 Done.
+ 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

Powered by Google App Engine
This is Rietveld 408576698