mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-26 23:01:05 +08:00
Add CRASHPAD_CHILD_TEST_MAIN() helper for multiprocess tests
Extends MultiprocessExec to support running functions registered via CRASHPAD_CHILD_TEST_MAIN() as the main of a new child process. Additionally, implements Fuchsia exit code checking, and adds a CRASHPAD_CHILD_TEST_MAIN()-based test for that. Bug: crashpad:196, crashpad:215 Change-Id: I49ce3f4d95a3b9823813e6df5a602cee2583bcf8 Reviewed-on: https://chromium-review.googlesource.com/879563 Commit-Queue: Scott Graham <scottmg@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
parent
26cd6138af
commit
48abd4a60f
@ -407,7 +407,8 @@ def _RunOnFuchsiaTarget(binary_dir, test, device_name, extra_command_line):
|
||||
|
||||
done_message = 'TERMINATED: ' + unique_id
|
||||
namespace_command = [
|
||||
'namespace', '/pkg=' + staging_root, '/tmp=' + tmp_root, '--',
|
||||
'namespace', '/pkg=' + staging_root, '/tmp=' + tmp_root,
|
||||
'--replace-child-argv0=/pkg/bin/' + test, '--',
|
||||
staging_root + '/bin/' + test] + extra_command_line
|
||||
netruncmd(namespace_command, ['echo', done_message])
|
||||
|
||||
|
@ -32,6 +32,7 @@ static_library("test") {
|
||||
"main_arguments.cc",
|
||||
"main_arguments.h",
|
||||
"multiprocess.h",
|
||||
"multiprocess_exec.cc",
|
||||
"multiprocess_exec.h",
|
||||
"process_type.cc",
|
||||
"process_type.h",
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/gtest_disabled.h"
|
||||
#include "test/main_arguments.h"
|
||||
#include "test/multiprocess_exec.h"
|
||||
|
||||
#if defined(CRASHPAD_TEST_LAUNCHER_GMOCK)
|
||||
#include "gmock/gmock.h"
|
||||
@ -31,11 +32,34 @@
|
||||
#include "base/test/test_suite.h"
|
||||
#endif // CRASHPAD_IS_IN_CHROMIUM
|
||||
|
||||
namespace {
|
||||
|
||||
bool GetChildTestFunctionName(std::string* child_func_name) {
|
||||
constexpr size_t arg_length =
|
||||
sizeof(crashpad::test::internal::kChildTestFunction) - 1;
|
||||
for (const auto& it : crashpad::test::GetMainArguments()) {
|
||||
if (it.compare(
|
||||
0, arg_length, crashpad::test::internal::kChildTestFunction) == 0) {
|
||||
*child_func_name = it.substr(arg_length);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
crashpad::test::InitializeMainArguments(argc, argv);
|
||||
testing::AddGlobalTestEnvironment(
|
||||
crashpad::test::DisabledTestGtestEnvironment::Get());
|
||||
|
||||
std::string child_func_name;
|
||||
if (GetChildTestFunctionName(&child_func_name)) {
|
||||
return crashpad::test::internal::CheckedInvokeMultiprocessChild(
|
||||
child_func_name);
|
||||
}
|
||||
|
||||
#if defined(CRASHPAD_IS_IN_CHROMIUM)
|
||||
|
||||
#if defined(OS_WIN)
|
||||
|
@ -49,11 +49,13 @@ class Multiprocess {
|
||||
//! that call `exit()` or `_exit()`.
|
||||
kTerminationNormal = false,
|
||||
|
||||
#if !defined(OS_FUCHSIA) // There are no signals on Fuchsia.
|
||||
//! \brief The child terminated by signal.
|
||||
//!
|
||||
//! Signal termination happens as a result of a crash, a call to `abort()`,
|
||||
//! assertion failure (including gtest assertions), etc.
|
||||
kTerminationSignal,
|
||||
#endif // !defined(OS_FUCHSIA)
|
||||
};
|
||||
|
||||
Multiprocess();
|
||||
|
74
test/multiprocess_exec.cc
Normal file
74
test/multiprocess_exec.cc
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2018 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/multiprocess_exec.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "test/main_arguments.h"
|
||||
#include "util/stdlib/map_insert.h"
|
||||
#include "test/test_paths.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
|
||||
namespace internal {
|
||||
|
||||
namespace {
|
||||
|
||||
std::map<std::string, int(*)()>* GetMultiprocessFunctionMap() {
|
||||
static auto* map = new std::map<std::string, int(*)()>();
|
||||
return map;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AppendMultiprocessTest::AppendMultiprocessTest(const std::string& test_name,
|
||||
int (*main_function_pointer)()) {
|
||||
CHECK(MapInsertOrReplace(
|
||||
GetMultiprocessFunctionMap(), test_name, main_function_pointer, nullptr))
|
||||
<< test_name << " already registered";
|
||||
}
|
||||
|
||||
int CheckedInvokeMultiprocessChild(const std::string& test_name) {
|
||||
const auto* functions = internal::GetMultiprocessFunctionMap();
|
||||
auto it = functions->find(test_name);
|
||||
CHECK(it != functions->end())
|
||||
<< "child main " << test_name << " not registered";
|
||||
return (*it->second)();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
void MultiprocessExec::SetChildTestMainFunction(
|
||||
const std::string& function_name) {
|
||||
std::vector<std::string> rest(GetMainArguments().begin() + 1,
|
||||
GetMainArguments().end());
|
||||
rest.push_back(internal::kChildTestFunction + function_name);
|
||||
|
||||
#if defined(OS_WIN)
|
||||
// Instead of using argv[0] on Windows, use the actual binary name. This is
|
||||
// necessary because if originally the test isn't run with ".exe" on the
|
||||
// command line, then argv[0] also won't include ".exe". This argument is used
|
||||
// as the lpApplicationName argument to CreateProcess(), and so will fail.
|
||||
SetChildCommand(TestPaths::Executable(), &rest);
|
||||
#else
|
||||
SetChildCommand(base::FilePath(GetMainArguments()[0]), &rest);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
@ -23,9 +23,57 @@
|
||||
#include "build/build_config.h"
|
||||
#include "test/multiprocess.h"
|
||||
|
||||
//! \file
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
|
||||
namespace internal {
|
||||
|
||||
//! \brief Command line argument used to indicate that a child test function
|
||||
//! should be run.
|
||||
constexpr char kChildTestFunction[] = "--child-test-function=";
|
||||
|
||||
|
||||
//! \brief Helper class used by CRASHPAD_CHILD_TEST_MAIN() to insert a child
|
||||
//! function into the global mapping.
|
||||
class AppendMultiprocessTest {
|
||||
public:
|
||||
AppendMultiprocessTest(const std::string& test_name,
|
||||
int (*main_function_pointer)());
|
||||
};
|
||||
|
||||
//! \brief Used to run a child test function by name, registered by
|
||||
//! CRASHPAD_CHILD_TEST_MAIN().
|
||||
//!
|
||||
//! \return The exit code of the child process after running the function named
|
||||
//! by \a test_name. Aborts with a CHECK() if \a test_name wasn't
|
||||
//! registered.
|
||||
int CheckedInvokeMultiprocessChild(const std::string& test_name);
|
||||
|
||||
} // namespace internal
|
||||
|
||||
//! \brief Registers a function that can be invoked as a child process by
|
||||
//! MultiprocessExec.
|
||||
//!
|
||||
//! Used as:
|
||||
//!
|
||||
//! \code
|
||||
//! CRASHPAD_CHILD_TEST_MAIN(MyChildTestBody) {
|
||||
//! ... child body ...
|
||||
//! }
|
||||
//! \endcode
|
||||
//!
|
||||
//! In the main (parent) test body, this function can be run in a child process
|
||||
//! via MultiprocessExec::SetChildTestMainFunction().
|
||||
#define CRASHPAD_CHILD_TEST_MAIN(test_main) \
|
||||
int test_main(); \
|
||||
namespace { \
|
||||
::crashpad::test::internal::AppendMultiprocessTest \
|
||||
AddMultiprocessTest##_##test_main(#test_main, (test_main)); \
|
||||
} /* namespace */ \
|
||||
int test_main()
|
||||
|
||||
//! \brief Manages an `exec()`-based multiprocess test.
|
||||
//!
|
||||
//! These tests are based on `fork()` and `exec()`. The parent process is able
|
||||
@ -44,14 +92,31 @@ class MultiprocessExec : public Multiprocess {
|
||||
//!
|
||||
//! This method must be called before the test can be Run().
|
||||
//!
|
||||
//! This method is useful when a custom executable is required for the child
|
||||
//! binary, however, SetChildTestMainFunction() should generally be preferred.
|
||||
//!
|
||||
//! \param[in] command The executable’s pathname.
|
||||
//! \param[in] arguments The command-line arguments to pass to the child
|
||||
//! process in its `argv[]` vector. This vector must begin at `argv[1]`,
|
||||
//! as \a command is implicitly used as `argv[0]`. This argument may be
|
||||
//! `nullptr` if no command-line arguments are to be passed.
|
||||
//!
|
||||
//! \sa SetChildTestMainFunction
|
||||
void SetChildCommand(const base::FilePath& command,
|
||||
const std::vector<std::string>* arguments);
|
||||
|
||||
//! \brief Calls SetChildCommand() to run a child test main function
|
||||
//! registered with CRASHPAD_CHILD_TEST_MAIN().
|
||||
//!
|
||||
//! This uses the same launch mechanism as SetChildCommand(), but coordinates
|
||||
//! with test/gtest_main.cc to allow for simple registration of a child
|
||||
//! processes' entry point via the helper macro, rather than needing to
|
||||
//! create a separate build target.
|
||||
//!
|
||||
//! \param[in] function_name The name of the function as passed to
|
||||
//! CRASHPAD_CHILD_TEST_MAIN().
|
||||
void SetChildTestMainFunction(const std::string& function_name);
|
||||
|
||||
protected:
|
||||
~MultiprocessExec();
|
||||
|
||||
|
@ -51,13 +51,39 @@ void Multiprocess::Run() {
|
||||
// And then run the parent actions in this process.
|
||||
RunParent();
|
||||
|
||||
// Reap the child.
|
||||
// Wait until the child exits.
|
||||
zx_signals_t signals;
|
||||
ASSERT_EQ(
|
||||
zx_object_wait_one(
|
||||
info_->child.get(), ZX_TASK_TERMINATED, ZX_TIME_INFINITE, &signals),
|
||||
ZX_OK);
|
||||
ASSERT_EQ(signals, ZX_TASK_TERMINATED);
|
||||
|
||||
// Get the child's exit code.
|
||||
zx_info_process_t proc_info;
|
||||
zx_status_t status = zx_object_get_info(info_->child.get(),
|
||||
ZX_INFO_PROCESS,
|
||||
&proc_info,
|
||||
sizeof(proc_info),
|
||||
nullptr,
|
||||
nullptr);
|
||||
if (status != ZX_OK) {
|
||||
ZX_LOG(ERROR, status) << "zx_object_get_info";
|
||||
ADD_FAILURE() << "Unable to get exit code of child";
|
||||
} else {
|
||||
if (code_ != proc_info.return_code) {
|
||||
ADD_FAILURE() << "Child exited with code " << proc_info.return_code
|
||||
<< ", expected exit with code " << code_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Multiprocess::SetExpectedChildTermination(TerminationReason reason,
|
||||
int code) {
|
||||
EXPECT_EQ(info_, nullptr)
|
||||
<< "SetExpectedChildTermination() must be called before Run()";
|
||||
reason_ = reason;
|
||||
code_ = code;
|
||||
}
|
||||
|
||||
Multiprocess::~Multiprocess() {
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "test/multiprocess_exec.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "build/build_config.h"
|
||||
@ -56,6 +57,47 @@ TEST(MultiprocessExec, MultiprocessExec) {
|
||||
multiprocess_exec.Run();
|
||||
}
|
||||
|
||||
|
||||
CRASHPAD_CHILD_TEST_MAIN(SimpleMultiprocess) {
|
||||
char c;
|
||||
CheckedReadFileExactly(StdioFileHandle(StdioStream::kStandardInput), &c, 1);
|
||||
LOG_IF(FATAL, c != 'z');
|
||||
|
||||
c = 'Z';
|
||||
CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput), &c, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST(MultiprocessExec, MultiprocessExecSimpleChild) {
|
||||
TestMultiprocessExec exec;
|
||||
exec.SetChildTestMainFunction("SimpleMultiprocess");
|
||||
exec.Run();
|
||||
};
|
||||
|
||||
|
||||
CRASHPAD_CHILD_TEST_MAIN(SimpleMultiprocessReturnsNonZero) {
|
||||
return 123;
|
||||
}
|
||||
|
||||
class TestMultiprocessExecEmpty final : public MultiprocessExec {
|
||||
public:
|
||||
TestMultiprocessExecEmpty() = default;
|
||||
~TestMultiprocessExecEmpty() = default;
|
||||
|
||||
private:
|
||||
void MultiprocessParent() override {}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TestMultiprocessExecEmpty);
|
||||
};
|
||||
|
||||
TEST(MultiprocessExec, MultiprocessExecSimpleChildReturnsNonZero) {
|
||||
TestMultiprocessExecEmpty exec;
|
||||
exec.SetChildTestMainFunction("SimpleMultiprocessReturnsNonZero");
|
||||
exec.SetExpectedChildTermination(
|
||||
Multiprocess::TerminationReason::kTerminationNormal, 123);
|
||||
exec.Run();
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
||||
|
@ -57,6 +57,14 @@ void Multiprocess::Run() {
|
||||
CloseHandle(info_->process_info.hProcess);
|
||||
}
|
||||
|
||||
void Multiprocess::SetExpectedChildTermination(TerminationReason reason,
|
||||
int code) {
|
||||
EXPECT_EQ(info_, nullptr)
|
||||
<< "SetExpectedChildTermination() must be called before Run()";
|
||||
reason_ = reason;
|
||||
code_ = code;
|
||||
}
|
||||
|
||||
Multiprocess::~Multiprocess() {
|
||||
delete info_;
|
||||
}
|
||||
|
@ -58,6 +58,7 @@
|
||||
'main_arguments.cc',
|
||||
'main_arguments.h',
|
||||
'multiprocess.h',
|
||||
'multiprocess_exec.cc',
|
||||
'multiprocess_exec.h',
|
||||
'multiprocess_exec_posix.cc',
|
||||
'multiprocess_exec_win.cc',
|
||||
|
Loading…
x
Reference in New Issue
Block a user