From bdfd147a47f4fcb44d971a743323f5f870f92805 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Wed, 5 Nov 2014 18:15:19 -0500 Subject: [PATCH] Add MinidumpMiscInfoWriter::InitializeFromSnapshot() and its test. TEST=minidump_test MinidumpMiscInfoWriter.InitializeFromSnapshot R=rsesek@chromium.org Review URL: https://codereview.chromium.org/701783004 --- minidump/minidump_misc_info_writer.cc | 181 ++++++++++++++++++- minidump/minidump_misc_info_writer.h | 29 ++- minidump/minidump_misc_info_writer_test.cc | 94 +++++++++- minidump/minidump_system_info_writer_test.cc | 1 - snapshot/snapshot.gyp | 2 + snapshot/test/test_process_snapshot.cc | 87 +++++++++ snapshot/test/test_process_snapshot.h | 121 +++++++++++++ 7 files changed, 510 insertions(+), 5 deletions(-) create mode 100644 snapshot/test/test_process_snapshot.cc create mode 100644 snapshot/test/test_process_snapshot.h diff --git a/minidump/minidump_misc_info_writer.cc b/minidump/minidump_misc_info_writer.cc index 9f829fa4..a839ab04 100644 --- a/minidump/minidump_misc_info_writer.cc +++ b/minidump/minidump_misc_info_writer.cc @@ -14,14 +14,122 @@ #include "minidump/minidump_misc_info_writer.h" +#include + #include "base/logging.h" #include "base/numerics/safe_conversions.h" +#include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" #include "minidump/minidump_writer_util.h" +#include "package.h" +#include "snapshot/process_snapshot.h" +#include "snapshot/system_snapshot.h" #include "util/file/file_writer.h" +#include "util/numeric/in_range_cast.h" #include "util/numeric/safe_assignment.h" +#if defined(OS_MACOSX) +#include +#endif + namespace crashpad { +namespace { + +uint32_t TimevalToRoundedSeconds(const timeval& tv) { + uint32_t seconds = + InRangeCast(tv.tv_sec, std::numeric_limits::max()); + const int kMicrosecondsPerSecond = 1E6; + if (tv.tv_usec >= kMicrosecondsPerSecond / 2 && + seconds != std::numeric_limits::max()) { + ++seconds; + } + return seconds; +} + +// For MINIDUMP_MISC_INFO_4::BuildString. dbghelp only places OS version +// information here, but if a machine description is also available, this is the +// only reasonable place in a minidump file to put it. +std::string BuildString(const SystemSnapshot* system_snapshot) { + std::string os_version_full = system_snapshot->OSVersionFull(); + std::string machine_description = system_snapshot->MachineDescription(); + if (!os_version_full.empty()) { + if (!machine_description.empty()) { + return base::StringPrintf( + "%s; %s", os_version_full.c_str(), machine_description.c_str()); + } + return os_version_full; + } + return machine_description; +} + +#if defined(OS_MACOSX) +// Converts the value of the MAC_OS_VERSION_MIN_REQUIRED or +// MAC_OS_X_VERSION_MAX_ALLOWED macro from to a number +// identifying the minor Mac OS X version that it represents. For example, with +// an argument of MAC_OS_X_VERSION_10_6, this function will return 6. +int AvailabilityVersionToMacOSXMinorVersion(int availability) { + // Through MAC_OS_X_VERSION_10_9, the minor version is the tens digit. + if (availability >= 1000 && availability <= 1099) { + return (availability / 10) % 10; + } + + // After MAC_OS_X_VERSION_10_9, the older format was insufficient to represent + // versions. Since then, the minor version is the thousands and hundreds + // digits. + if (availability >= 100000 && availability <= 109999) { + return (availability / 100) % 100; + } + + return 0; +} +#endif + +} // namespace + +namespace internal { + +// For MINIDUMP_MISC_INFO_4::DbgBldStr. dbghelp produces strings like +// “dbghelp.i386,6.3.9600.16520” and “dbghelp.amd64,6.3.9600.16520”. Mimic that +// format, and add the OS that wrote the minidump along with any relevant +// platform-specific data describing the compilation environment. +std::string MinidumpMiscInfoDebugBuildString() { + // Caution: the minidump file format only has room for 39 UTF-16 code units + // plus a UTF-16 NUL terminator. Don’t let strings get longer than this, or + // they will be truncated and a message will be logged. +#if defined(OS_MACOSX) + const char kOS[] = "mac"; +#elif defined(OS_LINUX) + const char kOS[] = "linux"; +#else +#error define kOS for this operating system +#endif + +#if defined(ARCH_CPU_X86) + const char kCPU[] = "i386"; +#elif defined(ARCH_CPU_X86_64) + const char kCPU[] = "amd64"; +#else +#error define kCPU for this CPU +#endif + + std::string debug_build_string = base::StringPrintf("%s.%s,%s,%s", + PACKAGE_TARNAME, + kCPU, + PACKAGE_VERSION, + kOS); + +#if defined(OS_MACOSX) + debug_build_string += base::StringPrintf( + ",%d,%d", + AvailabilityVersionToMacOSXMinorVersion(MAC_OS_X_VERSION_MIN_REQUIRED), + AvailabilityVersionToMacOSXMinorVersion(MAC_OS_X_VERSION_MAX_ALLOWED)); +#endif + + return debug_build_string; +} + +} // namespace internal MinidumpMiscInfoWriter::MinidumpMiscInfoWriter() : MinidumpStreamWriter(), misc_info_() { @@ -30,7 +138,78 @@ MinidumpMiscInfoWriter::MinidumpMiscInfoWriter() MinidumpMiscInfoWriter::~MinidumpMiscInfoWriter() { } -void MinidumpMiscInfoWriter::SetProcessId(uint32_t process_id) { +void MinidumpMiscInfoWriter::InitializeFromSnapshot( + const ProcessSnapshot* process_snapshot) { + DCHECK_EQ(state(), kStateMutable); + DCHECK_EQ(misc_info_.Flags1, 0u); + + SetProcessID(InRangeCast(process_snapshot->ProcessID(), 0)); + + const SystemSnapshot* system_snapshot = process_snapshot->System(); + + uint64_t current_mhz; + uint64_t max_mhz; + system_snapshot->CPUFrequency(¤t_mhz, &max_mhz); + const uint32_t kHzPerMHz = 1E6; + SetProcessorPowerInfo( + InRangeCast(current_mhz / kHzPerMHz, + std::numeric_limits::max()), + InRangeCast(max_mhz / kHzPerMHz, + std::numeric_limits::max()), + 0, + 0, + 0); + + timeval start_time; + process_snapshot->ProcessStartTime(&start_time); + + timeval user_time; + timeval system_time; + process_snapshot->ProcessCPUTimes(&user_time, &system_time); + + // Round the resource usage fields to the nearest second, because the minidump + // format only has one-second resolution. The start_time field is truncated + // instead of rounded so that the process uptime is reflected more accurately + // when the start time is compared to the snapshot time in the + // MINIDUMP_HEADER, which is also truncated, not rounded. + uint32_t user_seconds = TimevalToRoundedSeconds(user_time); + uint32_t system_seconds = TimevalToRoundedSeconds(system_time); + + SetProcessTimes(start_time.tv_sec, user_seconds, system_seconds); + + // This determines the system’s time zone, which may be different than the + // process’ notion of the time zone. + SystemSnapshot::DaylightSavingTimeStatus dst_status; + int standard_offset_seconds; + int daylight_offset_seconds; + std::string standard_name; + std::string daylight_name; + system_snapshot->TimeZone(&dst_status, + &standard_offset_seconds, + &daylight_offset_seconds, + &standard_name, + &daylight_name); + + // standard_offset_seconds is seconds east of UTC, but the minidump file wants + // minutes west of UTC. daylight_offset_seconds is also seconds east of UTC, + // but the minidump file wants minutes west of the standard offset. The empty + // ({}) arguments are for the transition times in and out of daylight saving + // time. These are not determined because no API exists to do so, and the + // transition times may vary from year to year. + SetTimeZone(dst_status, + standard_offset_seconds / -60, + standard_name, + {}, + 0, + daylight_name, + {}, + (standard_offset_seconds - daylight_offset_seconds) / 60); + + SetBuildString(BuildString(system_snapshot), + internal::MinidumpMiscInfoDebugBuildString()); +} + +void MinidumpMiscInfoWriter::SetProcessID(uint32_t process_id) { DCHECK_EQ(state(), kStateMutable); misc_info_.ProcessId = process_id; diff --git a/minidump/minidump_misc_info_writer.h b/minidump/minidump_misc_info_writer.h index 0ca54826..4e7fc03a 100644 --- a/minidump/minidump_misc_info_writer.h +++ b/minidump/minidump_misc_info_writer.h @@ -28,6 +28,24 @@ namespace crashpad { +class ProcessSnapshot; + +namespace internal { + +//! \brief Returns the string to set in MINIDUMP_MISC_INFO_4::DbgBldStr. +//! +//! dbghelp produces strings like `"dbghelp.i386,6.3.9600.16520"` and +//! `"dbghelp.amd64,6.3.9600.16520"`. This function mimics that format, and adds +//! the OS that wrote the minidump along with any relevant platform-specific +//! data describing the compilation environment. +//! +//! This function is an implementation detail of +//! MinidumpMiscInfoWriter::InitializeFromSnapshot() and is only exposed for +//! testing purposes. +std::string MinidumpMiscInfoDebugBuildString(); + +} // namespace internal + //! \brief The writer for a stream in the MINIDUMP_MISC_INFO family in a //! minidump file. //! @@ -41,8 +59,17 @@ class MinidumpMiscInfoWriter final : public internal::MinidumpStreamWriter { MinidumpMiscInfoWriter(); ~MinidumpMiscInfoWriter() override; + //! \brief Initializes MINIDUMP_MISC_INFO_N based on \a process_snapshot. + //! + //! \param[in] process_snapshot The process snapshot to use as source data. + //! + //! \note Valid in #kStateMutable. No mutator methods may be called before + //! this method, and it is not normally necessary to call any mutator + //! methods after this method. + void InitializeFromSnapshot(const ProcessSnapshot* process_snapshot); + //! \brief Sets the field referenced by #MINIDUMP_MISC1_PROCESS_ID. - void SetProcessId(uint32_t process_id); + void SetProcessID(uint32_t process_id); //! \brief Sets the fields referenced by #MINIDUMP_MISC1_PROCESS_TIMES. void SetProcessTimes(time_t process_create_time, diff --git a/minidump/minidump_misc_info_writer_test.cc b/minidump/minidump_misc_info_writer_test.cc index 8a27ad4e..6e55a088 100644 --- a/minidump/minidump_misc_info_writer_test.cc +++ b/minidump/minidump_misc_info_writer_test.cc @@ -27,6 +27,8 @@ #include "minidump/minidump_file_writer.h" #include "minidump/test/minidump_file_writer_test_util.h" #include "minidump/test/minidump_writable_test_util.h" +#include "snapshot/test/test_process_snapshot.h" +#include "snapshot/test/test_system_snapshot.h" #include "util/file/string_file_writer.h" #include "util/stdlib/strlcpy.h" @@ -177,7 +179,7 @@ TEST(MinidumpMiscInfoWriter, ProcessId) { const uint32_t kProcessId = 12345; - misc_info_writer->SetProcessId(kProcessId); + misc_info_writer->SetProcessID(kProcessId); minidump_file_writer.AddStream(misc_info_writer.Pass()); @@ -535,7 +537,7 @@ TEST(MinidumpMiscInfoWriter, Everything) { const char kBuildString[] = "build string"; const char kDebugBuildString[] = "debug build string"; - misc_info_writer->SetProcessId(kProcessId); + misc_info_writer->SetProcessID(kProcessId); misc_info_writer->SetProcessTimes( kProcessCreateTime, kProcessUserTime, kProcessKernelTime); misc_info_writer->SetProcessorPowerInfo(kProcessorMaxMhz, @@ -612,6 +614,94 @@ TEST(MinidumpMiscInfoWriter, Everything) { ExpectMiscInfoEqual(&expected, observed); } +TEST(MinidumpMiscInfoWriter, InitializeFromSnapshot) { + MINIDUMP_MISC_INFO_4 expect_misc_info = {}; + + const char kStandardTimeName[] = "EST"; + const char kDaylightTimeName[] = "EDT"; + const char kOSVersionFull[] = + "Mac OS X 10.9.5 (13F34); " + "Darwin 13.4.0 Darwin Kernel Version 13.4.0: " + "Sun Aug 17 19:50:11 PDT 2014; " + "root:xnu-2422.115.4~1/RELEASE_X86_64 x86_64"; + const char kMachineDescription[] = "MacBookPro11,3 (Mac-2BD1B31983FE1663)"; + string16 standard_time_name_utf16 = base::UTF8ToUTF16(kStandardTimeName); + string16 daylight_time_name_utf16 = base::UTF8ToUTF16(kDaylightTimeName); + string16 build_string_utf16 = base::UTF8ToUTF16( + std::string(kOSVersionFull) + "; " + kMachineDescription); + std::string debug_build_string = internal::MinidumpMiscInfoDebugBuildString(); + EXPECT_FALSE(debug_build_string.empty()); + string16 debug_build_string_utf16 = base::UTF8ToUTF16(debug_build_string); + + expect_misc_info.SizeOfInfo = sizeof(expect_misc_info); + expect_misc_info.Flags1 = MINIDUMP_MISC1_PROCESS_ID | + MINIDUMP_MISC1_PROCESS_TIMES | + MINIDUMP_MISC1_PROCESSOR_POWER_INFO | + MINIDUMP_MISC3_TIMEZONE | + MINIDUMP_MISC4_BUILDSTRING; + expect_misc_info.ProcessId = 12345; + expect_misc_info.ProcessCreateTime = 0x555c7740; + expect_misc_info.ProcessUserTime = 60; + expect_misc_info.ProcessKernelTime = 15; + expect_misc_info.ProcessorCurrentMhz = 2800; + expect_misc_info.ProcessorMaxMhz = 2800; + expect_misc_info.TimeZoneId = 1; + expect_misc_info.TimeZone.Bias = 300; + c16lcpy(expect_misc_info.TimeZone.StandardName, + standard_time_name_utf16.c_str(), + arraysize(expect_misc_info.TimeZone.StandardName)); + expect_misc_info.TimeZone.StandardBias = 0; + c16lcpy(expect_misc_info.TimeZone.DaylightName, + daylight_time_name_utf16.c_str(), + arraysize(expect_misc_info.TimeZone.DaylightName)); + expect_misc_info.TimeZone.DaylightBias = -60; + c16lcpy(expect_misc_info.BuildString, + build_string_utf16.c_str(), + arraysize(expect_misc_info.BuildString)); + c16lcpy(expect_misc_info.DbgBldStr, + debug_build_string_utf16.c_str(), + arraysize(expect_misc_info.DbgBldStr)); + + const timeval kStartTime = { expect_misc_info.ProcessCreateTime, 0 }; + const timeval kUserCPUTime = { expect_misc_info.ProcessUserTime, 0 }; + const timeval kSystemCPUTime = { expect_misc_info.ProcessKernelTime, 0 }; + + TestProcessSnapshot process_snapshot; + process_snapshot.SetProcessID(expect_misc_info.ProcessId); + process_snapshot.SetProcessStartTime(kStartTime); + process_snapshot.SetProcessCPUTimes(kUserCPUTime, kSystemCPUTime); + + auto system_snapshot = make_scoped_ptr(new TestSystemSnapshot()); + const uint64_t kHzPerMHz = 1E6; + system_snapshot->SetCPUFrequency( + expect_misc_info.ProcessorCurrentMhz * kHzPerMHz, + expect_misc_info.ProcessorMaxMhz * kHzPerMHz); + system_snapshot->SetTimeZone(SystemSnapshot::kObservingStandardTime, + expect_misc_info.TimeZone.Bias * -60, + (expect_misc_info.TimeZone.Bias + + expect_misc_info.TimeZone.DaylightBias) * -60, + kStandardTimeName, + kDaylightTimeName); + system_snapshot->SetOSVersionFull(kOSVersionFull); + system_snapshot->SetMachineDescription(kMachineDescription); + + process_snapshot.SetSystem(system_snapshot.Pass()); + + auto misc_info_writer = make_scoped_ptr(new MinidumpMiscInfoWriter()); + misc_info_writer->InitializeFromSnapshot(&process_snapshot); + + MinidumpFileWriter minidump_file_writer; + minidump_file_writer.AddStream(misc_info_writer.Pass()); + + StringFileWriter file_writer; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); + + const MINIDUMP_MISC_INFO_4* misc_info; + ASSERT_NO_FATAL_FAILURE(GetMiscInfoStream(file_writer.string(), &misc_info)); + + ExpectMiscInfoEqual(&expect_misc_info, misc_info); +} + } // namespace } // namespace test } // namespace crashpad diff --git a/minidump/minidump_system_info_writer_test.cc b/minidump/minidump_system_info_writer_test.cc index a75e84b7..8f96bda7 100644 --- a/minidump/minidump_system_info_writer_test.cc +++ b/minidump/minidump_system_info_writer_test.cc @@ -339,7 +339,6 @@ TEST(MinidumpSystemInfoWriter, InitializeFromSnapshot_X86) { const MINIDUMP_SYSTEM_INFO* system_info; const MINIDUMP_STRING* csd_version; - ASSERT_NO_FATAL_FAILURE(GetSystemInfoStream(file_writer.string(), strlen(kOSVersionBuild), &system_info, diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp index ae0bcf39..09bd027c 100644 --- a/snapshot/snapshot.gyp +++ b/snapshot/snapshot.gyp @@ -94,6 +94,8 @@ 'test/test_memory_snapshot.h', 'test/test_module_snapshot.cc', 'test/test_module_snapshot.h', + 'test/test_process_snapshot.cc', + 'test/test_process_snapshot.h', 'test/test_system_snapshot.cc', 'test/test_system_snapshot.h', 'test/test_thread_snapshot.cc', diff --git a/snapshot/test/test_process_snapshot.cc b/snapshot/test/test_process_snapshot.cc new file mode 100644 index 00000000..4b343261 --- /dev/null +++ b/snapshot/test/test_process_snapshot.cc @@ -0,0 +1,87 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/exception_snapshot.h" +#include "snapshot/module_snapshot.h" +#include "snapshot/system_snapshot.h" +#include "snapshot/test/test_process_snapshot.h" +#include "snapshot/thread_snapshot.h" + +namespace crashpad { +namespace test { + +TestProcessSnapshot::TestProcessSnapshot() + : process_id_(0), + parent_process_id_(0), + snapshot_time_(), + process_start_time_(), + process_cpu_user_time_(), + process_cpu_system_time_(), + system_(), + threads_(), + modules_(), + exception_() { +} + +TestProcessSnapshot::~TestProcessSnapshot() { +} + +pid_t TestProcessSnapshot::ProcessID() const { + return process_id_; +} + +pid_t TestProcessSnapshot::ParentProcessID() const { + return parent_process_id_; +} + +void TestProcessSnapshot::SnapshotTime(timeval* snapshot_time) const { + *snapshot_time = snapshot_time_; +} + +void TestProcessSnapshot::ProcessStartTime(timeval* start_time) const { + *start_time = process_start_time_; +} + +void TestProcessSnapshot::ProcessCPUTimes(timeval* user_time, + timeval* system_time) const { + *user_time = process_cpu_user_time_; + *system_time = process_cpu_system_time_; +} + +const SystemSnapshot* TestProcessSnapshot::System() const { + return system_.get(); +} + +std::vector TestProcessSnapshot::Threads() const { + std::vector threads; + for (const ThreadSnapshot* thread : threads_) { + threads.push_back(thread); + } + return threads; +} + +std::vector TestProcessSnapshot::Modules() const { + std::vector modules; + for (const ModuleSnapshot* module : modules_) { + modules.push_back(module); + } + return modules; +} + +const ExceptionSnapshot* TestProcessSnapshot::Exception() const { + return exception_.get(); +} + +} // namespace test +} // namespace crashpad diff --git a/snapshot/test/test_process_snapshot.h b/snapshot/test/test_process_snapshot.h new file mode 100644 index 00000000..25550ccc --- /dev/null +++ b/snapshot/test/test_process_snapshot.h @@ -0,0 +1,121 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_TEST_TEST_PROCESS_SNAPSHOT_H_ +#define CRASHPAD_SNAPSHOT_TEST_TEST_PROCESS_SNAPSHOT_H_ + +#include +#include +#include + +#include + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "snapshot/exception_snapshot.h" +#include "snapshot/process_snapshot.h" +#include "snapshot/system_snapshot.h" +#include "util/stdlib/pointer_container.h" + +namespace crashpad { + +class ModuleSnapshot; +class ThreadSnapshot; + +namespace test { + +//! \brief A test ProcessSnapshot that can carry arbitrary data for testing +//! purposes. +class TestProcessSnapshot final : public ProcessSnapshot { + public: + TestProcessSnapshot(); + ~TestProcessSnapshot() override; + + void SetProcessID(pid_t process_id) { process_id_ = process_id; } + void SetParentProcessID(pid_t parent_process_id) { + parent_process_id_ = parent_process_id; + } + void SetSnapshotTime(const timeval& snapshot_time) { + snapshot_time_ = snapshot_time; + } + void SetProcessStartTime(const timeval& start_time) { + process_start_time_ = start_time; + } + void SetProcessCPUTimes(const timeval& user_time, + const timeval& system_time) { + process_cpu_user_time_ = user_time; + process_cpu_system_time_ = system_time; + } + + //! \brief Sets the system snapshot to be returned by System(). + //! + //! \param[in] system The system snapshot that System() will return. The + //! TestProcessSnapshot object takes ownership of \a system. + void SetSystem(scoped_ptr system) { system_ = system.Pass(); } + + //! \brief Adds a thread snapshot to be returned by Threads(). + //! + //! \param[in] thread The thread snapshot that will be included in Threads(). + //! The TestProcessSnapshot object takes ownership of \a thread. + void AddThread(scoped_ptr thread) { + threads_.push_back(thread.release()); + } + + //! \brief Adds a module snapshot to be returned by Modules(). + //! + //! \param[in] module The module snapshot that will be included in Modules(). + //! The TestProcessSnapshot object takes ownership of \a module. + void AddModule(scoped_ptr module) { + modules_.push_back(module.release()); + } + + //! \brief Sets the exception snapshot to be returned by Exception(). + //! + //! \param[in] exception The exception snapshot that Exception() will return. + //! The TestProcessSnapshot object takes ownership of \a exception. + void SetException(scoped_ptr exception) { + exception_ = exception.Pass(); + } + + // ProcessSnapshot: + + pid_t ProcessID() const override; + pid_t ParentProcessID() const override; + void SnapshotTime(timeval* snapshot_time) const override; + void ProcessStartTime(timeval* start_time) const override; + void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override; + const SystemSnapshot* System() const override; + std::vector Threads() const override; + std::vector Modules() const override; + const ExceptionSnapshot* Exception() const override; + + private: + pid_t process_id_; + pid_t parent_process_id_; + timeval snapshot_time_; + timeval process_start_time_; + timeval process_cpu_user_time_; + timeval process_cpu_system_time_; + scoped_ptr system_; + PointerVector threads_; + PointerVector modules_; + scoped_ptr exception_; + + DISALLOW_COPY_AND_ASSIGN(TestProcessSnapshot); +}; + +} // namespace test +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_TEST_TEST_PROCESS_SNAPSHOT_H_