| 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 | 
|  |