死亡测试是为了判断一段逻辑是否会导致进程退出而设计的。这种场景并不常见,但是GTest依然为我们设计了这个功能。我们先看下其应用实例。(转载请指明出于breaksoftware的csdn博客)
死亡测试技术应用
我们可以使用TEST声明并注册一个简单的测试特例。其实现内部才是死亡测试相关代码运行的地方。GTest为我们提供了如下的宏用于组织测试逻辑
Fatal assertion | Nonfatal assertion | Verifies |
ASSERT_DEATH(statement, regex); | EXPECT_DEATH(statement, regex); | statement crashes with the given error |
ASSERT_DEATH_IF_SUPPORTED(statement, regex); | EXPECT_DEATH_IF_SUPPORTED(statement, regex); | if death tests are supported, verifies that statement crashes with the given error; otherwise verifies nothing |
ASSERT_EXIT(statement, predicate, regex); | EXPECT_EXIT(statement, predicate, regex); | statement exits with the given error and its exit code matches predicate |
宏中的statement是测试逻辑的表达式,它可以是个函数,可以是个对象的方法,也可以是几个表达式的组合,比如
EXPECT_DEATH({ int n = 4; n = 5;},"");
regex是一个正则表达式,它用于匹配stderr输出的内容。如果匹配上了,则测试成功,否则测试失败。比如
void Foo() {
std::cerr<<"Failed Foo";
_exit(0);
}
EXPECT_DEATH(Foo(),".*Foo");
EXPECT_DEATH(Foo(),".*FAAA");
第5行的局部测试匹配上了测试预期,而第6行没有。
注意下正则表达式这个功能只支持linux系统,windows上不支持,所以windows上我们对此参数传空串。我们看个完整的例子
void Foo() {
std::cerr<<"Fail Foo";
_exit(0);
}
TEST(MyDeathTest, Foo) {
EXPECT_EXIT(Foo(), ::testing::ExitedWithCode(0), ".*Foo");
}
注意下我们测试用例名——MyDeathTest。GTest强烈建议测试用例名以DeathTest结尾。这是为了让死亡测试在所有其他测试之前运行。
死亡测试技术分析
死亡测试非常依赖于系统的实现。本文并不打算把每个系统都覆盖到,我将以windows系统上的实现详细讲解其过程。在Linux上实现的思路基本和windows上相同,只是在一些系统实现上存在差异导致GTest具有不同的属性。
先概括的讲一下windows上实现的过程
- 测试实体中准备启动新的进程,进程路径就是本进程可执行文件路径
- 子进程传入了标准输入输出句柄
- 启动子进程时传入类型筛选,即指定执行该测试用例
- 监听子进程的输出
- 判断子进程退出模式
子进程的执行过程是:
- 执行父进程指定的测试特例
- 运行死亡测试宏中的表达式
- 如果没有crash,则根据情况选择退出模式
我们来看下EXPECT_DEATH的实现,其最终将调用到GTEST_DEATH_TEST_宏中
# define GTEST_DEATH_TEST_(statement, predicate, regex, fail) \\
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \\
if (::testing::internal::AlwaysTrue()) { \\
const ::testing::internal::RE& gtest_regex = (regex); \\
::testing::internal::DeathTest* gtest_dt; \\
if (!::testing::internal::DeathTest::Create(#statement, >est_regex, \\
__FILE__, __LINE__, >est_dt)) { \\
goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \\
} \\
if (gtest_dt != NULL) { \\
::testing::internal::scoped_ptr< ::testing::internal::DeathTest> \\
gtest_dt_ptr(gtest_dt); \\
switch (gtest_dt->AssumeRole()) { \\
case ::testing::internal::DeathTest::OVERSEE_TEST: \\
if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) { \\
goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \\
} \\
break; \\
case ::testing::internal::DeathTest::EXECUTE_TEST: { \\
::testing::internal::DeathTest::ReturnSentinel \\
gtest_sentinel(gtest_dt); \\
GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt); \\
gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \\
break; \\
} \\
default: \\
break; \\
} \\
} \\
} else \\
GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__): \\
fail(::testing::internal::DeathTest::LastMessage())
第5行我们声明了一个DeathTest*指针,这个类暴露了一个静态方法用于创建对象。可以说它是一个接口类,我们看下它重要的部分定义
enum TestRole { OVERSEE_TEST, EXECUTE_TEST };
// An enumeration of the three reasons that a test might be aborted.
enum AbortReason {
TEST_ENCOUNTERED_RETURN_STATEMENT,
TEST_THREW_EXCEPTION,
TEST_DID_NOT_DIE
};
// Assumes one of the above roles.
virtual TestRole AssumeRole() = 0;
// Waits for the death test to finish and returns its status.
virtual int Wait() = 0;
// Returns true if the death test passed; that is, the test process
// exited during the test, its exit status matches a user-supplied
// predicate, and its stderr output matches a user-supplied regular
// expression.
// The user-supplied predicate may be a macro expression rather
// than a function pointer or functor, or else Wait and Passed could
// be combined.
virtual bool Passed(bool exit_status_ok) = 0;
// Signals that the death test did not die as expected.
virtual void Abort(AbortReason reason) = 0;
TestRole就是角色,我们父进程角色是OVERSEE_TEST,子进程的角色是EXECUTE_TEST。因为父子进程都将进入这个测试特例逻辑,所以要通过角色标记来区分执行逻辑。AbortReason枚举中类型表达了测试终止的原因。
AssumeRole是主要是父进程启动子进程的逻辑。Wait是父进程等待子进程执行完毕,并尝试读取子进程的输出。
DeathTest::Create方法最终会进入DefaultDeathTestFactory::Create方法
bool DefaultDeathTestFactory::Create(const char* statement, const RE* regex,
const char* file, int line,
DeathTest** test) {
UnitTestImpl* const impl = GetUnitTestImpl();
const InternalRunDeathTestFlag* const flag =
impl->internal_run_death_test_flag();
const int death_test_index = impl->current_test_info()
->increment_death_test_count();
if (flag != NULL) {
if (death_test_index > flag->index()) {
DeathTest::set_last_death_test_message(
"Death test count (" + StreamableToString(death_test_index)
+ ") somehow exceeded expected maximum ("
+ StreamableToString(flag->index()) + ")");
return false;
}
if (!(flag->file() == file && flag->line() == line &&
flag->index() == death_test_index)) {
*test = NULL;
return true;
}
}
此处通过获取flag变量,得知当前运行的是子进程还是父进程。如果flag不是NULL,则是子进程,它主要做些输出的工作;如果是父进程,则进入下面代码
# if GTEST_OS_WINDOWS
if (GTEST_FLAG(death_test_style) == "threadsafe" ||
GTEST_FLAG(death_test_style) == "fast") {
*test = new WindowsDeathTest(statement, regex, file, line);
}
# else
if (GTEST_FLAG(death_test_style) == "threadsafe") {
*test = new ExecDeathTest(statement, regex, file, line);
} else if (GTEST_FLAG(death_test_style) == "fast") {
*test = new NoExecDeathTest(statement, regex);
}
# endif // GTEST_OS_WINDOWS
可见Windows上死亡测试最终将由WindowsDeathTest代理,而linux系统根据传入参数不同而选择不同的类。它们都是DeathTest的派生类。为什么linux系统上支持参数选择,这要从系统暴露出来的接口和系统实现来说。windows系统上进程创建只要调用CreateProcess之类的函数就可以了,这个函数调用后,子进程就创建出来了。而linux系统上则要调用fork或者clone之类,这两种函数执行机制也不太相同。fork是标准的子进程和父进程分离执行,所以threadsafe对应的ExecDeathTest类在底层调用的是fork,从而可以保证是安全的。但是clone用于创建轻量级进程,即创建的子进程与父进程共用线性地址空间,只是它们的堆栈不同,这样不用执行父子进程分离,执行当然会快些,所以这种方式对应的是fast——NoExecDeathTest。
我们看下WindowsDeathTest::AssumeRole()的实现
// The AssumeRole process for a Windows death test. It creates a child
// process with the same executable as the current process to run the
// death test. The child process is given the --gtest_filter and
// --gtest_internal_run_death_test flags such that it knows to run the
// current death test only.
DeathTest::TestRole WindowsDeathTest::AssumeRole() {
const UnitTestImpl* const impl = GetUnitTestImpl();
const InternalRunDeathTestFlag* const flag =
impl->internal_run_death_test_flag();
const TestInfo* const info = impl->current_test_info();
const int death_test_index = info->result()->death_test_count();
if (flag != NULL) {
// ParseInternalRunDeathTestFlag() has performed all the necessary
// processing.
set_write_fd(flag->write_fd());
return EXECUTE_TEST;
}
这段代码的注释写的很清楚,父进程将向子进程传递什么样的参数。
和之前一样,需要获取flag,如果不是NULL,则是子进程,设置写入句柄,并返回自己角色。如果是父进程则执行下面逻辑
// WindowsDeathTest uses an anonymous pipe to communicate results of
// a death test.
SECURITY_ATTRIBUTES handles_are_inheritable = {
sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
HANDLE read_handle, write_handle;
GTEST_DEATH_TEST_CHECK_(
::CreatePipe(&read_handle, &write_handle, &handles_are_inheritable,
0) // Default buffer size.
!= FALSE);
set_read_fd(::_open_osfhandle(reinterpret_cast<intptr_t>(read_handle),
O_RDONLY));
write_handle_.Reset(write_handle);
event_handle_.Reset(::CreateEvent(
&handles_are_inheritable,
TRUE, // The event will automatically reset to non-signaled state.
FALSE, // The initial state is non-signalled.
NULL)); // The even is unnamed.
GTEST_DEATH_TEST_CHECK_(event_handle_.Get() != NULL);
const std::string filter_flag =
std::string("--") + GTEST_FLAG_PREFIX_ + kFilterFlag + "=" +
info->test_case_name() + "." + info->name();
const std::string internal_flag =
std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag +
"=" + file_ + "|" + StreamableToString(line_) + "|" +
StreamableToString(death_test_index) + "|" +
StreamableToString(static_cast<unsigned int>(::GetCurrentProcessId())) +
// size_t has the same width as pointers on both 32-bit and 64-bit
// Windows platforms.
// See http://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx.
"|" + StreamableToString(reinterpret_cast<size_t>(write_handle)) +
"|" + StreamableToString(reinterpret_cast<size_t>(event_handle_.Get()));
char executable_path[_MAX_PATH + 1]; // NOLINT
GTEST_DEATH_TEST_CHECK_(
_MAX_PATH + 1 != ::GetModuleFileNameA(NULL,
executable_path,
_MAX_PATH));
std::string command_line =
std::string(::GetCommandLineA()) + " " + filter_flag + " \\"" +
internal_flag + "\\"";
DeathTest::set_last_death_test_message("");
CaptureStderr();
// Flush the log buffers since the log streams are shared with the child.
FlushInfoLog();
// The child process will share the standard handles with the parent.
STARTUPINFOA startup_info;
memset(&startup_info, 0, sizeof(STARTUPINFO));
startup_info.dwFlags = STARTF_USESTDHANDLES;
startup_info.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE);
startup_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE);
startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE);
PROCESS_INFORMATION process_info;
GTEST_DEATH_TEST_CHECK_(::CreateProcessA(
executable_path,
const_cast<char*>(command_line.c_str()),
NULL, // Retuned process handle is not inheritable.
NULL, // Retuned thread handle is not inheritable.
TRUE, // Child inherits all inheritable handles (for write_handle_).
0x0, // Default creation flags.
NULL, // Inherit the parent's environment.
UnitTest::GetInstance()->original_working_dir(),
&startup_info,
&process_info) != FALSE);
child_handle_.Reset(process_info.hProcess);
::CloseHandle(process_info.hThread);
set_spawned(true);
return OVERSEE_TEST;
这段逻辑创建了父进程和子进程通信的匿名管道和事件句柄,这些都通过命令行参数传递给子进程。
我们再看下父进程等待的过程
int WindowsDeathTest::Wait() {
if (!spawned())
return 0;
// Wait until the child either signals that it has acquired the write end
// of the pipe or it dies.
const HANDLE wait_handles[2] = { child_handle_.Get(), event_handle_.Get() };
switch (::WaitForMultipleObjects(2,
wait_handles,
FALSE, // Waits for any of the handles.
INFINITE)) {
case WAIT_OBJECT_0:
case WAIT_OBJECT_0 + 1:
break;
default:
GTEST_DEATH_TEST_CHECK_(false); // Should not get here.
}
// The child has acquired the write end of the pipe or exited.
// We release the handle on our side and continue.
write_handle_.Reset();
event_handle_.Reset();
ReadAndInterpretStatusByte();
它等待子进程句柄或者完成事件。一旦等到,则在ReadAndInterpretStatusByte中读取子进程的输出
void DeathTestImpl::ReadAndInterpretStatusByte() {
char flag;
int bytes_read;
// The read() here blocks until data is available (signifying the
// failure of the death test) or until the pipe is closed (signifying
// its success), so it's okay to call this in the parent before
// the child process has exited.
do {
bytes_read = posix::Read(read_fd(), &flag, 1);
} while (bytes_read == -1 && errno == EINTR);
if (bytes_read == 0) {
set_outcome(DIED);
} else if (bytes_read == 1) {
switch (flag) {
case kDeathTestReturned:
set_outcome(RETURNED);
break;
case kDeathTestThrew:
set_outcome(THREW);
break;
case kDeathTestLived:
set_outcome(LIVED);
break;
case kDeathTestInternalError:
FailFromInternalError(read_fd()); // Does not return.
break;
default:
GTEST_LOG_(FATAL) << "Death test child process reported "
<< "unexpected status byte ("
<< static_cast<unsigned int>(flag) << ")";
}
} else {
GTEST_LOG_(FATAL) << "Read from death test child process failed: "
<< GetLastErrnoDescription();
}
GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Close(read_fd()));
set_read_fd(-1);
}
这段代码可以用于区分子进程的退出状态。如果子进程crash了,则读取不到数据,进入第14行。
子进程则是执行完表达式后调用Abort返回相应错误。GTEST_DEATH_TEST_剩下的实现,把这个过程表达的很清楚
if (gtest_dt != NULL) { \\
::testing::internal::scoped_ptr< ::testing::internal::DeathTest> \\
gtest_dt_ptr(gtest_dt); \\
switch (gtest_dt->AssumeRole()) { \\
case ::testing::internal::DeathTest::OVERSEE_TEST: \\
if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) { \\
goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \\
} \\
break; \\
case ::testing::internal::DeathTest::EXECUTE_TEST: { \\
::testing::internal::DeathTest::ReturnSentinel \\
gtest_sentinel(gtest_dt); \\
GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt); \\
gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \\
break; \\
} \\
default: \\
break; \\
} \\
} \\
暂无评论内容