Модифицированный текст файла
coredump.cpp
, в котором с помощью макроса UNIT_TESTING
встроена поддержка для тестирования. Если этот макрос определен, то, как я уже сказал, подавляется появление окна с ошибкой, и coredump файл создается с постоянным именем.Файл
coredump.cpp
:#include <windows.h>
#include <dbghelp.h>
#include <stdio.h> // _snprintf
// Наш обработчик непойманного исключения.
static LONG WINAPI ExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo);
// Статический экземпляр переменной, конструктор которой
// вызывается до начала функции main().
static struct CoredumpInitializer {
CoredumpInitializer() {
SetUnhandledExceptionFilter(&ExceptionFilter);
}
} coredumpInitializer;
LONG WINAPI ExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) {
char fname[_MAX_PATH];
SYSTEMTIME st;
GetLocalTime(&st);
HANDLE proc = GetCurrentProcess();
#ifdef UNIT_TESTING
lstrcpy(fname, "___coredump.dmp");
#else
// Формируем имя для coredump'а.
_snprintf(
fname, _MAX_PATH,
"coredump-%ld-%ld-%04d%02d%02d%02d%02d%02d%03d.dmp",
GetProcessId(proc), GetCurrentThreadId(),
st.wYear, st.wMonth, st.wDay,
st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
);
#endif
// Открываем файл.
HANDLE file = CreateFile(
fname,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
MINIDUMP_EXCEPTION_INFORMATION info;
info.ExceptionPointers = ExceptionInfo;
info.ThreadId = GetCurrentThreadId();
info.ClientPointers = NULL;
// Собственно, сбрасываем образ памяти в файл.
MiniDumpWriteDump(
proc,
GetProcessId(proc),
file,
MiniDumpWithFullMemory,
ExceptionInfo ? &info : NULL,
NULL, NULL
);
CloseHandle(file);
#ifdef UNIT_TESTING
return EXCEPTION_EXECUTE_HANDLER;
#else
return EXCEPTION_CONTINUE_SEARCH;
#endif
}
Теперь, собственно, тест (файл coredump_unittest.cpp
):#include "gtest/gtest.h"
#include <fstream>
#include <windows.h>
#include <stdlib.h>
TEST(Coredump, CoredumpCreation) {
const char* coredump = "___coredump.dmp";
// На всякий случай заведомо стираем старые файлы.
EXPECT_EQ(0, std::system("cmd.exe /c del ___coredump_main.* 1>nul 2>&1"));
// Создаем файл с тестовой программой.
std::string program = "int main() { *(char *)0 = 0; return 0; }";
std::ofstream os("___coredump_main.cpp");
os << program << std::endl;
os.close();
// Компилируем тестовую программу с опцией UNIT_TESTING.
// С этой опцией coredump файл будет создаваться с постоянным
// именем "___coredump.dmp", и будет подавляется окно с сообщением
// об ошибке.
EXPECT_EQ(
0, std::system(
"cl /Zi /DUNIT_TESTING /Fe___coredump_main.exe"
" ___coredump_main.cpp coredump.cpp dbghelp.lib"
" 1>nul 2>&1"
)
);
// На всякий случая удаляем старый coredump файл.
std::remove(coredump);
// Убеждаемся, что файл действительно удалился.
std::ifstream isdel(coredump);
EXPECT_FALSE(isdel.good());
isdel.close();
// Запускаем тестовую программу.
ASSERT_EQ(0xC0000005, std::system("___coredump_main.exe"));
// Проверяем, создался ли файл coredump.dmp.
std::ifstream is(coredump);
EXPECT_TRUE(is.good());
is.close();
// Удаляем за собой временные файлы.
EXPECT_EQ(0, std::system("cmd.exe /c del ___coredump_main.* 1>nul 2>&1"));
std::remove(coredump);
}
Данный тест имеет ряд существенных недостатков. Во-первых, он использует файловую систему, и во-вторых, он вызывает компилятор, что занимает небольшое, но все же время. Недостатки неприятные, но в целом приемлемые.Кстати, Google Test Framework умеет делать так называемые "смертельные" (death) тесты. То есть можно протестировать именно аварийное "падение" фрагмента кода, например, из-за нарушения защиты памяти, и для проведения такого теста не надо вручную компилировать что-либо, как мы делали тут. К сожалению, эта возможность основана на использования юниксового системного вызова fork()
и поэтому доступна только на UNIX платформах.
Дежурный файл для запуска тестов runner.cpp
: #include "gtest/gtest.h"
int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Традиционно, для компиляции тестов нам нужна Google Test Framework. Как я уже писал, вы можете скачать мою модификацию этой библиотеки, которая сокращена без потери какой-либо функциональности до двух необходимых файловКомпилируем, например в Visual Studio 2008:gtest/gtest.h
иgtest-all.cc
.
cl /EHsc /I. /Fecoredump_unittest_vs2008.exe /DWIN32 runner.cpp coredump_unittest.cpp gtest\gtest-all.cc
Запускаем:[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from Coredump
[ RUN ] Coredump.CoredumpCreation
[ OK ] Coredump.CoredumpCreation
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran.
[ PASSED ] 1 test.
Работает.Сразу скажу, я проверял все это только под Windows XP SP2 и Server 2003. Пожалуйста, сообщайте, если есть какие-то проблемы или тонкости под другими виндами.
Как это часто бывает в unit-тестировании, тест получился больше, чем сам тестируемый код. Но повторюсь это того стоит. Буквально скоро расскажу о моих приключениях с модулем таймера, и как меня выручили тесты.
Другие посты по теме:
Комментариев нет:
Отправить комментарий