Загрузка...

Дебаг и стресс тестирование

Тема в разделе C/C++ создана пользователем tgf1090 2 июл 2022. 338 просмотров

  1. tgf1090
    tgf1090 Автор темы 2 июл 2022 6 12 июл 2021
    В данной теме собраны фишки и подходы отладки и стресс тестирования программы.

    Компиляция кода из консоли
    Для компиляции используем команду: g++ -o <executable name> <source name> -std=c++17 -O2, где <executable name> – имя исполняемого файла, в который будет скомпилирован код, а <source name> – имя файла с кодом. Чтобы это работало, нужно, чтобы путь к g++ был прописан в PATH.

    При компиляции можно указать различные дополнительные параметры: -Wall -Wextra -Wshadow – включает показ большого количество warning'ов при компиляции, -DLOCAL – определяет макрос LOCAL, который в дальнейшем можно использовать в ifdef и подобных конструкциях.

    Все дополнительные параметры также можно использовать при компиляции кода в вашей среде программирования. В этой статье собраны другие полезные параметры, которые можно указывать при компиляции.

    Дебаг
    Для удобства дебага выводом можно определить следующий макрос:
    C
    #ifdef LOCAL
    #define debug(x) cerr << (#x) << ": " << x << endl;
    #endif
    Пример использования:
    C
    int x = 5, y = 10;
    string s = "Hello!";

    debug(x)
    debug(s)
    debug(x + y)
    debug(s << ' ' << x << ' ' << y << ' ' << x + y)
    Вывод будет следующим:
    C

    x: 5
    s: Hello!
    x + y: 15
    s << ' ' << x << ' ' << y << ' ' << x + y: Hello! 5 10 15
    За счет того, что LOCAL был определен при компиляции локально, в тестирующей системе данный код не скомпилируется, так как макрос debug(x) не будет определен. Если мы хотим, чтобы код компилировался и в тестирующей системе, можно написать более сложную конструкцию:
    C
    #ifdef LOCAL
    #define debug(x) cerr << (#x) << ": " << x << endl;
    #else
    #define debug(x) ;
    #endif
    Файловый ввод-вывод
    Для того, чтобы использовать локально файловый ввод-вывод, можно написать конструкцию:
    C
    #ifdef LOCAL
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    #endif
    Выход за границы массива
    При использовании vector, для гарантированного получения Runtime Error при обращении к несуществующим элементам, можно вместо оператора квадратных скобок использовать метод at.

    Пример:
    C
    vector<int> a = {1, 2, 3};
    cout << a[5];
    cout << a.at(5);
    Второй cout гарантированно упадет с Runtime Error и напишет вполне читаемое сообщение об ошибке. Злоупотреблять этим на туре не стоит, так как at работает несколько дольше.

    Также можно использовать #define _GLIBCXX_DEBUG, объявленный перед всеми include. В случае выхода за границы программа также будет падать с RE.

    Увеличение размера стека
    Для увеличения размера стека можно использовать следующую конструкцию:
    C
    #pragma comment(linker, "/stack:200000000")
    Здесь 200000000 – это размер стека в байтах.

    На Linux можно локально увеличить стек при помощи команды ulimit -s unlimited.

    Рандом
    Для использования рандома с фиксированным seed'ом, можно воспользоваться следующей конструкцией:
    C
    mt19937 rnd(seed);
    cout << rnd();
    Вызов rnd возвращает целое 32-битное беззнаковое число. Для генерации 64-битных чисел можно использовать mt19937_64.

    Для того, чтобы сделать нефиксированный заранее seed, можно воспользоваться random_device:
    C
    random_device rd;
    mt19937 rnd(rd());
    cout << rnd();
    На некоторых платформах rd() может работать детерминированно, и при каждом запуске программы выдавать один и тот же seed. Чтобы этого избежать, можно использовать более сложную конструкцию:
    C
    mt19937 rnd(chrono::high_resolution_clock::now().time_since_epoch().count());
    cout << rnd();
    Для генерации целых чисел в нужном диапазоне можно написать свою функцию, либо воспользоваться uniform_int_distribution:
    [CODE=c]uniform_int_distribution<int> dist(1, 10);
    cout << dist(rnd);[/CODE]Данный код выведет целое число из отрезка [1, 10].

    Для генерации вещественных чисел существует uniform_real_distribution, почитать про который можно на cppreference.

    Генерация случайной перестановки
    [CODE=c]vector<int> p(n);
    iota(p.begin(), p.end(), 1);
    shuffle(p.begin(), p.end(), rnd);[/CODE]Генерация случайного дерева
    Сгенерируем подвешенное дерево с корнем в вершине 0. Для этого для каждой вершины сгенерируем предка с номером, меньшим, чем номер текущей вершины:
    [CODE=c]for (int i = 1; i < n; ++i) {
    cout << i << ' ' << rnd() % i << '\n';
    }[/CODE]Такой способ генерации не очень хорош тем, что корень дерева – всегда вершина 0, а также в дереве всегда есть ребро (0,1). Поэтому после генерации дерева можно применить случайную перестановку к номерам всех вершин.

    Также при таком способе генерации средняя высота дерева будет порядка O(log n), что может быть не хорошо. Можно воспользоваться следующим хаком:
    [CODE=c]for (int i = 1; i < n; ++i) {
    int parent = -1;
    for (int it = 0; it < 5; ++it) {
    parent = max(parent, rnd() % i);
    }
    cout << i << ' ' << parent << '\n';
    }[/CODE]Если константу вложенного цикла делать больше, то глубина дерева будет получаться больше. Аналогично, если хочется генерировать неглубокие деревья, можно вместо максимума случайных значений выбирать минимум.

    Стресс тестирование в одном файле
    Шаблон для стресс тестирования в одном файле выглядит следующим образом:
    [CODE=c]// Делаем входные данные теста глобальными, либо передаем их аргументами в функции

    int correct() {
    // Здесь пишем корректное решение
    }

    int wrong() {
    // Здесь пишем неправильное решение
    }

    void gen() {
    // Генерируем тест каким-либо образом
    }

    void stress() {
    for (int test = 1; ; ++test) {
    cout << "Test #" << test << '\n';
    gen();
    if (correct() != wrong()) {
    // Выводим тест на экран
    break;
    }
    }
    }[/CODE]Разумеется, иногда сравнение ответов корректного и неправильного решений выполняется не так тривиально, для этого его удобно вынести в отдельную функцию.

    Стресс тестирование при помощи bash скрипта (Linux only)
    Создадим несколько файлов: correct.cpp – корректное решение, wrong.cpp – неправильное решение, gen.cpp – генератор тестов. Код всех этих файлов работает со стандартным вводом и выводом.

    Теперь напишем bash скрипт stress.sh для тестирования:
    [CODE=code]#!/bin/bash

    # Компилируем все файлы
    g++ -o correct correct.cpp -std=c++17 -O2
    g++ -o wrong wrong.cpp -std=c++17 -O2
    g++ -o gen gen.cpp -std=c++17 -O2

    for (( i = 1; i < 100000; i++ ))
    do
    echo "Test number: ${i}"

    # Генерируем тест и записываем его в файл test.txt
    ./gen > test.txt

    # Запускаем корректное решение и записываем ответ в correct.txt
    ./correct < test.txt > correct.txt

    # Запускаем неверное решение и записываем ответ в wrong.txt
    ./wrong < test.txt > wrong.txt

    # Сравниваем файлы с ответом на равенство
    # Здесь можно использовать diff, либо какую-то другую утилиту на ваше усмотрение
    # Можно даже написать свой чекер!
    if cmp -s correct.txt wrong.txt; then
    echo "OK!"
    else
    echo "Fail!"
    break
    fi
    done[/CODE]Перед запуском нужно выдать права на запуск командой chmod +x stress.sh.
     
    2 июл 2022 Изменено
  2. Whales_Nik
    Whales_Nik 2 июл 2022 50 27 мар 2022
    Статья довольно полезная, некоторые факты можно применить в олимпиадном программировании. Сразу видно, автор старался =)
     
  3. GGSenpai
    GGSenpai 26 июл 2022 148 20 авг 2018
    Почему не юзать встроенный отладчик? Выход за пределы массива можно словить с помощью AddressSanitazer
    Для написания тестов есть нормальные фреймворки со своими фишками, гугл всегда поможет!
     
Top
Загрузка...