Index: components/browser_watcher/postmortem_unittest.cc |
diff --git a/components/browser_watcher/postmortem_unittest.cc b/components/browser_watcher/postmortem_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..350394d3a5434c3fb14e82ad49d24aa7f9e30c9c |
--- /dev/null |
+++ b/components/browser_watcher/postmortem_unittest.cc |
@@ -0,0 +1,321 @@ |
+// 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.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" |
+ |
+namespace browser_watcher { |
+ |
+using base::debug::GlobalActivityTracker; |
+using base::debug::ThreadActivityTracker; |
+using base::File; |
+using base::FilePersistentMemoryAllocator; |
+using base::MemoryMappedFile; |
+using base::PersistentMemoryAllocator; |
+using base::WrapUnique; |
+using testing::_; |
+using testing::Return; |
+ |
+namespace { |
+ |
+class MockReporterDelegate |
+ : public PostmortemReportCollector::ReporterDelegate { |
+ public: |
+ MockReporterDelegate() {} |
+ MOCK_METHOD1(SubmitReportForUpload, void(const base::FilePath& minidump)); |
+}; |
+ |
+// Exposes GetDebugStateFilePaths for unit testing. |
+class TestPostmortemReportCollector : public PostmortemReportCollector { |
+ public: |
+ TestPostmortemReportCollector( |
+ const base::FilePath& debug_dir, |
+ const base::FilePath::StringType& debug_file_pattern, |
+ ReporterDelegate* delegate) |
+ : PostmortemReportCollector(debug_dir, debug_file_pattern, delegate) {} |
+ TestPostmortemReportCollector() : PostmortemReportCollector() {} |
+ |
+ using PostmortemReportCollector::GetDebugStateFilePaths; |
+ using PostmortemReportCollector::Collect; |
+}; |
+ |
+// Used for testing CollectAndSubmitForUpload. |
+class MockPostmortemReportCollector : public PostmortemReportCollector { |
+ public: |
+ MockPostmortemReportCollector( |
+ const base::FilePath& debug_dir, |
+ const base::FilePath::StringType& debug_file_pattern, |
+ ReporterDelegate* delegate) |
+ : PostmortemReportCollector(debug_dir, debug_file_pattern, delegate) {} |
+ |
+ // 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_METHOD1(GetDebugStateFilePaths, |
+ std::vector<base::FilePath>(const std::set<base::FilePath>&)); |
+ MOCK_METHOD1(CollectRaw, StabilityReport*(const base::FilePath&)); |
+ MOCK_METHOD2(CreateReport, |
+ bool(const StabilityReport& report, |
+ const base::FilePath& minidump_path)); |
+}; |
+ |
+// 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)); |
+ |
+ // Create mocks. |
+ MockReporterDelegate reporter; |
+ MockPostmortemReportCollector collector( |
+ debug_file.DirName(), FILE_PATH_LITERAL("foo-*.pma"), &reporter); |
+ |
+ // Set up expectations. |
+ std::set<base::FilePath> no_excluded_files; |
+ std::vector<base::FilePath> debug_files{debug_file}; |
+ EXPECT_CALL(collector, GetDebugStateFilePaths(no_excluded_files)) |
+ .Times(1) |
+ .WillOnce(Return(debug_files)); |
+ |
+ // Note: caller takes ownership. |
+ StabilityReport* stability_report = new StabilityReport(); |
+ EXPECT_CALL(collector, CollectRaw(debug_file)) |
+ .Times(1) |
+ .WillOnce(Return(stability_report)); |
+ |
+ base::FilePath expected_minidump_path = |
+ temp_dir.path().AppendASCII("foo-1.dmp"); |
+ EXPECT_CALL(collector, CreateReport(EqualsProto(*stability_report), |
+ expected_minidump_path)) |
+ .Times(1) |
+ .WillOnce(Return(true)); |
+ |
+ EXPECT_CALL(reporter, SubmitReportForUpload(expected_minidump_path)).Times(1); |
+ |
+ collector.CollectAndSubmitForUpload(no_excluded_files); |
+ 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); |
+ } |
+ |
+ MockReporterDelegate reporter; |
+ TestPostmortemReportCollector collector( |
+ temp_dir.path(), FILE_PATH_LITERAL("foo*.pma"), &reporter); |
+ |
+ EXPECT_THAT(collector.GetDebugStateFilePaths(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. |
+ TestPostmortemReportCollector 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. |
+ TestPostmortemReportCollector 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, ThreadActivityTracker::ACT_TASK_RUN, |
+ ThreadActivityTracker::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. |
+ TestPostmortemReportCollector 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 |