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上實現的過程
- 測試實體中準備啟動新的進程,進程路徑就是本進程可執行文件路徑
- 子進程傳入了標準輸入輸出句柄
- 啟動子進程時傳入類型篩選,即指定執行該測試用例
- 監聽子進程的輸出
- 判斷子進程退出模式
? ? ? ? 子進程的執行過程是:
- 執行父進程指定的測試特例
- 運行死亡測試宏中的表達式
- 如果沒有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)使用方法和源码解析——死亡测试技术分析和应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Google Test(GTest)使用
- 下一篇: GoogleLog(GLog)源码分析