Найти неисполненные строки кода C++

В рамках моего модульного тестирования я хочу обеспечить покрытие кода тестами. Цель состоит в том, чтобы разместить что-то вроде REQUIRE_TEST где-нибудь в коде макросы и проверьте, все ли они были вызваны.

void foo(bool b) {
  if (b) {
    REQUIRE_TEST
    ...
  } else {
    REQUIRE_TEST
    ...
  }
}

void main() {
  foo(true);

  output_all_missed_REQUIRE_macros();
}

В идеале вывод должен включать исходный файл и строку макроса.

Моя первоначальная идея состояла в том, чтобы макросы создавали статические объекты, которые регистрировались бы на некоторой карте, а затем проверяли, все ли они были вызваны

#define REQUIRE_TEST \
        do { \
            static ___RequiredTest requiredTest(__func__, __FILE__, __LINE__);\
            (void)requiredTest;\
            ___RequiredTest::increaseCounter(__func__, __FILE__, __LINE__);\
        } while(false)

но статический объект создается только при первом вызове кода. Таким образом, карта содержит только функции, которые также учитываются в следующей строке - отсутствующие макросы REQUIRE_TEST не найдены. __attribute__((used)) игнорируется в этом случае.

GCC имеет хороший атрибут __attribute__((constructor)), но, по-видимому, предпочитает игнорировать его при размещении здесь (следующий код вместо статического объекта)

struct teststruct { \
  __attribute__((constructor)) static void bla() {\
    ___RequiredTest::register(__func__, __FILE__, __LINE__); \
  } \
};\

а также для

[]() __attribute__((constructor)) { \
  ___RequiredTest::register(__func__, __FILE__, __LINE__); \
};\

Единственный выход, о котором я могу подумать сейчас, - это а) вручную (или с помощью скрипта) проанализировать код вне обычной компиляции (uargh) или б) использовать __COUNTER__ макрос для подсчета макросов - но тогда я бы не знал, какие именно макросы REQUIRE_TEST не были вызваны... (и все ломается, если кто-то решит использовать __COUNTER__ макрос также...)

Есть ли достойные решения этой проблемы? Что мне не хватает? Было бы неплохо иметь макрос, который добавляет текущую строку и файл, так что некоторая переменная препроцессора при каждом вызове - но это невозможно, верно? Есть ли другие способы зарегистрировать что-то, что будет выполнено перед main() что можно сделать в теле функции?

3 ответа

Как насчет этого:

#include <iostream>

static size_t cover() { return 1; }

#define COV() do { static size_t cov[2] __attribute__((section("cov"))) = { __LINE__, cover() }; } while(0)

static void dump_cov()
{
        extern size_t __start_cov, __stop_cov;

        for (size_t* p = &__start_cov; p < &__stop_cov; p += 2)
        {
                std::cout << p[0] << ": " << p[1] << "\n";
        }
}

int main(int argc, char* argv[])
{
        COV();

        if (argc > 1)
                COV();

        if (argc > 2)
                COV();

        dump_cov();
        return 0;
}

Результаты:

$ ./cov_test
19: 1
22: 0
25: 0

а также:

$ ./cov_test x
19: 1
22: 1
25: 0

а также:

$ ./cov_test x y
19: 1
22: 1
25: 1

По сути, мы устанавливаем массив покрытия в именованном разделе памяти (очевидно, для этого мы использовали специфичные для GCC механизмы), который мы выгружаем после выполнения.

Мы полагаемся на постоянную инициализацию локальной статики, выполняемой при запуске - которая получает номера строк в массиве покрытия с установленным нулевым флагом покрытия - и на инициализацию через вызов функции cover() выполняется при первом выполнении оператора, который устанавливает флаги покрытия равными 1 для выполненных строк. Я не уверен на 100%, что все это гарантируется стандартом или какими версиями стандарта (я скомпилировал --std=c++11).

Наконец, сборка с '-O3' также дает правильные результаты (хотя и в другом порядке):

$ ./a
25: 0
22: 0
19: 1

а также:

$ ./a x
25: 0
22: 1
19: 1

а также

$ ./a x y
25: 1
22: 1
19: 1

Уродливый, но простой подход для REQUIRE_TEST использовать __LINE__ или же __COUNTER__ создать имя уникального статического объекта области файла, на который он ссылается, и который вызовет ошибку компилятора, если он не был объявлен. Затем вам нужно вручную объявить все такие объекты, по одному для каждого REQUIRE_TEST - но, по крайней мере, вы получите ошибку компиляции, если вы этого не сделали.

Эй, я сказал, что это ужасно!

Джереми дал мне правильную идею поближе взглянуть на разделы. Его ответ работает - но только без inline функции. Проведя дополнительные исследования, я смог найти следующее решение, которое в той же степени зависит от gcc (из-за названия раздела), но более гибко (и работает с встроенными функциями). Макрос теперь выглядит следующим образом:

#define REQUIRE_TEST \
    do { \
        struct ___a{ static void rt() {\
            ___RequiredTest::register_test(__FILE__, __LINE__);\
        } };\
        static auto ___rtp __attribute__((section(".init_array"))) = &___a::rt; \
        (void) ___rtp; \
        ___RequiredTest::increase_counter(__FILE__, __LINE__); \
    } while(false)

Размещение указателя функции в разделе .init_array фактически помещает его в список функций инициализации, которые вызываются перед main. Таким образом, можно быть уверенным, что локально определенная функция вызывается перед main.

Другие вопросы по тегам