diff --git a/snapshot/win/pe_image_annotations_reader_test.cc b/snapshot/win/pe_image_annotations_reader_test.cc index d9365c2b..383ada99 100644 --- a/snapshot/win/pe_image_annotations_reader_test.cc +++ b/snapshot/win/pe_image_annotations_reader_test.cc @@ -36,19 +36,18 @@ namespace crashpad { namespace test { namespace { +enum TestType { + // Don't crash, just test the CrashpadInfo interface. + kDontCrash = 0, + + // The child process should crash by __debugbreak(). + kCrashDebugBreak, +}; + +template class TestPEImageAnnotationsReader final : public WinMultiprocess { public: - enum TestType { - // Don't crash, just test the CrashpadInfo interface. - kDontCrash = 0, - - // The child process should crash by __debugbreak(). - kCrashDebugBreak, - }; - - explicit TestPEImageAnnotationsReader(TestType test_type) - : WinMultiprocess(), test_type_(test_type) {} - + TestPEImageAnnotationsReader() {} ~TestPEImageAnnotationsReader() {} private: @@ -87,7 +86,7 @@ class TestPEImageAnnotationsReader final : public WinMultiprocess { EXPECT_EQ("shorter", all_annotations_simple_map["#TEST# longer"]); EXPECT_EQ("", all_annotations_simple_map["#TEST# empty_value"]); - if (test_type_ == kCrashDebugBreak) + if (Type == kCrashDebugBreak) SetExpectedChildExitCode(STATUS_BREAKPOINT); // Tell the child process to continue. @@ -115,7 +114,7 @@ class TestPEImageAnnotationsReader final : public WinMultiprocess { // Wait for the parent to indicate that it's safe to continue/crash. CheckedReadFile(ReadPipeHandle(), &c, sizeof(c)); - switch (test_type_) { + switch (Type) { case kDontCrash: break; @@ -125,21 +124,15 @@ class TestPEImageAnnotationsReader final : public WinMultiprocess { } } - TestType test_type_; - DISALLOW_COPY_AND_ASSIGN(TestPEImageAnnotationsReader); }; TEST(PEImageAnnotationsReader, DontCrash) { - TestPEImageAnnotationsReader test_pe_image_annotations_reader( - TestPEImageAnnotationsReader::kDontCrash); - test_pe_image_annotations_reader.Run(); + WinMultiprocess::Run>(); } TEST(PEImageAnnotationsReader, CrashDebugBreak) { - TestPEImageAnnotationsReader test_pe_image_annotations_reader( - TestPEImageAnnotationsReader::kCrashDebugBreak); - test_pe_image_annotations_reader.Run(); + WinMultiprocess::Run>(); } } // namespace diff --git a/snapshot/win/process_reader_win_test.cc b/snapshot/win/process_reader_win_test.cc index d4890b8a..6cd06e5b 100644 --- a/snapshot/win/process_reader_win_test.cc +++ b/snapshot/win/process_reader_win_test.cc @@ -85,8 +85,7 @@ class ProcessReaderChild final : public WinMultiprocess { }; TEST(ProcessReaderWin, ChildBasic) { - ProcessReaderChild process_reader_child; - process_reader_child.Run(); + WinMultiprocess::Run(); } TEST(ProcessReaderWin, SelfOneThread) { diff --git a/test/test.gyp b/test/test.gyp index aa0d7af6..2d02287e 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -51,6 +51,8 @@ 'scoped_temp_dir.h', 'scoped_temp_dir_posix.cc', 'scoped_temp_dir_win.cc', + 'win/win_child_process.cc', + 'win/win_child_process.h', 'win/win_multiprocess.cc', 'win/win_multiprocess.h', ], @@ -69,13 +71,6 @@ ], }, }], - ['OS=="win"', { - 'link_settings': { - 'libraries': [ - '-lshell32.lib', - ], - }, - }], ], }, ], diff --git a/test/test_test.gyp b/test/test_test.gyp index 66baf600..97e237d9 100644 --- a/test/test_test.gyp +++ b/test/test_test.gyp @@ -39,6 +39,7 @@ 'multiprocess_posix_test.cc', 'paths_test.cc', 'scoped_temp_dir_test.cc', + 'win/win_child_process_test.cc', 'win/win_multiprocess_test.cc', ], }, diff --git a/test/win/win_child_process.cc b/test/win/win_child_process.cc new file mode 100644 index 00000000..f6156d15 --- /dev/null +++ b/test/win/win_child_process.cc @@ -0,0 +1,238 @@ +// Copyright 2015 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 "test/win/win_child_process.h" + +#include +#include + +#include + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "gtest/gtest.h" +#include "util/stdlib/string_number_conversion.h" +#include "util/string/split_string.h" +#include "test/paths.h" + +namespace crashpad { +namespace test { + +namespace { + +const char kIsMultiprocessChild[] = "--is-multiprocess-child"; +struct LocalFreeTraits { + static HLOCAL InvalidValue() { return nullptr; } + static void Free(HLOCAL mem) { + if (LocalFree(mem) != nullptr) + PLOG(ERROR) << "LocalFree"; + } +}; + +using ScopedLocalFree = base::ScopedGeneric; + +bool GetSwitch(const char* switch_name, std::string* value) { + int num_args; + wchar_t** args = CommandLineToArgvW(GetCommandLine(), &num_args); + ScopedLocalFree scoped_args(args); // Take ownership. + if (!args) { + PLOG(FATAL) << "CommandLineToArgvW"; + return false; + } + + std::string switch_name_with_equals(switch_name); + switch_name_with_equals += "="; + for (size_t i = 1; i < num_args; ++i) { + const wchar_t* arg = args[i]; + std::string arg_as_utf8 = base::UTF16ToUTF8(arg); + if (arg_as_utf8.compare( + 0, switch_name_with_equals.size(), switch_name_with_equals) == 0) { + if (value) + *value = arg_as_utf8.substr(switch_name_with_equals.size()); + return true; + } + } + + return false; +} + +ScopedKernelHANDLE LaunchCommandLine(wchar_t* command_line) { + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + startup_info.dwFlags = STARTF_USESTDHANDLES; + PROCESS_INFORMATION process_info; + if (!CreateProcess(Paths::Executable().value().c_str(), + &command_line[0], // This cannot be constant, per MSDN. + nullptr, + nullptr, + true, // Inherit handles. + 0, + nullptr, + nullptr, + &startup_info, + &process_info)) { + PLOG(ERROR) << "CreateProcess"; + return ScopedKernelHANDLE(); + } + if (!CloseHandle(process_info.hThread)) { + PLOG(ERROR) << "CloseHandle"; + if (!CloseHandle(process_info.hProcess)) + PLOG(ERROR) << "CloseHandle"; + return ScopedKernelHANDLE(); + } + return ScopedKernelHANDLE(process_info.hProcess); +} + +bool UnsetHandleInheritance(HANDLE handle) { + if (!SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0)) { + PLOG(ERROR) << "SetHandleInformation"; + ADD_FAILURE() << "SetHandleInformation"; + return false; + } + return true; +} + +bool CreateInheritablePipe(ScopedFileHANDLE* read_handle, + bool read_inheritable, + ScopedFileHANDLE* write_handle, + bool write_inheritable) { + // Mark both sides as inheritable via the SECURITY_ATTRIBUTES and use + // SetHandleInformation as necessary to restrict inheritance of either side. + SECURITY_ATTRIBUTES security_attributes = {0}; + security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); + security_attributes.bInheritHandle = true; + + HANDLE read, write; + BOOL result = CreatePipe(&read, &write, &security_attributes, 0); + if (!result) { + PLOG(ERROR) << "CreatePipe"; + ADD_FAILURE() << "CreatePipe failed"; + return false; + } + ScopedFileHANDLE temp_read(read); + ScopedFileHANDLE temp_write(write); + + if (!read_inheritable && !UnsetHandleInheritance(temp_read.get())) + return false; + if (!write_inheritable && !UnsetHandleInheritance(temp_write.get())) + return false; + + *read_handle = temp_read.Pass(); + *write_handle = temp_write.Pass(); + + return true; +} + +} // namespace + +WinChildProcess::WinChildProcess() { + std::string switch_value; + CHECK(GetSwitch(kIsMultiprocessChild, &switch_value)); + + // Set up the handles we inherited from the parent. These are inherited from + // the parent and so are open and have the same value as in the parent. The + // values are passed to the child on the command line. + std::string left, right; + CHECK(SplitString(switch_value, '|', &left, &right)); + unsigned int write, read; + CHECK(StringToNumber(left, &write)); + CHECK(StringToNumber(right, &read)); + pipe_write_.reset(reinterpret_cast(write)); + pipe_read_.reset(reinterpret_cast(read)); + + // Notify the parent that it's OK to proceed. We only need to wait to get to + // the process entry point, but this is the easiest place we can notify. + char c = ' '; + CheckedWriteFile(WritePipeHandle(), &c, sizeof(c)); +} + +// static +bool WinChildProcess::IsChildProcess() { + return GetSwitch(kIsMultiprocessChild, nullptr); +} + +// static +scoped_ptr WinChildProcess::Launch() { + // Make pipes for child-to-parent and parent-to-child communication. + scoped_ptr handles_for_parent(new Handles); + ScopedFileHANDLE read_for_child; + ScopedFileHANDLE write_for_child; + + if (!CreateInheritablePipe( + &handles_for_parent->read, false, &write_for_child, true)) { + return scoped_ptr(); + } + + if (!CreateInheritablePipe( + &read_for_child, true, &handles_for_parent->write, false)) { + return scoped_ptr(); + } + + // Build a command line for the child process that tells it only to run the + // current test, and to pass down the values of the pipe handles. + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + std::wstring command_line = + Paths::Executable().value() + L" " + + base::UTF8ToUTF16(base::StringPrintf("--gtest_filter=%s.%s %s=0x%x|0x%x", + test_info->test_case_name(), + test_info->name(), + kIsMultiprocessChild, + write_for_child, + read_for_child.get())); + + // Command-line buffer cannot be constant, per CreateProcess signature. + handles_for_parent->process = LaunchCommandLine(&command_line[0]); + if (!handles_for_parent->process.is_valid()) + return scoped_ptr(); + + // Block until the child process has launched. CreateProcess() returns + // immediately, and test code expects process initialization to have + // completed so it can, for example, read the process memory. + char c; + if (!LoggingReadFile(handles_for_parent->read.get(), &c, sizeof(c))) { + ADD_FAILURE() << "LoggedReadFile"; + return scoped_ptr(); + } + + if (c != ' ') { + ADD_FAILURE() << "invalid data read from child"; + return scoped_ptr(); + } + + return handles_for_parent.Pass(); +} + +FileHandle WinChildProcess::ReadPipeHandle() const { + return pipe_read_.get(); +} + +FileHandle WinChildProcess::WritePipeHandle() const { + return pipe_write_.get(); +} + +void WinChildProcess::CloseReadPipe() { + pipe_read_.reset(); +} + +void WinChildProcess::CloseWritePipe() { + pipe_write_.reset(); +} + +} // namespace test +} // namespace crashpad diff --git a/test/win/win_child_process.h b/test/win/win_child_process.h new file mode 100644 index 00000000..6ca6eca3 --- /dev/null +++ b/test/win/win_child_process.h @@ -0,0 +1,117 @@ +// Copyright 2015 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_TEST_WIN_WIN_CHILD_PROCESS_H_ +#define CRASHPAD_TEST_WIN_WIN_CHILD_PROCESS_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "util/file/file_io.h" +#include "util/win/scoped_handle.h" + +namespace crashpad { +namespace test { + +//! \brief Facilitates the launching of child processes from unit tests. +class WinChildProcess { + public: + //! \brief Groups handles used to communicate with, observe, and manage a + //! child process. + struct Handles { + //! \brief A handle to read from an anonymous pipe shared with the child + //! process. + ScopedFileHANDLE read; + //! \brief A handle to write to an anonymous pipe shared with the child + //! process. + ScopedFileHANDLE write; + //! \brief A handle to the child process. + ScopedKernelHANDLE process; + }; + + WinChildProcess(); + virtual ~WinChildProcess() {} + + //! \brief Returns true if the current process is a child process. + static bool IsChildProcess(); + + //! \brief Runs the child process defined by T if the current process is a + //! child process; does not return in that case. Otherwise, returns. + template + static void EntryPoint() { + if (IsChildProcess()) { + // The static_cast here will cause a compiler failure if T is not a + // subclass of WinChildProcess. It's the constructor of WinChildProcess + // that performs the pipe handshake with the parent process (without which + // we would have a hang). + T child_process; + int result = static_cast(&child_process)->Run(); + exit(result); + } + } + + //! \brief Launches a child process and returns the Handles for that process. + //! The process is guaranteed to be executing by the time this method + //! returns. Returns null and logs a GTest failure in case of failure. + static scoped_ptr Launch(); + + protected: + //! \brief Returns a handle to read from an anonymous pipe shared with the + //! parent process. + //! + //! It is an error to call this after CloseReadPipe() has been called. + //! + //! \return The read pipe's file handle. + FileHandle ReadPipeHandle() const; + + //! \brief Returns a handle to write to an anonymous pipe shared with the + //! parent process. + //! + //! It is an error to call this after CloseWritePipe() has been called. + //! + //! \return The write pipe's file handle. + FileHandle WritePipeHandle() const; + + //! \brief Closes the read pipe. + //! + //! ReadPipeHandle() must not be called after this. + void CloseReadPipe(); + + //! \brief Closes the write pipe. + //! + //! An attempt to read from the read pipe in the parent process will indicate + //! end-of-file. WritePipeHandle() must not be called after this. + void CloseWritePipe(); + + private: + //! \brief The subclass-provided child routine. + //! + //! Subclasses must implement this method to define how the child operates. + //! Subclasses may exit with a failure status by using `LOG(FATAL)`, + //! `abort()`, or similar. They may also exit by returning their exit code + //! from this method. It is up to the client to observe and interpret the + //! child's exit code. + //! + //! \return The child process exit code. + virtual int Run() = 0; + + ScopedFileHANDLE pipe_read_; + ScopedFileHANDLE pipe_write_; + + DISALLOW_COPY_AND_ASSIGN(WinChildProcess); +}; + +} // namespace test +} // namespace crashpad + +#endif // CRASHPAD_TEST_WIN_WIN_CHILD_PROCESS_H_ diff --git a/test/win/win_child_process_test.cc b/test/win/win_child_process_test.cc new file mode 100644 index 00000000..bdf627a8 --- /dev/null +++ b/test/win/win_child_process_test.cc @@ -0,0 +1,81 @@ +// Copyright 2015 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 "test/win/win_child_process.h" + +#include + +#include "base/basictypes.h" +#include "gtest/gtest.h" + +namespace crashpad { +namespace test { +namespace { + +int ReadInt(HANDLE handle) { + int value = 0; + DWORD bytes_read = 0; + EXPECT_TRUE(::ReadFile(handle, &value, sizeof(value), &bytes_read, nullptr)); + EXPECT_EQ(sizeof(value), bytes_read); + return value; +} + +void WriteInt(HANDLE handle, int value) { + DWORD bytes_written = 0; + EXPECT_TRUE( + ::WriteFile(handle, &value, sizeof(value), &bytes_written, nullptr)); + EXPECT_EQ(sizeof(value), bytes_written); +} + +class TestWinChildProcess final : public WinChildProcess { + public: + TestWinChildProcess() : WinChildProcess() {} + + ~TestWinChildProcess() {} + + private: + // WinChildProcess will have already exercised the pipes. + int Run() override { + int value = ReadInt(ReadPipeHandle()); + WriteInt(WritePipeHandle(), value); + return EXIT_SUCCESS; + } + + DISALLOW_COPY_AND_ASSIGN(TestWinChildProcess); +}; + +TEST(WinChildProcessTest, WinChildProcess) { + WinChildProcess::EntryPoint(); + + WinChildProcess::Launch(); +} + +TEST(WinChildProcessTest, MultipleChildren) { + WinChildProcess::EntryPoint(); + + scoped_ptr handles_1 = WinChildProcess::Launch(); + scoped_ptr handles_2 = WinChildProcess::Launch(); + scoped_ptr handles_3 = WinChildProcess::Launch(); + + WriteInt(handles_1->write.get(), 1); + WriteInt(handles_2->write.get(), 2); + WriteInt(handles_3->write.get(), 3); + ASSERT_EQ(3, ReadInt(handles_3->read.get())); + ASSERT_EQ(2, ReadInt(handles_2->read.get())); + ASSERT_EQ(1, ReadInt(handles_1->read.get())); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/test/win/win_multiprocess.cc b/test/win/win_multiprocess.cc index cdcec0af..6a451f4d 100644 --- a/test/win/win_multiprocess.cc +++ b/test/win/win_multiprocess.cc @@ -15,13 +15,11 @@ #include "test/win/win_multiprocess.h" #include -#include #include "base/logging.h" #include "base/scoped_generic.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" -#include "gtest/gtest.h" #include "util/stdlib/string_number_conversion.h" #include "util/string/split_string.h" #include "test/paths.h" @@ -29,201 +27,53 @@ namespace crashpad { namespace test { -namespace { - -const char kIsMultiprocessChild[] = "--is-multiprocess-child"; - -struct LocalFreeTraits { - static HLOCAL InvalidValue() { return nullptr; } - static void Free(HLOCAL mem) { - if (LocalFree(mem) != nullptr) - PLOG(ERROR) << "LocalFree"; - } -}; - -using ScopedLocalFree = base::ScopedGeneric; - -bool GetSwitch(const char* switch_name, std::string* value) { - int num_args; - wchar_t** args = CommandLineToArgvW(GetCommandLine(), &num_args); - ScopedLocalFree scoped_args(args); // Take ownership. - if (!args) { - PLOG(ERROR) << "couldn't parse command line"; - return false; - } - - std::string switch_name_with_equals(switch_name); - switch_name_with_equals += "="; - for (size_t i = 1; i < num_args; ++i) { - const wchar_t* arg = args[i]; - std::string arg_as_utf8 = base::UTF16ToUTF8(arg); - if (arg_as_utf8.compare( - 0, switch_name_with_equals.size(), switch_name_with_equals) == 0) { - *value = arg_as_utf8.substr(switch_name_with_equals.size()); - return true; - } - } - - return false; -} - -} // namespace - WinMultiprocess::WinMultiprocess() - : pipe_c2p_read_(), - pipe_c2p_write_(), - pipe_p2c_read_(), - pipe_p2c_write_(), - child_handle_(), - exit_code_(EXIT_SUCCESS) { -} + : exit_code_(EXIT_SUCCESS), + child_handles_(nullptr), + child_process_helper_(nullptr) {} -void WinMultiprocess::Run() { - std::string switch_value; - if (GetSwitch(kIsMultiprocessChild, &switch_value)) { - // If we're in the child, then set up the handles we inherited from the - // parent. These are inherited from the parent and so are open and have the - // same value as in the parent. The values are passed to the child on the - // command line. - std::string left, right; - ASSERT_TRUE(SplitString(switch_value, '|', &left, &right)); - unsigned int c2p_write, p2c_read; - ASSERT_TRUE(StringToNumber(left, &c2p_write)); - ASSERT_TRUE(StringToNumber(right, &p2c_read)); - pipe_c2p_write_.reset(reinterpret_cast(c2p_write)); - pipe_p2c_read_.reset(reinterpret_cast(p2c_read)); - - // Notify the parent that it's OK to proceed. We only need to wait to get to - // the process entry point, but this is the easiest place we can notify. - char c = ' '; - CheckedWriteFile(WritePipeHandle(), &c, sizeof(c)); - - // Invoke the child side of the test. - WinMultiprocessChild(); - - if (testing::Test::HasFailure()) - exit(255); - exit(EXIT_SUCCESS); - } else { - // If we're in the parent, make pipes for child-to-parent and - // parent-to-child communication. Mark them as inheritable via the - // SECURITY_ATTRIBUTES, but use SetHandleInformation to ensure that the - // parent sides are not inherited. - SECURITY_ATTRIBUTES security_attributes = {0}; - security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); - security_attributes.bInheritHandle = true; - - HANDLE c2p_read, c2p_write; - PCHECK(CreatePipe(&c2p_read, &c2p_write, &security_attributes, 0)); - PCHECK(SetHandleInformation(c2p_read, HANDLE_FLAG_INHERIT, 0)); - pipe_c2p_read_.reset(c2p_read); - pipe_c2p_write_.reset(c2p_write); - - HANDLE p2c_read, p2c_write; - PCHECK(CreatePipe(&p2c_read, &p2c_write, &security_attributes, 0)); - PCHECK(SetHandleInformation(p2c_write, HANDLE_FLAG_INHERIT, 0)); - pipe_p2c_read_.reset(p2c_read); - pipe_p2c_write_.reset(p2c_write); - - // Build a command line for the child process that tells it only to run the - // current test, and to pass down the values of the pipe handles. - const ::testing::TestInfo* const test_info = - ::testing::UnitTest::GetInstance()->current_test_info(); - std::wstring command_line = Paths::Executable().value() + L" " + - base::UTF8ToUTF16(base::StringPrintf( - "--gtest_filter=%s.%s %s=0x%x|0x%x", - test_info->test_case_name(), - test_info->name(), - kIsMultiprocessChild, - c2p_write, - p2c_read)); - STARTUPINFO startup_info = {0}; - startup_info.cb = sizeof(startup_info); - startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); - startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); - startup_info.dwFlags = STARTF_USESTDHANDLES; - PROCESS_INFORMATION process_info; - PCHECK( - CreateProcess(Paths::Executable().value().c_str(), - &command_line[0], // This cannot be constant, per MSDN. - nullptr, - nullptr, - true, // Inherit handles. - 0, - nullptr, - nullptr, - &startup_info, - &process_info)); - child_handle_.reset(process_info.hProcess); - CloseHandle(process_info.hThread); - - // Block until the child process has launched. CreateProcess() returns - // immediately, and test code expects process initialization to have - // completed so it can, for example, use the process handle. - char c; - CheckedReadFile(pipe_c2p_read_.get(), &c, sizeof(c)); - ASSERT_EQ(' ', c); - - // These have been passed to the child, close our side. - pipe_c2p_write_.reset(); - pipe_p2c_read_.reset(); - - WinMultiprocessParent(); - - // Close our side of the handles now that we're done. The child can - // use this to know when it's safe to complete. - pipe_p2c_write_.reset(); - pipe_c2p_read_.reset(); - - // Wait for the child to complete. - ASSERT_EQ(WAIT_OBJECT_0, - WaitForSingleObject(child_handle_.get(), INFINITE)); - - DWORD exit_code; - ASSERT_TRUE(GetExitCodeProcess(child_handle_.get(), &exit_code)); - ASSERT_EQ(exit_code_, exit_code); - } +WinMultiprocess::~WinMultiprocess() { } void WinMultiprocess::SetExpectedChildExitCode(unsigned int exit_code) { exit_code_ = exit_code; } -WinMultiprocess::~WinMultiprocess() { -} - FileHandle WinMultiprocess::ReadPipeHandle() const { - FileHandle handle = - child_handle_.get() ? pipe_c2p_read_.get() : pipe_p2c_read_.get(); - CHECK(handle != nullptr); - return handle; + if (child_handles_) + return child_handles_->read.get(); + CHECK(child_process_helper_); + return child_process_helper_->ReadPipeHandleForwarder(); } FileHandle WinMultiprocess::WritePipeHandle() const { - FileHandle handle = - child_handle_.get() ? pipe_p2c_write_.get() : pipe_c2p_write_.get(); - CHECK(handle != nullptr); - return handle; + if (child_handles_) + return child_handles_->write.get(); + CHECK(child_process_helper_); + return child_process_helper_->WritePipeHandleForwarder(); } void WinMultiprocess::CloseReadPipe() { - if (child_handle_.get()) - pipe_c2p_read_.reset(); - else - pipe_p2c_read_.reset(); + if (child_handles_) { + child_handles_->read.reset(); + } else { + CHECK(child_process_helper_); + child_process_helper_->CloseReadPipeForwarder(); + } } void WinMultiprocess::CloseWritePipe() { - if (child_handle_.get()) - pipe_p2c_write_.reset(); - else - pipe_c2p_write_.reset(); + if (child_handles_) { + child_handles_->write.reset(); + } else { + CHECK(child_process_helper_); + child_process_helper_->CloseWritePipeForwarder(); + } } HANDLE WinMultiprocess::ChildProcess() const { - EXPECT_NE(nullptr, child_handle_.get()); - return child_handle_.get(); + CHECK(child_handles_); + return child_handles_->process.get(); } } // namespace test diff --git a/test/win/win_multiprocess.h b/test/win/win_multiprocess.h index 7624adab..906fa5dd 100644 --- a/test/win/win_multiprocess.h +++ b/test/win/win_multiprocess.h @@ -18,6 +18,8 @@ #include #include "base/basictypes.h" +#include "gtest/gtest.h" +#include "test/win/win_child_process.h" #include "util/file/file_io.h" #include "util/win/scoped_handle.h" @@ -36,7 +38,34 @@ class WinMultiprocess { //! //! In the parent process, WinMultiprocessParent() is run, and in the child //! WinMultiprocessChild(). - void Run(); + template + static void Run() { + ASSERT_NO_FATAL_FAILURE( + WinChildProcess::EntryPoint>()); + // If WinChildProcess::EntryPoint returns, we are in the parent process. + scoped_ptr child_handles = + WinChildProcess::Launch(); + ASSERT_TRUE(child_handles.get()); + T parent_process; + parent_process.child_handles_ = child_handles.get(); + static_cast(&parent_process)->WinMultiprocessParent(); + + // Close our side of the handles now that we're done. The child can + // use this to know when it's safe to complete. + child_handles->read.reset(); + child_handles->write.reset(); + + // Wait for the child to complete. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(child_handles->process.get(), INFINITE)); + + DWORD exit_code; + ASSERT_TRUE(GetExitCodeProcess(child_handles->process.get(), &exit_code)); + ASSERT_EQ(parent_process.exit_code_, exit_code); + } + + protected: + virtual ~WinMultiprocess(); //! \brief Sets the expected exit code of the child process. //! @@ -45,9 +74,6 @@ class WinMultiprocess { //! \param[in] code The expected exit status of the child. void SetExpectedChildExitCode(unsigned int exit_code); - protected: - virtual ~WinMultiprocess(); - //! \brief Returns the read pipe's file handle. //! //! This method may be called by either the parent or the child process. @@ -89,6 +115,42 @@ class WinMultiprocess { HANDLE ChildProcess() const; private: + // Implements an adapter to provide WinMultiprocess with access to the + // anonymous pipe handles from WinChildProcess. + class ChildProcessHelperBase : public WinChildProcess { + public: + ChildProcessHelperBase() {} + ~ChildProcessHelperBase() override {} + + void CloseWritePipeForwarder() { CloseWritePipe(); } + void CloseReadPipeForwarder() { CloseReadPipe(); } + FileHandle ReadPipeHandleForwarder() const { return ReadPipeHandle(); } + FileHandle WritePipeHandleForwarder() const { return WritePipeHandle(); } + + private: + DISALLOW_COPY_AND_ASSIGN(ChildProcessHelperBase); + }; + + // Forwards WinChildProcess::Run to T::WinMultiprocessChild. + template + class ChildProcessHelper : public ChildProcessHelperBase { + public: + ChildProcessHelper() {} + ~ChildProcessHelper() override {} + + private: + int Run() override { + T child_process; + child_process.child_process_helper_ = this; + static_cast(&child_process)->WinMultiprocessChild(); + if (testing::Test::HasFailure()) + return 255; + return EXIT_SUCCESS; + } + + DISALLOW_COPY_AND_ASSIGN(ChildProcessHelper); + }; + //! \brief The subclass-provided parent routine. //! //! Test failures should be reported via gtest: `EXPECT_*()`, `ASSERT_*()`, @@ -111,12 +173,9 @@ class WinMultiprocess { //! method. virtual void WinMultiprocessChild() = 0; - ScopedFileHANDLE pipe_c2p_read_; - ScopedFileHANDLE pipe_c2p_write_; - ScopedFileHANDLE pipe_p2c_read_; - ScopedFileHANDLE pipe_p2c_write_; - ScopedKernelHANDLE child_handle_; unsigned int exit_code_; + WinChildProcess::Handles* child_handles_; + ChildProcessHelperBase* child_process_helper_; DISALLOW_COPY_AND_ASSIGN(WinMultiprocess); }; diff --git a/test/win/win_multiprocess_test.cc b/test/win/win_multiprocess_test.cc index eaf3d669..19af08e9 100644 --- a/test/win/win_multiprocess_test.cc +++ b/test/win/win_multiprocess_test.cc @@ -21,79 +21,62 @@ namespace crashpad { namespace test { namespace { +template class TestWinMultiprocess final : public WinMultiprocess { public: - explicit TestWinMultiprocess(unsigned int exit_code) - : WinMultiprocess(), exit_code_(exit_code) {} - - ~TestWinMultiprocess() {} + TestWinMultiprocess() {} private: // WinMultiprocess will have already exercised the pipes. - void WinMultiprocessParent() override {} + void WinMultiprocessParent() override { SetExpectedChildExitCode(ExitCode); } void WinMultiprocessChild() override { - exit(exit_code_); + exit(ExitCode); } - unsigned int exit_code_; - DISALLOW_COPY_AND_ASSIGN(TestWinMultiprocess); }; -enum class FailureType { - kExpect, - kAssert, -}; - -class TestWinMultiprocessChildFails final : public WinMultiprocess { +class TestWinMultiprocessChildAsserts final : public WinMultiprocess { public: - explicit TestWinMultiprocessChildFails(FailureType failure_type) - : WinMultiprocess(), failure_type_(failure_type) {} - ~TestWinMultiprocessChildFails() {} + TestWinMultiprocessChildAsserts() {} private: - void WinMultiprocessParent() override {} + void WinMultiprocessParent() override { SetExpectedChildExitCode(255); } void WinMultiprocessChild() override { - switch (failure_type_) { - case FailureType::kExpect: - EXPECT_FALSE(true); - break; - case FailureType::kAssert: - ASSERT_FALSE(true); - break; - } + ASSERT_FALSE(true); } - FailureType failure_type_; - - DISALLOW_COPY_AND_ASSIGN(TestWinMultiprocessChildFails); + DISALLOW_COPY_AND_ASSIGN(TestWinMultiprocessChildAsserts); }; +class TestWinMultiprocessChildExpects final : public WinMultiprocess { + public: + TestWinMultiprocessChildExpects() {} + + private: + void WinMultiprocessParent() override { SetExpectedChildExitCode(255); } + void WinMultiprocessChild() override { + EXPECT_FALSE(true); + } + + DISALLOW_COPY_AND_ASSIGN(TestWinMultiprocessChildExpects); +}; TEST(WinMultiprocess, WinMultiprocess) { - TestWinMultiprocess win_multiprocess(0); - win_multiprocess.Run(); + WinMultiprocess::Run>(); } TEST(WinMultiprocess, WinMultiprocessNonSuccessExitCode) { - TestWinMultiprocess win_multiprocess(100); - win_multiprocess.SetExpectedChildExitCode(100); - win_multiprocess.Run(); + WinMultiprocess::Run>(); } TEST(WinMultiprocessChildFails, ChildExpectFailure) { - TestWinMultiprocessChildFails multiprocess_failing_child( - FailureType::kExpect); - multiprocess_failing_child.SetExpectedChildExitCode(255); - multiprocess_failing_child.Run(); + WinMultiprocess::Run(); } TEST(WinMultiprocessChildFails, ChildAssertFailure) { - TestWinMultiprocessChildFails multiprocess_failing_child( - FailureType::kAssert); - multiprocess_failing_child.SetExpectedChildExitCode(255); - multiprocess_failing_child.Run(); + WinMultiprocess::Run(); } } // namespace