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

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

© 版权声明
THE END
喜欢就支持一下吧
点赞400 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容