Покрытие веток 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. В качестве альтернативы я мог бы также отключить покрытие филиала только для этого класса исключений.
Я признаю, что это не простое решение, но для моих целей оно также идеально подходит и по другим причинам (мне нужно, чтобы этот класс исключений был гибким, чтобы я мог предложить другую реализацию его: один раз выбрасывая исключение, другой раз делая что-то еще).