Найти неисполненные строки кода 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.