Возвращаясь к теме про фаззеры

Вот код, которым я пользуюсь для написания стресс тестов и фаззеров в своих проектах. Класс 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 поддерживает два режима работы:

  1. При первом проходе он генерирует случайные целые в заданном диапазоне и сохраняет каждое сгенерированное число в лог;

  2. При втором и следующих проходах, 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.

comments powered by Disqus