Получение полезных результатов GCov для библиотек только для заголовков
Для моей библиотеки C++ только для заголовков (много шаблонов и т. Д.) Я использую GCov для проверки покрытия тестами. Тем не менее, он сообщает о 100% покрытии для всех заголовков, потому что неиспользуемые функции не генерируются компилятором. Обнаружение обнаруженных функций вручную легко, но не дает возможности непрерывной интеграции…
Как решить это автоматически? Должен ли я просто использовать "попадание строк / LOC" в качестве метрики покрытия и никогда больше не достигать 100%?
3 ответа
Помимо обычных флагов для GCC, контролирующих встраивание;
--coverage -fno-inline -fno-inline-small-functions -fno-default-inline
Вы можете создавать экземпляры классов шаблонов в верхней части файлов модульного теста;
template class std::map<std::string, std::string>;
Это сгенерирует код для каждого метода в этом шаблонном классе, благодаря чему инструменты покрытия будут работать идеально.
Также убедитесь, что вы инициализируете свои файлы *.gcno (так же для lcov).
lcov -c -i -b ${ROOT} -d . -o Coverage.baseline
<run your tests here>
lcov -c -d . -b ${ROOT} -o Coverage.out
lcov -a Coverage.baseline -a Coverage.out -o Coverage.combined
genhtml Coverage.combined -o HTML
Я тоже наткнулся на эту проблему, и, к сожалению, мне не повезло с различными упомянутыми флагами, однако я обнаружил два способа генерировать более точную информацию о покрытии при работе с функциями только для заголовков.
Первый - добавить флаг -fkeep-inline-functions
(https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html).
Это дало мне только те результаты, которые мне были нужны, но возникли серьезные проблемы при попытке интеграции с другими библиотеками (даже с обычной стандартной библиотекой C++). Я получал ошибки ссылки, потому что некоторые функции, которые должны были быть удалены компоновщиком, не были (например, объявление функции без определения).
Второй подход (тот, который я выбрал в итоге) заключался в использовании __attribute(used)__
в GCC, чтобы аннотировать все мои функции API заголовка. В документации (https://gcc.gnu.org/onlinedocs/gcc-4.3.0/gcc/Function-Attributes.html) указано:
используемый
Этот атрибут, прикрепленный к функции, означает, что код должен быть выдан для функции, даже если кажется, что на функцию нет ссылки.
Я использовал #define
чтобы обернуть его, чтобы он был включен только тогда, когда я использую GCC и включено покрытие:
#ifdef _MSC_VER
#define MY_API
#elif defined __GNUC__ && defined COVERAGE
#define MY_API __attribute__((__used__))
#endif // _MSC_VER ? __GNUC__ && COVERAGE
Тогда использование выглядит так:
MY_API void some_inline_function() {}
Я собираюсь попытаться написать, как у меня все заработало в какой-то момент, на что я буду ссылаться отсюда в будущем, если я когда-нибудь дойду до этого
(Примечание: я также использовал -coverage -g -O0 -fno-inline
при компиляции)
Я также использую GCov для проверки покрытия тестами (тесты, написанные с помощью среды Google Test), кроме того, я использую плагин интеграции Eclipse GCov или инструмент LCov, чтобы генерировать удобные для просмотра представления результатов покрытия тестами. Необработанный вывод GCov слишком сложен для использования:-(.
Если у вас есть библиотеки шаблонов только с заголовками, вам также необходимо (с использованием флага G++ --coverage) оборудовать свои тестовые классы, которые создают экземпляры шаблонных классов и функций-членов шаблона, чтобы увидеть для них разумные выходные данные GCov.
С помощью упомянутых инструментов легко обнаружить шаблонный код, который вообще не создавался в тестовых примерах, поскольку в нем НЕТ аннотаций.
Я настроил образец и скопировал вывод LCov в ссылку DropBox, которую вы можете проверить.
Пример кода (TemplateSampleTest.cpp инструментируется с использованием g++ --coverage
опция):
TemplateSample.hpp
template<typename T>
class TemplateSample
{
public:
enum CodePath
{
Path1 ,
Path2 ,
Path3 ,
};
TemplateSample(const T& value)
: data(value)
{
}
int doSomething(CodePath path)
{
switch(path)
{
case Path1:
return 1;
case Path2:
return 2;
case Path3:
return 3;
default:
return 0;
}
return -1;
}
template<typename U>
U& returnRefParam(U& refParam)
{
instantiatedCode();
return refParam;
}
template<typename U, typename R>
R doSomethingElse(const U& param)
{
return static_cast<R>(data);
}
private:
void instantiatedCode()
{
int x = 5;
x = x * 10;
}
void neverInstantiatedCode()
{
int x = 5;
x = x * 10;
}
T data;
};
TemplateSampleTest.cpp
#include <string>
#include "gtest/gtest.h"
#include "TemplateSample.hpp"
class TemplateSampleTest : public ::testing::Test
{
public:
TemplateSampleTest()
: templateSample(5)
{
}
protected:
TemplateSample<int> templateSample;
private:
};
TEST_F(TemplateSampleTest,doSomethingPath1)
{
EXPECT_EQ(1,templateSample.doSomething(TemplateSample<int>::Path1));
}
TEST_F(TemplateSampleTest,doSomethingPath2)
{
EXPECT_EQ(2,templateSample.doSomething(TemplateSample<int>::Path2));
}
TEST_F(TemplateSampleTest,returnRefParam)
{
std::string stringValue = "Hello";
EXPECT_EQ(stringValue,templateSample.returnRefParam(stringValue));
}
TEST_F(TemplateSampleTest,doSomethingElse)
{
std::string stringValue = "Hello";
long value = templateSample.doSomethingElse<std::string,long>(stringValue);
EXPECT_EQ(5,value);
}
Смотрите вывод покрытия кода, сгенерированный из lcov здесь:
Предостережение: статистика 'Functions' представлена как 100%, что не совсем верно в отношении неинстанцированных шаблонных функций.
Так как я нашел этот вопрос очень полезным при настройке покрытия тестами для моей библиотеки только для заголовков, вот некоторые дополнительные вещи, которые я узнал в надежде, что они могут помочь другим:
Даже со всеми флагами, упомянутыми в этих ответах, у меня все еще были проблемы с оптимизацией неиспользуемых методов класса. После долгих экспериментов я обнаружил, что покрытие на основе источника лягушек (эти флаги: -fprofile-instr-generate -fcoverage-mapping
) включает в себя все методы класса и в целом является наиболее надежным способом получения данных покрытия. Я также использую флаги: -O0 -fno-inline -fno-elide-constructors
чтобы еще больше снизить риск оптимизации кода.
Для большой библиотеки проблема создания экземпляра шаблона все еще остается проблемой. Явное их создание - это хорошо, но если кто-то забудет об этом, вы получите неточные показатели покрытия кода. Смотрите мой ответ на этот вопрос для подхода к автоматической настройке данных покрытия кода для учета этого.