mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
Refactor multiprocess test code to allow multiple child processes to be launched.
BUG= R=scottmg@chromium.org Review URL: https://codereview.chromium.org/1164453003 .
This commit is contained in:
parent
797adb3206
commit
263582c2d0
@ -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 <TestType Type>
|
||||
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<TestPEImageAnnotationsReader<kDontCrash>>();
|
||||
}
|
||||
|
||||
TEST(PEImageAnnotationsReader, CrashDebugBreak) {
|
||||
TestPEImageAnnotationsReader test_pe_image_annotations_reader(
|
||||
TestPEImageAnnotationsReader::kCrashDebugBreak);
|
||||
test_pe_image_annotations_reader.Run();
|
||||
WinMultiprocess::Run<TestPEImageAnnotationsReader<kCrashDebugBreak>>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -85,8 +85,7 @@ class ProcessReaderChild final : public WinMultiprocess {
|
||||
};
|
||||
|
||||
TEST(ProcessReaderWin, ChildBasic) {
|
||||
ProcessReaderChild process_reader_child;
|
||||
process_reader_child.Run();
|
||||
WinMultiprocess::Run<ProcessReaderChild>();
|
||||
}
|
||||
|
||||
TEST(ProcessReaderWin, SelfOneThread) {
|
||||
|
@ -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',
|
||||
],
|
||||
},
|
||||
}],
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -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',
|
||||
],
|
||||
},
|
||||
|
238
test/win/win_child_process.cc
Normal file
238
test/win/win_child_process.cc
Normal file
@ -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 <windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#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<HLOCAL, LocalFreeTraits>;
|
||||
|
||||
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<HANDLE>(write));
|
||||
pipe_read_.reset(reinterpret_cast<HANDLE>(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::Handles> WinChildProcess::Launch() {
|
||||
// Make pipes for child-to-parent and parent-to-child communication.
|
||||
scoped_ptr<Handles> 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<Handles>();
|
||||
}
|
||||
|
||||
if (!CreateInheritablePipe(
|
||||
&read_for_child, true, &handles_for_parent->write, false)) {
|
||||
return scoped_ptr<Handles>();
|
||||
}
|
||||
|
||||
// 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<Handles>();
|
||||
|
||||
// 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<Handles>();
|
||||
}
|
||||
|
||||
if (c != ' ') {
|
||||
ADD_FAILURE() << "invalid data read from child";
|
||||
return scoped_ptr<Handles>();
|
||||
}
|
||||
|
||||
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
|
117
test/win/win_child_process.h
Normal file
117
test/win/win_child_process.h
Normal file
@ -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 <class T>
|
||||
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<WinChildProcess*>(&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<Handles> 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_
|
81
test/win/win_child_process_test.cc
Normal file
81
test/win/win_child_process_test.cc
Normal file
@ -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 <windows.h>
|
||||
|
||||
#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<TestWinChildProcess>();
|
||||
|
||||
WinChildProcess::Launch();
|
||||
}
|
||||
|
||||
TEST(WinChildProcessTest, MultipleChildren) {
|
||||
WinChildProcess::EntryPoint<TestWinChildProcess>();
|
||||
|
||||
scoped_ptr<WinChildProcess::Handles> handles_1 = WinChildProcess::Launch();
|
||||
scoped_ptr<WinChildProcess::Handles> handles_2 = WinChildProcess::Launch();
|
||||
scoped_ptr<WinChildProcess::Handles> 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
|
@ -15,13 +15,11 @@
|
||||
#include "test/win/win_multiprocess.h"
|
||||
|
||||
#include <shellapi.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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<HLOCAL, LocalFreeTraits>;
|
||||
|
||||
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<HANDLE>(c2p_write));
|
||||
pipe_p2c_read_.reset(reinterpret_cast<HANDLE>(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
|
||||
|
@ -18,6 +18,8 @@
|
||||
#include <windows.h>
|
||||
|
||||
#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 <class T>
|
||||
static void Run() {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
WinChildProcess::EntryPoint<ChildProcessHelper<T>>());
|
||||
// If WinChildProcess::EntryPoint returns, we are in the parent process.
|
||||
scoped_ptr<WinChildProcess::Handles> child_handles =
|
||||
WinChildProcess::Launch();
|
||||
ASSERT_TRUE(child_handles.get());
|
||||
T parent_process;
|
||||
parent_process.child_handles_ = child_handles.get();
|
||||
static_cast<WinMultiprocess*>(&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 T>
|
||||
class ChildProcessHelper : public ChildProcessHelperBase {
|
||||
public:
|
||||
ChildProcessHelper() {}
|
||||
~ChildProcessHelper() override {}
|
||||
|
||||
private:
|
||||
int Run() override {
|
||||
T child_process;
|
||||
child_process.child_process_helper_ = this;
|
||||
static_cast<WinMultiprocess*>(&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);
|
||||
};
|
||||
|
@ -21,79 +21,62 @@ namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
template <int ExitCode>
|
||||
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<TestWinMultiprocess<0>>();
|
||||
}
|
||||
|
||||
TEST(WinMultiprocess, WinMultiprocessNonSuccessExitCode) {
|
||||
TestWinMultiprocess win_multiprocess(100);
|
||||
win_multiprocess.SetExpectedChildExitCode(100);
|
||||
win_multiprocess.Run();
|
||||
WinMultiprocess::Run<TestWinMultiprocess<100>>();
|
||||
}
|
||||
|
||||
TEST(WinMultiprocessChildFails, ChildExpectFailure) {
|
||||
TestWinMultiprocessChildFails multiprocess_failing_child(
|
||||
FailureType::kExpect);
|
||||
multiprocess_failing_child.SetExpectedChildExitCode(255);
|
||||
multiprocess_failing_child.Run();
|
||||
WinMultiprocess::Run<TestWinMultiprocessChildExpects>();
|
||||
}
|
||||
|
||||
TEST(WinMultiprocessChildFails, ChildAssertFailure) {
|
||||
TestWinMultiprocessChildFails multiprocess_failing_child(
|
||||
FailureType::kAssert);
|
||||
multiprocess_failing_child.SetExpectedChildExitCode(255);
|
||||
multiprocess_failing_child.Run();
|
||||
WinMultiprocess::Run<TestWinMultiprocessChildAsserts>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
Loading…
x
Reference in New Issue
Block a user