Возвращаясь к теме про фаззеры
Jan 31, 2008 · CommentsC/C++ПрограммированиеТестирование
Вот код, которым я пользуюсь для написания стресс тестов и фаззеров в своих проектах. Класс EntropyGenerator - обертка вокруг генератора случайных чисел.
class EntropyGenerator
{
public:
EntropyGenerator():
m_replay_index(std::numeric_limits<size_t>::max())
{
m_log.reserve(1024 * 64);
}
void
replay()
{
m_replay_index = 0;
}
size_t
generate(
size_t up_to
)
{
size_t r;
if (m_replay_index == std::numeric_limits<size_t>::max())
{
if (up_to > 0)
{
errno_t err = rand_s(&r);
BOOST_REQUIRE(err == 0);
r /= std::numeric_limits<size_t>::max() / up_to;
}
else
{
r = 0;
}
m_log.push_back(r);
}
else
{
r = m_log[m_replay_index++];
if (m_replay_index == m_log.size())
{
// This is the last generated number
__debugbreak();
}
}
BOOST_REQUIRE((r == 0 && up_to == 0) || (0 <= r && r < up_to));
return r;
}
protected:
std::vector<size_t> m_log;
size_t m_replay_index;
};
EntropyGenerator поддерживает два режима работы:
-
При первом проходе он генерирует случайные целые в заданном диапазоне и сохраняет каждое сгенерированное число в лог;
-
При втором и следующих проходах, EntropyGenerator переключается в режим воспроизведения сгенерированных чисел. При выдаче последнего сгенерированного числа срабатывает точка останова, позволяя посмотреть, что происходит в момент сбоя в отладчике.
Функция runStress запускает переданный тест, позволяя в случае сбоя прогнать тест сначала нужное количество раз. Сбоем считается любое непойманное исключение. Я пользуюсь Boost.Test, а там проверочные макросы (BOOST_REQUIRE и Ko) бросают исключения.
template <typename TestT>
runStress(
TestT* test,
void
size_t replay_count
)
{
EntropyGenerator rnd;
for (size_t i = 0; i <= replay_count; )
{
try
{
// Run test
(*test)(rnd);
return;
}
catch (...)
{
if (i < replay_count)
{
++i;
rnd.replay();
}
else
throw;
}
}
}
Пример теста:
// 64 chars
char randomData[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ[]";
void
stressLoadCells(
EntropyGenerator& rnd
)
{
for (size_t repeat = 20; repeat > 0; --repeat)
{
// Fill the source with random data of random length
Source source;
source.m_data.assign(rnd.generate(128), ' ');
for (
std::string::iterator i = source.m_data.begin(), i_end = source.m_data.end();
i != i_end;
++i)
{
*i = randomData[rnd.generate(64)];
}
// Create buffer with a cell of random size
Buffer buffer(&source, rnd.generate(15) + 1);
for (size_t i = 0; i < 128; ++i)
{
size_t j = rnd.generate(static_cast<size_t>(source.size()));
BOOST_REQUIRE(buffer[j] == source.m_data[j]);
}
}
}
void
stressTestLoadCells()
{
runStress(&stressLoadCells, 1);
}
Точка входа в тест - функция stressTestLoadCells. Тестовый код помещается в функцию stressLoadCells, которая и вызывается один (в случае успеха) или несколько (в случае сбоя) раз.
Тест пишется как обычно. Никакой особенной поддержки повторов, логов в коде теста нет. Там где нужно получить очередное случайное число вызывается rnd.generate с нужным диапазоном. Удобно и довольно эффективно, хотя этот код ещё сильно недоработан. Нет возможности сохранить сбойную последовательность чисел для того, чтобы её затем воспроизвести. Нет возможности вывалиться в отладчик не на последнем числе, а за N вызовов rnd.generate.