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

Unified Diff: components/browser_watcher/postmortem_report_collector_unittest.cc

Issue 2339193003: A collector for postmortem reports (Closed)
Patch Set: Address nits 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..c717e10c5875f4b5089d256885a3de7a1b703da3
--- /dev/null
+++ b/components/browser_watcher/postmortem_report_collector_unittest.cc
@@ -0,0 +1,417 @@
+// 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.
+ CollectionStatus Collect(const base::FilePath& debug_state_file,
+ std::unique_ptr<StabilityReport>* report) override {
+ DCHECK_NE(nullptr, report);
+ report->reset(CollectRaw(debug_state_file));
+ return SUCCESS;
+ }
+
+ 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 based on their serializations. Note
+// this only works if serialization is deterministic, which is not guaranteed.
+// In practice, serialization is deterministic (even for protocol buffers with
+// maps) and such matchers are common in the Chromium code base. Also note that
+// in the context of this test, false positive matches are the problem and these
+// are not possible (otherwise serialization would be ambiguous). False
+// negatives would lead to test failure and developer action. Alternatives are:
+// 1) a generic matcher (likely not possible without reflections, missing from
+// lite runtime), 2) a specialized matcher or 3) implementing deterministic
+// serialization.
+// TODO(manzagop): switch a matcher with guarantees.
+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
+
+class PostmortemReportCollectorCollectAndSubmitTest : public testing::Test {
+ public:
+ void SetUp() override {
+ testing::Test::SetUp();
+ // Create a dummy debug file.
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ 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_));
+
+ // Expect collection of the debug file paths.
+ debug_file_pattern_ = FILE_PATH_LITERAL("foo-*.pma");
+ std::vector<base::FilePath> debug_files{debug_file_};
+ EXPECT_CALL(collector_,
+ GetDebugStateFilePaths(debug_file_.DirName(),
+ debug_file_pattern_, 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);
+ 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));
+ }
+
+ protected:
+ base::ScopedTempDir temp_dir_;
+ base::FilePath debug_file_;
+ MockCrashReportDatabase database_;
+ MockPostmortemReportCollector collector_;
+ base::FilePath::StringType debug_file_pattern_;
+ std::set<base::FilePath> no_excluded_files_;
+ CrashReportDatabase::NewReport crashpad_report_;
+};
+
+TEST_F(PostmortemReportCollectorCollectAndSubmitTest,
+ CollectAndSubmitForUpload) {
+ EXPECT_CALL(database_, FinishedWritingCrashReport(&crashpad_report_, _))
+ .Times(1)
+ .WillOnce(Return(CrashReportDatabase::kNoError));
+
+ // Run the test.
+ int success_cnt = collector_.CollectAndSubmitForUpload(
+ debug_file_.DirName(), debug_file_pattern_, no_excluded_files_,
+ &database_);
+ ASSERT_EQ(1, success_cnt);
+ ASSERT_FALSE(base::PathExists(debug_file_));
+}
+
+TEST_F(PostmortemReportCollectorCollectAndSubmitTest,
+ CollectAndSubmitForUploadStuckFile) {
+ // Open the stability debug file to prevent its deletion.
+ base::ScopedFILE file(base::OpenFile(debug_file_, "w"));
+ ASSERT_NE(file.get(), nullptr);
+
+ // Expect Crashpad is notified of an error writing the crash report.
+ EXPECT_CALL(database_, ErrorWritingCrashReport(&crashpad_report_))
+ .Times(1)
+ .WillOnce(Return(CrashReportDatabase::kNoError));
+
+ // Run the test.
+ int success_cnt = collector_.CollectAndSubmitForUpload(
+ debug_file_.DirName(), debug_file_pattern_, no_excluded_files_,
+ &database_);
+ ASSERT_EQ(0, success_cnt);
+ ASSERT_TRUE(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: an empty file cannot suppport an analyzer.
+ PostmortemReportCollector collector;
+ std::unique_ptr<StabilityReport> report;
+ ASSERT_EQ(PostmortemReportCollector::ANALYZER_CREATION_FAILED,
+ collector.Collect(file_path, &report));
+}
+
+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: random content appears as though there is not
+ // stability data.
+ PostmortemReportCollector collector;
+ std::unique_ptr<StabilityReport> report;
+ ASSERT_EQ(PostmortemReportCollector::DEBUG_FILE_NO_DATA,
+ collector.Collect(file_path, &report));
+}
+
+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;
+ ASSERT_EQ(PostmortemReportCollector::SUCCESS,
+ collector.Collect(debug_file_path(), &report));
+ 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