Покрытие веток LCOV/GCOV с производством веток C++ повсюду

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

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

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

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

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

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

У кого-нибудь есть идеи? Tipps? Мы используем что-то неправильно? Является ли это - как указано в другом месте - действительно ожидаемым поведением?

Обновить:

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

4 ответа

Дело в том, что 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: и не принадлежат, если-оператор удаляются. Смотри например filterbr.py который реализует этот подход. Смотрите также gen-coverage.py для других шагов обработки lcov / genhtml и примера проекта, в котором полученный файл трассировки загружается в codecov (codecov не использует genhtml но может импортировать файлы трассировки lcov и отображает данные покрытия филиала).

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

  • отключение исключений возможно только тогда, когда ваш код C++ не использует
  • составление с чем-то вроде -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)

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

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

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

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

Ведется PR, который устраняет эти ограничения. https://github.com/linux-test-project/lcov/pull/86.

В этой статье объясняется теория, лежащая в основе реализации.

https://user-images.githubusercontent.com/471374/99132883-bc70d780-25cc-11eb-84da-4cf92b4bdc69.png

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

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

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

Я проделал некоторую работу, чтобы добавить фильтрацию в geninfo / lcov / genhtml, чтобы удалить ветки в коде C/C++, которые связаны со строками исходного кода, которые, похоже, не содержат никаких условных выражений, - используя некоторые относительно простые регулярные выражения. Похоже, что фильтры работают в нашей кодовой базе / на продуктах, в которых использовались модифицированные инструменты lcov. Фильтры не идеальны, и их легко победить кровожадный пользователь.

Недавно я получил разрешение на апстрим моих обновлений lcov. Вы можете найти их на https://github.com/henry2cox/lcov.

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

Периферийным дополнительным изменением было добавление "фильтрации", как описано выше, в основном потому, что покрытие ветвей казалось непригодным для кода C++ без него.
Вы можете найти (по общему признанию, хакерские и легко обходимые) регулярные выражения в методе lcovutil::ReadCurrentSource::containsConditional - рядом с строкой.../bin/lcovutil.pm:1122

Хотя и не идеально: похоже, этот хак работает с нашим кодом. Ваш пробег может отличаться.

Это проверено с perl/5.12.5 и gcc/8.3.0 и 9.2.0. Он может работать и с другими версиями (пожалуйста, дайте мне знать, если вы обнаружите проблемы с переносимостью, чтобы я мог их исправить).

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

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

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

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