Google Test(GTest)の使用方法とソースコードの解析-死亡テスト技術の分析と応用
15136 ワード
死亡テストは、論理がプロセスを終了させるかどうかを判断するために設計されています.このようなシーンはあまり見られませんが、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は論理をテストする式であり、関数であってもよいし、オブジェクトであってもよいし、いくつかの式の組み合わせであってもよい.
注意下の正規表現この機能はlinuxシステムのみをサポートし、windowsではサポートされていないので、windowsではこのパラメータに空欄を渡します.完全な例を見てみましょう
まずwindowsで実現した過程を要約しますテストエンティティで新しいプロセスを開始する準備ができています.プロセスパスは、このプロセスが実行可能なファイルパス です.サブプロセスは、標準入出力ハンドル に入力.サブプロセスの開始時に入力タイプフィルタ、すなわち、このテストの実行例 を指定する.傍受サブプロセスの出力 判定サブプロセス終了モード サブプロセスの実行プロセスは次のとおりです.親プロセスが指定するテスト特例 を実行する.死亡テストマクロの式 を実行する crashがない場合、場合によっては終了モード を選択する.
EXPECTを見てみましょう最終的にGTEST_に呼び出されるDEATHの実装DEATH_TEST_マクロ#マクロ#
AssemerRoleは、主に親プロセスがサブプロセスを開始する論理です.Waitは、親プロセスが子プロセスの実行を待機し、子プロセスの出力を読み込もうとします.
DeathTest::Createメソッドは最終的にDefaultDeathTestFactory::Createメソッドに入ります.
WindowsDeathTest::AssumeRole()の実装を見てみましょう
以前と同様にflagを取得する必要があり、NULLでなければサブプロセスであり、書き込みハンドルを設定し、自分のロールに戻る.親プロセスの場合は、次の論理を実行します.
親プロセスが待つプロセスを見てみましょう
サブプロセスは、式の実行後にAbortを呼び出して対応するエラーを返します.GTEST_DEATH_TEST_残りの実現は、この過程をはっきりと表現しています.
死亡テスト技術応用
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行目にテストの予想に一致したが、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で実現した過程を要約します
EXPECTを見てみましょう最終的にGTEST_に呼び出されるDEATHの実装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はロールです.親プロセスロールはOVERSEです.TEST、サブプロセスの役割はEXECUTE_TEST.親子プロセスはこのテスト特例ロジックに入るため、ロールタグで実行ロジックを区別します.AbortReason列挙のタイプは、テストが終了した理由を表します.AssemerRoleは、主に親プロセスがサブプロセスを開始する論理です.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などを呼び出すが、この2つの関数の実行メカニズムも異なる.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; \
} \
} \