死亡测试是为了判断一段逻辑是否会导致进程退出而设计的。这种场景并不常见,但是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上实现的过程

  1. 测试实体中准备启动新的进程,进程路径就是本进程可执行文件路径
  2. 子进程传入了标准输入输出句柄
  3. 启动子进程时传入类型筛选,即指定执行该测试用例
  4. 监听子进程的输出
  5. 判断子进程退出模式

子进程的执行过程是:

  1. 执行父进程指定的测试特例
  2. 运行死亡测试宏中的表达式
  3. 如果没有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_WINDOWSif (GTEST_FLAG(death_test_style) == "threadsafe" ||GTEST_FLAG(death_test_style) == "fast") {*test = new WindowsDeathTest(statement, regex, file, line);}# elseif (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];  // NOLINTGTEST_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; \} \} \

Google Test(GTest)使用方法和源码解析——死亡测试技术分析和应用相关推荐

  1. Google Test(GTest)使用方法和源码解析——结果统计机制分析

    在分析源码之前,我们先看一个例子.以<Google Test(GTest)使用方法和源码解析--概况 >一文中最后一个实例代码为基准,修改最后一个"局部测试"结果为错误 ...

  2. Google Test(GTest)使用方法和源码解析——自动调度机制分析

    在<Google Test(GTest)使用方法和源码解析--概况 >一文中,我们简单介绍了下GTest的使用和特性.从这篇博文开始,我们将深入代码,研究这些特性的实现.(转载请指明出于b ...

  3. Google Test(GTest)使用方法和源码解析——自定义输出技术的分析和应用

    在介绍自定义输出机制之前,我们先了解下AssertResult类型函数.(转载请指明出于breaksoftware的csdn博客) 在函数中使用AssertionResult AssertionRes ...

  4. Google Test(GTest)使用方法和源码解析——模板类测试技术分析和应用

    写C++难免会遇到模板问题,如果要针对一个模板类进行测试,似乎之前博文中介绍的方式只能傻乎乎的一个一个特化类型后再进行测试.其实GTest提供了两种测试模板类的方法,本文我们将介绍方法的使用,并分析其 ...

  5. Google Test(GTest)使用方法和源码解析——参数自动填充技术分析和应用

    在我们设计测试用例时,我们需要考虑很多场景.每个场景都可能要细致地考虑到到各个参数的选择.比如我们希望使用函数IsPrime检测10000以内字的数字,难道我们要写一万行代码么?(转载请指明出于bre ...

  6. Google Test(GTest)使用方法和源码解析——预处理技术分析和应用

    预处理 在<Google Test(GTest)使用方法和源码解析--概况>最后一部分,我们介绍了GTest的预处理特性.现在我们就详细介绍该特性的使用和相关源码.(转载请指明出于brea ...

  7. Google Test(GTest)使用方法和源码解析——断言的使用方法和解析

    在之前博文的基础上,我们将介绍部分断言的使用,同时穿插一些源码.(转载请指明出于breaksoftware的csdn博客) 断言(Assertions) 断言是GTest局部测试中最简单的使用方法,我 ...

  8. Google Test(GTest)使用方法和源码解析——Listener技术分析和应用

    在<Google Test(GTest)使用方法和源码解析--结果统计机制分析>文中,我么分析了GTest如何对测试结果进行统计的.本文我们将解析其结果输出所使用到的Listener机制. ...

  9. Google Test(GTest)使用方法和源码解析——私有属性代码测试技术分析

    有些时候,我们不仅要测试类暴露出来的公有方法,还要测试其受保护的或者私有方法.GTest测试框架提供了一种方法,让我们可以测试类的私有方法.但是这是一种侵入式的,会破坏原来代码的结构,所以我觉得还是谨 ...

最新文章

  1. bat 取得服务列表_基于IDEA热部署更新服务器Tomcat类,服务器Tomcat热更新
  2. 从JVM看类的加载过程与对象实例化过程
  3. 小程序swiper(tab)高度自适应
  4. 【BZOJ4237】稻草人
  5. Activiti 简易教程
  6. php 抽象类 静态方法吗,php中的抽象类和静态方法是什么
  7. 字符串数值的比较 java
  8. php文本框长度限制,php截取富文本框中的固定长度的字符
  9. warning: initialization from incompatible pointer type error, forbidden解决
  10. iOS 6分享列表——UIActivityViewController详解
  11. Solr 4.10.3 后台管理页面介绍
  12. 计算机编程和机器人编程有什么不同,编程和机器人编程的区别
  13. haneWIN NFS服务器端 V1.1.69 汉化版
  14. oracle 中 的 =,oracle中=是什么意思
  15. git提交Push to origin/master was rejected
  16. mad和php的区别,良心解析kakaKUC-MAD好用吗?怎么样呢?体验揭秘分析
  17. Wikidata知识图谱介绍与数据处理
  18. 如何查看linux系统的状态,如何查看Linux和Mac机器的系统状态
  19. RankNet - LambdaRank - LambdaMART
  20. 分析程序员为什么单身

热门文章

  1. 【PostgreSQL】行变列、非空(CASE WHEN、COALESCE)语句总结
  2. PCL:求两条直线交点
  3. 2020阿里全球数学大赛:3万名高手、4道题、2天2夜未交卷,73人天团,正式出道!
  4. CS131-专题7:图像特征(SIFT算法)
  5. linux arm中断跑马灯,S3C2410 MDK实验---ARM汇编语言实现跑马灯
  6. python 视频播放 拖动_视频画中画效果,拖动进度条可以seek到相应视频帧显示
  7. python显示行数_在idle中如何显示行号
  8. 从2D到3D的目标检测综述
  9. Learn OpenGL (四):纹理
  10. Angular多个页面引入同一个组件报错The Component ‘MyComponentComponent‘ is declared by more than one NgModule怎么办?