LCOV/GCOV охват веток С++ по всему региону

мы используем LCOV/GCOV для обеспечения тестового покрытия наших проектов. В последнее время мы также попытались включить покрытие веток. Но похоже, что это просто не дает результатов, которые мы ожидали от представления разработчика на высоком уровне.

Использование покрытия ветвей с С++ удаляет отчет с помощью ветвей повсюду. Мы подозреваем (как указывает поиск проблем), что в основном код обработки исключений создает эти "скрытые ветки". И GCOV/LCOV, похоже, не пропускают их.

Я создал небольшой тестовый проект, чтобы показать проблему: https://github.com/ghandmann/lcov-branch-coverage-weirdness

В настоящее время мы используем Ubuntu 16.04. с:

  • gcc v5.4
  • lcov и genhtml v1.12

Наш производственный код построен с включенным С++ 11. Минимальный пример не строится с включенным С++ 11, но, когда мы немного экспериментировали со всеми различными опциями (стандарт С++, оптимизация, -fno-exceptions), мы не принесли пропущенного результата.

У кого-нибудь есть идеи? Tipps? Используем ли мы что-то не так? Это - как указано где-то еще - действительно ожидаемое поведение?

Спасибо четыре раза ваше время!

Update:

Как также указано в списке рассылки gcc-help эти "скрытые ветки" происходят из-за обработки исключений. Таким образом, добавление переключателя "-fno-exceptions" в gcc обеспечивает 100% -ный охват веток для "простых" программ. Но когда исключения отключены, gcc отказывается компилировать код, который фактически использует исключения (например, try-catch, throw). Поэтому для реального кода производства это не вариант. Похоже, вам нужно просто объявить, что 50% -ное покрытие будет новым 100% в этом случае.;)

Ответ 1

Дело в том, что GCC также записывает информацию о ветвях для каждой строки, в которой возможен выход области из-за какого-то исключенного исключения (например, в Fedora 25 с GCC 6.3.1 и lcov 1.12).

Значение этой информации ограничено. Основным прецедентом для данных об охвате веток являются сложные операторы if, которые имеют многоклассовое логическое выражение, подобное этому:

if (foo < 1 && (bar > x || y == 0))

Скажите, что вам интересно проверить, распространяется ли ваш набор тестов на случай bar > x, или если у вас есть только тестовые примеры, где y == 0.

Для этого полезно использовать сбор данных о филиале и визуализацию с помощью lcov genhtml. Для простых if-утверждений типа

if (p == nullptr) {
  return false;
}
return true;

вам не нужны данные о охвате веток, потому что вы видите, была ли ветка занята или нет, посмотрев на охват следующих строк.

Ввод genhtml, который генерируется lcov, находится в относительно простом текстовом формате (см. geninfo(1)). Таким образом, вы можете выполнить его пост-обработку таким образом, чтобы все строки, начинающиеся с BRDA: и не принадлежащие к if-statement, удаляются. См. Например filterbr.py, который реализует этот подход. См. Также gen-coverage.py для других шагов обработки lcov/genhtml и example где результирующий файл трассировки загружается в кодеки (кодеки не используют genhtml, но могут импортировать файлы трассировки lcov и отображать данные о покрытии ветвей).

(не) Альтернативы

  • Отключение исключений - это только вариант, когда ваш код на С++ не использует
  • компиляция с чем-то вроде -O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls несколько уменьшает количество записанных данных покрытия ветвей, но не намного
  • Clang поддерживает коллекцию покрытия стиля GCOV, но также реализует другой подход, называемый "исходный код покрытия" (скомпилируйте с -fprofile-instr-generate -fcoverage-mapping и пост-процесс с llvm-profdata и llvm-cov). Эта программная цепочка не поддерживает данные по охвату веток, хотя (по состоянию на 2017-05-01).
  • по умолчанию lcov + genhtml не генерирует данные о покрытии ветки - иногда вам это действительно не нужно (см. выше), таким образом, это значит отключить его (см. --rc lcov_branch_coverage=0 и --no-branch-coverage)

Ответ 2

GCC добавит кучу элементов обработки исключений. Особенно, когда вы выполняете функциональные вызовы.

Вы можете исправить это, добавив -fno-exceptions -fno-inline к вашей сборке.

Я должен добавить, вы, вероятно, хотите только эти флаги для тестирования. Так что-то вроде этого:

g++ -O0 --coverage -fno-exceptions -fno-inline main.cpp -o test-coverage

Ответ 3

Вы можете попробовать g++ -O3 --coverage main.cpp -o testcov. Я пробовал это с g++ - 5.4 в вашем файле, и он отлично работает, что означает, что исключения отбрасываются стандартными вызовами printf и string.

Фактически любой флаг оптимизации, отличный от O0, заставит gcov игнорировать исключения, созданные для обычных вызовов библиотеки в файле CPP. Я не уверен, что нормальные исключения также будут оптимизированы (я так не думаю, но еще не пробовал).

Но я не уверен, что у вас есть какие-либо требования в вашем проекте, чтобы использовать только код O0 с вашим кодом, а не O1, O2, O3 или даже Os.

Ответ 4

Я столкнулся с одной и той же проблемой, и я хочу избавиться от этих непокрытых ветвей из-за исключений. Я нашел подходящее решение для меня:

Я просто избегаю использования "throw exception" в своем коде, который я хочу использовать, напрямую. Я разработал класс, который предлагает некоторые методы, которые вместо этого исключают исключения. Поскольку класс исключений не является таким сложным, я действительно не забочусь о покрытии, поэтому я просто исключаю все с помощью LCOV_EXCL_START и LCOV_EXCL_STOP. В качестве альтернативы я мог бы также отключить покрытие ветки только для этого класса исключений.

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