前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Google Test(GTest)使用方法和源码解析——死亡测试技术分析和应用

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

作者头像
方亮
发布2019-01-16 16:29:17
2.4K0
发布2019-01-16 16:29:17
举报
文章被收录于专栏:方亮方亮

        死亡测试是为了判断一段逻辑是否会导致进程退出而设计的。这种场景并不常见,但是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是测试逻辑的表达式,它可以是个函数,可以是个对象的方法,也可以是几个表达式的组合,比如

代码语言:javascript
复制
EXPECT_DEATH({ int n = 4; n = 5;},"");

        regex是一个正则表达式,它用于匹配stderr输出的内容。如果匹配上了,则测试成功,否则测试失败。比如

代码语言:javascript
复制
void Foo() {
	std::cerr<<"Failed Foo";
	_exit(0);
}
EXPECT_DEATH(Foo(),".*Foo");
EXPECT_DEATH(Foo(),".*FAAA");

        第5行的局部测试匹配上了测试预期,而第6行没有。

        注意下正则表达式这个功能只支持linux系统,windows上不支持,所以windows上我们对此参数传空串。我们看个完整的例子

代码语言:javascript
复制
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_宏中

代码语言:javascript
复制
# 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*指针,这个类暴露了一个静态方法用于创建对象。可以说它是一个接口类,我们看下它重要的部分定义

代码语言:javascript
复制
  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方法

代码语言:javascript
复制
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,则是子进程,它主要做些输出的工作;如果是父进程,则进入下面代码

代码语言:javascript
复制
# 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()的实现

代码语言:javascript
复制
// 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,则是子进程,设置写入句柄,并返回自己角色。如果是父进程则执行下面逻辑

代码语言:javascript
复制
  // 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;

        这段逻辑创建了父进程和子进程通信的匿名管道和事件句柄,这些都通过命令行参数传递给子进程。

        我们再看下父进程等待的过程

代码语言:javascript
复制
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中读取子进程的输出

代码语言:javascript
复制
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_剩下的实现,把这个过程表达的很清楚

代码语言:javascript
复制
    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; \
      } \
    } \
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年04月08日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 死亡测试技术应用
  • 死亡测试技术分析
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档