Chromium Code Reviews| Index: components/crash/content/app/fallback_crash_handler_win_unittest.cc |
| diff --git a/components/crash/content/app/fallback_crash_handler_win_unittest.cc b/components/crash/content/app/fallback_crash_handler_win_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..3898e27d21c3cec47a1536dfb95335379288f3ca |
| --- /dev/null |
| +++ b/components/crash/content/app/fallback_crash_handler_win_unittest.cc |
| @@ -0,0 +1,210 @@ |
| +// Copyright 2017 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/crash/content/app/fallback_crash_handler_win.h" |
| + |
| +#include "base/command_line.h" |
| +#include "base/files/file_path.h" |
| +#include "base/files/scoped_temp_dir.h" |
| +#include "base/process/process_handle.h" |
| +#include "base/strings/string_number_conversions.h" |
| +#include "base/test/multiprocess_test.h" |
| +#include "base/threading/platform_thread.h" |
| +#include "base/win/scoped_handle.h" |
| +#include "base/win/win_util.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| +#include "testing/multiprocess_func_list.h" |
| +#include "third_party/crashpad/crashpad/client/crash_report_database.h" |
| +#include "third_party/crashpad/crashpad/client/settings.h" |
| +#include "third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.h" |
| +#include "third_party/crashpad/crashpad/util/file/file_reader.h" |
| +#include "third_party/crashpad/crashpad/util/misc/uuid.h" |
| + |
| +namespace crash_reporter { |
| + |
| +namespace { |
| + |
| +// This is the main function for the process to dump. It's unwise to call |
| +// MinidumpWriteDump on one's own process, as that can hang or flake out in |
|
scottmg
2017/01/10 21:33:40
same, and another below
Sigurður Ásgeirsson
2017/01/10 21:56:12
Done.
|
| +// other ways. |
| +MULTIPROCESS_TEST_MAIN(FallbackCrashHandlerWinSleeper) { |
| + // Sleep forever, the parent will kill us. |
| + Sleep(INFINITE); |
| + return 0; |
| +} |
| + |
| +class FallbackCrashHandlerWinTest : public base::MultiProcessTest { |
| + public: |
| + FallbackCrashHandlerWinTest() : sleeper_handle_(base::kNullProcessHandle) { |
| + RtlCaptureContext(&context_); |
| + memset(&exception_, 0, sizeof(exception_)); |
| + exception_.ExceptionCode = EXCEPTION_ACCESS_VIOLATION; |
| + |
| + exc_ptrs_.ExceptionRecord = &exception_; |
| + exc_ptrs_.ContextRecord = &context_; |
| + } |
| + |
| + void SetUp() override { |
| + ASSERT_TRUE(database_dir_.CreateUniqueTempDir()); |
| + |
| + // Spawn the target sleeper process. |
| + sleeper_ = SpawnChild("FallbackCrashHandlerWinSleeper"); |
| + |
| + // Open a handle to the sleeper process. |
| + const DWORD kAccessMask = |
| + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_DUP_HANDLE; |
| + sleeper_handle_ = OpenProcess(kAccessMask, FALSE, sleeper_.Pid()); |
| + DWORD err = GetLastError(); |
| + EXPECT_NE(base::kNullProcessHandle, sleeper_handle_) << "GetLastError: " |
| + << err; |
| + } |
| + |
| + void TearDown() { |
| + // Terminate the sleeper, so that it doesn't leak. |
| + EXPECT_TRUE(sleeper_.Terminate(1, true)); |
| + |
| + if (sleeper_handle_ != base::kNullProcessHandle) { |
| + CloseHandle(sleeper_handle_); |
| + sleeper_handle_ = base::kNullProcessHandle; |
| + } |
| + } |
| + |
| + std::string ExcPtrsAsString() const { |
| + return base::UintToString(reinterpret_cast<uintptr_t>(&exc_ptrs_)); |
| + }; |
| + |
| + std::string SleeperHandleAsString() const { |
| + return base::UintToString(base::win::HandleToUint32(sleeper_handle_)); |
| + }; |
| + |
| + void CreateDatabase() { |
| + std::unique_ptr<crashpad::CrashReportDatabase> database = |
| + crashpad::CrashReportDatabase::InitializeWithoutCreating( |
| + database_dir_.GetPath()); |
| + } |
| + |
| + protected: |
| + CONTEXT context_; |
| + EXCEPTION_RECORD exception_; |
| + EXCEPTION_POINTERS exc_ptrs_; |
| + |
| + base::Process sleeper_; |
| + base::ProcessHandle sleeper_handle_; |
| + base::ScopedTempDir database_dir_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(FallbackCrashHandlerWinTest); |
| +}; |
| + |
| +} // namespace |
| + |
| +TEST_F(FallbackCrashHandlerWinTest, ParseCommandLine) { |
| + FallbackCrashHandler handler; |
| + |
| + // An empty command line shouldn't work. |
| + base::CommandLine cmd_line(base::FilePath(L"empty")); |
| + ASSERT_FALSE(handler.ParseCommandLine(cmd_line)); |
| + |
| + cmd_line.AppendSwitchPath("database", database_dir_.GetPath()); |
| + cmd_line.AppendSwitchASCII("exception-pointers", ExcPtrsAsString()); |
| + cmd_line.AppendSwitchASCII("process", SleeperHandleAsString()); |
| + |
| + // Thread missing, still should fail. |
| + ASSERT_FALSE(handler.ParseCommandLine(cmd_line)); |
| + |
| + cmd_line.AppendSwitchASCII( |
| + "thread", base::UintToString(base::PlatformThread::CurrentId())); |
| + |
| + // Should succeed with a fully populated command line. |
| + // Because of how handle ownership is guarded, we have to "disown" it before |
| + // the handler takes it over. |
| + EXPECT_TRUE(handler.ParseCommandLine(cmd_line)); |
| + sleeper_handle_ = base::kNullProcessHandle; |
| +} |
| + |
| +TEST_F(FallbackCrashHandlerWinTest, GenerateCrashDump) { |
| + FallbackCrashHandler handler; |
| + |
| + base::CommandLine cmd_line(base::FilePath(L"empty")); |
| + cmd_line.AppendSwitchPath("database", database_dir_.GetPath()); |
| + cmd_line.AppendSwitchASCII("exception-pointers", ExcPtrsAsString()); |
| + |
| + // TODO(siggi): It's probably safer to spawn a sacrificial process and then |
| + // terminate it. MinidumpWriteDump is alleged to occasionally hang if used |
| + // to dump own process. |
| + cmd_line.AppendSwitchASCII("process", SleeperHandleAsString()); |
| + cmd_line.AppendSwitchASCII( |
| + "thread", base::UintToString(base::PlatformThread::CurrentId())); |
| + |
| + // Because how handle ownership is guarded, we have to "disown" this before |
| + // the handler takes it over. |
| + ASSERT_TRUE(handler.ParseCommandLine(cmd_line)); |
| + sleeper_handle_ = base::kNullProcessHandle; |
| + |
| + const char kProduct[] = "SomeProduct"; |
| + const char kVersion[] = "1.2.3.6"; |
| + const char kChannel[] = "canary"; |
| + const char kProcessType[] = "Test"; |
| + |
| + EXPECT_TRUE( |
| + handler.GenerateCrashDump(kProduct, kVersion, kChannel, kProcessType)); |
| + |
| + // Validate that the database contains one valid crash dump. |
| + std::unique_ptr<crashpad::CrashReportDatabase> database = |
| + crashpad::CrashReportDatabase::InitializeWithoutCreating( |
| + database_dir_.GetPath()); |
| + |
| + std::vector<crashpad::CrashReportDatabase::Report> reports; |
| + ASSERT_EQ(crashpad::CrashReportDatabase::kNoError, |
| + database->GetPendingReports(&reports)); |
| + |
| + EXPECT_EQ(1U, reports.size()); |
| + |
| + // Validate crashpad can read the produced minidump. |
| + crashpad::FileReader minidump_file_reader; |
| + ASSERT_TRUE(minidump_file_reader.Open(reports[0].file_path)); |
| + |
| + crashpad::ProcessSnapshotMinidump minidump_process_snapshot; |
| + ASSERT_TRUE(minidump_process_snapshot.Initialize(&minidump_file_reader)); |
| + |
| + crashpad::UUID expected_client_id; |
| + ASSERT_TRUE(database->GetSettings()->GetClientID(&expected_client_id)); |
| + |
| + // Validate that the CrashpadInfo in the report contains the same basic |
| + // info, as does the database. |
| + crashpad::UUID client_id; |
| + minidump_process_snapshot.ClientID(&client_id); |
| + EXPECT_EQ(expected_client_id, client_id); |
| + |
| + crashpad::UUID report_id; |
| + minidump_process_snapshot.ReportID(&report_id); |
| + EXPECT_EQ(reports[0].uuid, report_id); |
| + |
| + std::map<std::string, std::string> parameters = |
| + minidump_process_snapshot.AnnotationsSimpleMap(); |
| + auto it = parameters.find("prod"); |
| + EXPECT_NE(parameters.end(), it); |
| + EXPECT_EQ(kProduct, it->second); |
| + |
| + it = parameters.find("ver"); |
| + EXPECT_NE(parameters.end(), it); |
| + EXPECT_EQ(kVersion, it->second); |
| + |
| + it = parameters.find("channel"); |
| + EXPECT_NE(parameters.end(), it); |
| + EXPECT_EQ(kChannel, it->second); |
| + |
| + it = parameters.find("plat"); |
| + EXPECT_NE(parameters.end(), it); |
| +#if defined(ARCH_CPU_64_BITS) |
| + EXPECT_EQ("Win64", it->second); |
| +#else |
| + EXPECT_EQ("Win32", it->second); |
| +#endif |
| + |
| + it = parameters.find("ptype"); |
| + EXPECT_NE(parameters.end(), it); |
| + EXPECT_EQ(kProcessType, it->second); |
| +} |
| + |
| +} // namespace crash_reporter |